fix: add cli
This commit is contained in:
parent
ca5f4984a4
commit
733b697ee2
15
package.json
15
package.json
@ -32,7 +32,8 @@
|
||||
"publish": "npm run build && npm publish ./dist",
|
||||
"coverage": "vitest run --coverage",
|
||||
"lint": "ultracite lint",
|
||||
"format": "ultracite format"
|
||||
"format": "ultracite format",
|
||||
"cli": "tsx scripts/cli.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ngify/http": "^2.0.4",
|
||||
@ -43,18 +44,24 @@
|
||||
"rxjs": "^7.4.0||>=8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@biomejs/js-api": "0.7.1",
|
||||
"@biomejs/wasm-nodejs": "^1.9.4",
|
||||
"@evilmartians/lefthook": "^1.0.3",
|
||||
"@playwright/test": "^1.49.1",
|
||||
"@rslib/core": "^0.3.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.10.1",
|
||||
"@vitest/coverage-v8": "^3.0.1",
|
||||
"commander": "^13.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"oxc-parser": "^0.48.1",
|
||||
"oxc-walker": "^0.2.2",
|
||||
"rfc4648": "^1.5.0",
|
||||
"rxjs": "^7.4.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.3",
|
||||
"ultracite": "^4.1.15",
|
||||
"vitest": "^3.0.1",
|
||||
"rxjs": "^7.4.0"
|
||||
"vitest": "^3.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
"rxjs",
|
||||
|
614
pnpm-lock.yaml
generated
614
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
19
scripts/cli.ts
Normal file
19
scripts/cli.ts
Normal 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);
|
35
scripts/code-transform.spec.ts
Normal file
35
scripts/code-transform.spec.ts
Normal 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
113
scripts/code-transform.ts
Normal 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');
|
||||
}
|
||||
}
|
@ -346,11 +346,9 @@ describe('CheckSessionService', () => {
|
||||
|
||||
describe('checkSessionChanged$', () => {
|
||||
it('emits when internal event is thrown', async () => {
|
||||
checkSessionService.checkSessionChanged$
|
||||
.pipe(skip(1))
|
||||
.subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
const result = await lastValueFrom(checkSessionService.checkSessionChanged$
|
||||
.pipe(skip(1)));
|
||||
expect(result).toBe(true);
|
||||
|
||||
const serviceAsAny = checkSessionService as any;
|
||||
|
||||
@ -358,9 +356,8 @@ describe('CheckSessionService', () => {
|
||||
});
|
||||
|
||||
it('emits false initially', async () => {
|
||||
checkSessionService.checkSessionChanged$.subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
const result = await lastValueFrom(checkSessionService.checkSessionChanged$);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('emits false then true when emitted', async () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DOCUMENT } from '../dom';
|
||||
import { Injectable, NgZone, OnDestroy, inject } from 'injection-js';
|
||||
import { Injectable, NgZone, type OnDestroy, inject } from 'injection-js';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import type { OpenIdConfiguration } from '../config/openid-configuration';
|
||||
import { DOCUMENT } from '../dom';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { EventTypes } from '../public-events/event-types';
|
||||
import { PublicEventsService } from '../public-events/public-events.service';
|
||||
@ -74,7 +74,7 @@ export class CheckSessionService implements OnDestroy {
|
||||
}
|
||||
|
||||
start(configuration: OpenIdConfiguration): void {
|
||||
if (!!this.scheduledHeartBeatRunning) {
|
||||
if (this.scheduledHeartBeatRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -141,13 +141,13 @@ export class CheckSessionService implements OnDestroy {
|
||||
return of();
|
||||
}
|
||||
|
||||
if (!contentWindow) {
|
||||
if (contentWindow) {
|
||||
contentWindow.location.replace(checkSessionIframe);
|
||||
} else {
|
||||
this.loggerService.logWarning(
|
||||
configuration,
|
||||
'CheckSession - init check session: IFrame contentWindow does not exist'
|
||||
);
|
||||
} else {
|
||||
contentWindow.location.replace(checkSessionIframe);
|
||||
}
|
||||
|
||||
return new Observable((observer) => {
|
||||
@ -197,7 +197,7 @@ export class CheckSessionService implements OnDestroy {
|
||||
|
||||
this.outstandingMessages++;
|
||||
contentWindow.postMessage(
|
||||
clientId + ' ' + sessionState,
|
||||
`${clientId} ${sessionState}`,
|
||||
iframeOrigin
|
||||
);
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TestBed } from '@/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { lastValueFrom, of } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import { LoggerService } from '../logging/logger.service';
|
||||
import { mockProvider } from '../testing/mock';
|
||||
@ -20,9 +20,6 @@ describe('RefreshSessionIframeService ', () => {
|
||||
mockProvider(UrlService),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
refreshSessionIframeService = TestBed.inject(RefreshSessionIframeService);
|
||||
urlService = TestBed.inject(UrlService);
|
||||
});
|
||||
@ -62,7 +59,9 @@ describe('RefreshSessionIframeService ', () => {
|
||||
it('dispatches customevent to window object', async () => {
|
||||
const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent');
|
||||
|
||||
(refreshSessionIframeService as any).initSilentRenewRequest();
|
||||
await lastValueFrom(
|
||||
(refreshSessionIframeService as any).initSilentRenewRequest()
|
||||
);
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledExactlyOnceWith(
|
||||
new CustomEvent('oidc-silent-renew-init', {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TestBed } from '@/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
|
||||
import { lastValueFrom, of } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import type { OpenIdConfiguration } from '../../config/openid-configuration';
|
||||
import { FlowsDataService } from '../../flows/flows-data.service';
|
||||
@ -1044,9 +1044,8 @@ describe('UrlService Tests', () => {
|
||||
|
||||
describe('getAuthorizeUrl', () => {
|
||||
it('returns null if no config is given', async () => {
|
||||
service.getAuthorizeUrl(null).subscribe((url) => {
|
||||
expect(url).toBeNull();
|
||||
});
|
||||
const url = await lastValueFrom(service.getAuthorizeUrl(null));
|
||||
expect(url).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if current flow is code flow and no redirect url is defined', async () => {
|
||||
@ -1383,7 +1382,7 @@ describe('UrlService Tests', () => {
|
||||
|
||||
resultObs$.subscribe((result) => {
|
||||
expect(result).toBe(
|
||||
`client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256`
|
||||
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1414,7 +1413,7 @@ describe('UrlService Tests', () => {
|
||||
|
||||
resultObs$.subscribe((result) => {
|
||||
expect(result).toBe(
|
||||
`client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam`
|
||||
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1445,7 +1444,7 @@ describe('UrlService Tests', () => {
|
||||
|
||||
resultObs$.subscribe((result) => {
|
||||
expect(result).toBe(
|
||||
`client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing`
|
||||
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1480,7 +1479,7 @@ describe('UrlService Tests', () => {
|
||||
|
||||
resultObs$.subscribe((result) => {
|
||||
expect(result).toBe(
|
||||
`client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing&any=otherThing`
|
||||
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing&any=otherThing'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1904,8 +1903,7 @@ describe('UrlService Tests', () => {
|
||||
|
||||
resultObs$.subscribe((result: any) => {
|
||||
expect(result).toBe(
|
||||
`authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com` +
|
||||
`&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&to=add&as=well`
|
||||
`authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&to=add&as=well`
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -2121,7 +2119,8 @@ describe('UrlService Tests', () => {
|
||||
const value = service.getEndSessionUrl(config);
|
||||
|
||||
// Assert
|
||||
const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`;
|
||||
const expectValue =
|
||||
'something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized';
|
||||
|
||||
expect(value).toEqual(expectValue);
|
||||
});
|
||||
|
@ -25,6 +25,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.scripts.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
12
tsconfig.scripts.json
Normal file
12
tsconfig.scripts.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"composite": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [],
|
||||
"include": ["scripts/"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user