fix: add cli

This commit is contained in:
2025-01-31 02:02:07 +08:00
parent ca5f4984a4
commit 733b697ee2
11 changed files with 818 additions and 54 deletions

19
scripts/cli.ts Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env node
import { Command } from 'commander';
import { rewriteAllObservableSubscribeToLastValueFrom } from './code-transform';
const program = new Command();
program
.version('1.0.0')
.description('A CLI tool to help develop oidc-client-rx');
program
.command('rewrite <pattern>')
.description('Rewrite files matching the given glob pattern')
.action(async (pattern: string) => {
await rewriteAllObservableSubscribeToLastValueFrom(pattern);
});
program.parse(process.argv);

View File

@@ -0,0 +1,35 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { Biome, Distribution } from '@biomejs/js-api';
import { rewriteObservableSubscribeToLastValueFrom } from './code-transform';
describe('writeAllSpecObservableSubscribeToLastValueFrom', () => {
it('should transform valid string', async () => {
const actual = await rewriteObservableSubscribeToLastValueFrom(
'index.ts',
`refreshSessionIframeService
.refreshSessionWithIframe(allConfigs[0]!, allConfigs)
.subscribe((result) => {
expect(
result
).toHaveBeenCalledExactlyOnceWith(
'a-url',
allConfigs[0]!,
allConfigs
);
});`
);
const expect = `const result = await lastValueFrom(refreshSessionIframeService.refreshSessionWithIframe(allConfigs[0]!, allConfigs));
expect(result).toHaveBeenCalledExactlyOnceWith('a-url',allConfigs[0]!,allConfigs);`;
const biome = await Biome.create({
distribution: Distribution.NODE,
});
assert.equal(
biome.formatContent(actual, { filePath: 'index.ts' }).content,
biome.formatContent(expect, { filePath: 'index.ts' }).content
);
});
});

113
scripts/code-transform.ts Normal file
View File

@@ -0,0 +1,113 @@
import assert from 'node:assert/strict';
import fsp from 'node:fs/promises';
import {
type ClassElement,
type MagicString,
type Statement,
parseSync,
} from 'oxc-parser';
import { type Node, walk } from 'oxc-walker';
function sourceTextFromNode(
context: { magicString?: MagicString },
node: Node
): string {
const magicString = context.magicString;
assert(magicString, 'magicString should be defined');
const start = node.start;
const end = node.end;
return magicString.getSourceText(start, end);
}
export async function rewriteObservableSubscribeToLastValueFrom(
filename: string,
content?: string
) {
const code = content ?? (await fsp.readFile(filename, 'utf-8'));
const parsedResult = parseSync('index.ts', code);
const magicString = parsedResult.magicString;
walk(parsedResult, {
leave(node, _, context) {
const transformExprs = <T extends Statement[]>(
children: T
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
): T => {
const newChildren: T = [] as any as T;
for (const child of children) {
if (
child.type === 'ExpressionStatement' &&
child.expression.type === 'CallExpression' &&
child.expression.callee.type === 'StaticMemberExpression' &&
child.expression.callee.property.name === 'subscribe' &&
child.expression.arguments[0]?.type === 'ArrowFunctionExpression' &&
child.expression.arguments[0].body.type === 'FunctionBody'
) {
const awaited =
child.expression.arguments[0].params.kind ===
'ArrowFormalParameters' &&
child.expression.arguments[0].params.items[0]?.type ===
'FormalParameter' &&
child.expression.arguments[0].params.items[0].pattern.type ===
'Identifier'
? child.expression.arguments[0].params.items[0].pattern.name
: undefined;
const newContent =
(awaited
? `const ${awaited} = await lastValueFrom(${sourceTextFromNode(
context,
child.expression.callee.object
)});\n`
: `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});\n`) +
child.expression.arguments[0].body.statements
.map((s) => sourceTextFromNode(context, s))
.join(';\n');
const newStatements = parseSync('index.ts', newContent).program
.body as any[];
magicString.remove(child.start, child.end);
magicString.appendRight(child.start, newContent);
newChildren.push(...newStatements);
} else {
newChildren.push(child as any);
}
return newChildren;
}
return newChildren;
};
if ('body' in node && Array.isArray(node.body) && node.body.length > 0) {
const children = node.body;
node.body = transformExprs(children as any)!;
} else if (
'body' in node &&
node.body &&
'type' in node.body &&
node.body.type === 'FunctionBody'
) {
console.error('xxx', node.body.type);
const children = node.body.statements;
node.body.statements = transformExprs(children)!;
}
},
});
const result = magicString.toString();
return result;
}
export async function rewriteAllObservableSubscribeToLastValueFrom(
pattern: string | string[]
) {
const files = fsp.glob(pattern);
for await (const file of files) {
const source = await fsp.readFile(file, 'utf-8');
const result = await rewriteObservableSubscribeToLastValueFrom(file);
if (source !== result) {
console.error('not equal');
}
await fsp.writeFile(file, result, 'utf-8');
}
}