cli-forge 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/composable-builder.d.ts +5 -1
- package/dist/lib/composable-builder.js +24 -3
- package/dist/lib/composable-builder.js.map +1 -1
- package/dist/lib/interactive-shell.js +2 -0
- package/dist/lib/interactive-shell.js.map +1 -1
- package/dist/lib/internal-cli.d.ts +18 -5
- package/dist/lib/internal-cli.js +161 -36
- package/dist/lib/internal-cli.js.map +1 -1
- package/dist/lib/public-api.d.ts +11 -2
- package/dist/lib/public-api.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/cli-localization.spec.ts +7 -7
- package/src/lib/composable-builder.spec.ts +73 -0
- package/src/lib/composable-builder.ts +26 -5
- package/src/lib/interactive-shell.ts +2 -0
- package/src/lib/internal-cli.spec.ts +822 -3
- package/src/lib/internal-cli.ts +201 -54
- package/src/lib/public-api.ts +25 -10
- package/tsconfig.lib.json.tsbuildinfo +1 -1
|
@@ -48,7 +48,7 @@ describe('CLI localization', () => {
|
|
|
48
48
|
.option('name', { type: 'string' })
|
|
49
49
|
.option('port', { type: 'number' })
|
|
50
50
|
.command('$0', {
|
|
51
|
-
handler: () => {},
|
|
51
|
+
handler: () => { /* noop */ },
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
const harness = new TestHarness(testCli);
|
|
@@ -64,7 +64,7 @@ describe('CLI localization', () => {
|
|
|
64
64
|
.option('name', { type: 'string' })
|
|
65
65
|
.option('port', { type: 'number' })
|
|
66
66
|
.command('$0', {
|
|
67
|
-
handler: () => {},
|
|
67
|
+
handler: () => { /* noop */ },
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
const harness = new TestHarness(testCli);
|
|
@@ -100,7 +100,7 @@ describe('CLI localization', () => {
|
|
|
100
100
|
.localize(dictionary, 'es-ES')
|
|
101
101
|
.command('serve', {
|
|
102
102
|
builder: (cmd) => cmd,
|
|
103
|
-
handler: () => {},
|
|
103
|
+
handler: () => { /* noop */ },
|
|
104
104
|
description: 'Start the server',
|
|
105
105
|
})
|
|
106
106
|
.forge(['--help']);
|
|
@@ -121,7 +121,7 @@ describe('CLI localization', () => {
|
|
|
121
121
|
cmd
|
|
122
122
|
.option('port', { type: 'number' })
|
|
123
123
|
.option('name', { type: 'string' }),
|
|
124
|
-
handler: () => {},
|
|
124
|
+
handler: () => { /* noop */ },
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
const harness = new TestHarness(testCli);
|
|
@@ -137,7 +137,7 @@ describe('CLI localization', () => {
|
|
|
137
137
|
.option('name', { type: 'string' })
|
|
138
138
|
.option('port', { type: 'number' })
|
|
139
139
|
.command('$0', {
|
|
140
|
-
handler: () => {},
|
|
140
|
+
handler: () => { /* noop */ },
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
const harness = new TestHarness(testCli);
|
|
@@ -154,7 +154,7 @@ describe('CLI localization', () => {
|
|
|
154
154
|
.option('port', { type: 'number' })
|
|
155
155
|
.env('TEST')
|
|
156
156
|
.command('$0', {
|
|
157
|
-
handler: () => {},
|
|
157
|
+
handler: () => { /* noop */ },
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
const harness = new TestHarness(testCli);
|
|
@@ -180,7 +180,7 @@ describe('CLI localization', () => {
|
|
|
180
180
|
.option('port', { type: 'number' })
|
|
181
181
|
.command('serve', {
|
|
182
182
|
builder: (cmd) => cmd,
|
|
183
|
-
handler: () => {},
|
|
183
|
+
handler: () => { /* noop */ },
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
const harness = new TestHarness(testCli);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { makeComposableBuilder } from './composable-builder';
|
|
3
|
+
import cli from './public-api';
|
|
4
|
+
import { chain } from '@cli-forge/parser';
|
|
5
|
+
|
|
6
|
+
describe('makeComposableBuilder', () => {
|
|
7
|
+
describe('capture-and-replay', () => {
|
|
8
|
+
it('should produce stable middleware references across applications', () => {
|
|
9
|
+
const builder = makeComposableBuilder((cmd) =>
|
|
10
|
+
cmd
|
|
11
|
+
.option('verbose', { type: 'boolean' })
|
|
12
|
+
.middleware((args: any) => args)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const cli1 = builder(cli('test1'));
|
|
16
|
+
const cli2 = builder(cli('test2'));
|
|
17
|
+
|
|
18
|
+
const mw1 = [...(cli1 as any).registeredMiddleware];
|
|
19
|
+
const mw2 = [...(cli2 as any).registeredMiddleware];
|
|
20
|
+
expect(mw1.length).toBe(1);
|
|
21
|
+
expect(mw2.length).toBe(1);
|
|
22
|
+
expect(mw1[0]).toBe(mw2[0]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should correctly replay option registrations', async () => {
|
|
26
|
+
const builder = makeComposableBuilder((cmd) =>
|
|
27
|
+
cmd.option('verbose', { type: 'boolean' })
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
let handlerArgs: any;
|
|
31
|
+
await chain(cli('test'), builder)
|
|
32
|
+
.command('$0', {
|
|
33
|
+
handler: (args) => {
|
|
34
|
+
handlerArgs = args;
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
.forge(['--verbose']);
|
|
38
|
+
expect(handlerArgs.verbose).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should deduplicate middleware when builder applied to parent and child', async () => {
|
|
42
|
+
let mwCallCount = 0;
|
|
43
|
+
const builder = makeComposableBuilder((cmd) =>
|
|
44
|
+
cmd.option('verbose', { type: 'boolean' }).middleware((args: any) => {
|
|
45
|
+
mwCallCount++;
|
|
46
|
+
return args;
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await chain(cli('parent'), builder)
|
|
51
|
+
.command('child', {
|
|
52
|
+
builder: (cmd) =>
|
|
53
|
+
chain(cmd, builder).option('format', { type: 'string' }),
|
|
54
|
+
handler: () => { /* noop */ },
|
|
55
|
+
})
|
|
56
|
+
.forge(['child']);
|
|
57
|
+
expect(mwCallCount).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should replay commands registered by the builder', () => {
|
|
61
|
+
const builder = makeComposableBuilder((cmd) =>
|
|
62
|
+
cmd.command('sub', {
|
|
63
|
+
builder: (c) => c.option('flag', { type: 'boolean' }),
|
|
64
|
+
handler: () => { /* noop */ },
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const myCli = builder(cli('test'));
|
|
69
|
+
const children = myCli.getChildren();
|
|
70
|
+
expect(children).toHaveProperty('sub');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParsedArgs } from '@cli-forge/parser';
|
|
1
|
+
import type { ParsedArgs } from '@cli-forge/parser';
|
|
2
2
|
import { CLI } from './public-api';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -30,6 +30,10 @@ export type ComposableBuilder<
|
|
|
30
30
|
* Can be used to add options, commands, or any other CLI modifications.
|
|
31
31
|
* Children added by the builder function are properly tracked in the type.
|
|
32
32
|
*
|
|
33
|
+
* The builder function runs once at creation time against a recording Proxy.
|
|
34
|
+
* Subsequent applications replay the captured operations, ensuring inline
|
|
35
|
+
* middleware closures have stable references for Set-based deduplication.
|
|
36
|
+
*
|
|
33
37
|
* @typeParam TArgs2 - The args type after the builder runs
|
|
34
38
|
* @typeParam TChildren2 - The children type added by the builder
|
|
35
39
|
*/
|
|
@@ -43,15 +47,32 @@ export function makeComposableBuilder<
|
|
|
43
47
|
init: CLI<ParsedArgs, any, {}, any>
|
|
44
48
|
) => CLI<TArgs2, any, TChildren2, any>
|
|
45
49
|
) {
|
|
50
|
+
// Run builder once against a recording proxy to capture operations.
|
|
51
|
+
// Replaying these ensures inline closures (e.g. middleware) keep stable
|
|
52
|
+
// references across applications, enabling Set-based deduplication.
|
|
53
|
+
const operations: { method: string; args: any[] }[] = [];
|
|
54
|
+
const proxy = new Proxy({} as CLI, {
|
|
55
|
+
get(_target, prop) {
|
|
56
|
+
return (...args: any[]) => {
|
|
57
|
+
operations.push({ method: prop as string, args });
|
|
58
|
+
return proxy;
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
fn(proxy);
|
|
63
|
+
|
|
46
64
|
return <TInit extends ParsedArgs, THandlerReturn, TChildren, TParent>(
|
|
47
65
|
init: CLI<TInit, THandlerReturn, TChildren, TParent>
|
|
48
|
-
) =>
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
) => {
|
|
67
|
+
let current: any = init;
|
|
68
|
+
for (const op of operations) {
|
|
69
|
+
current = current[op.method](...op.args);
|
|
70
|
+
}
|
|
71
|
+
return current as unknown as CLI<
|
|
51
72
|
TInit & TArgs2,
|
|
52
73
|
THandlerReturn,
|
|
53
74
|
TChildren & TChildren2,
|
|
54
75
|
TParent
|
|
55
76
|
>;
|
|
77
|
+
};
|
|
56
78
|
}
|
|
57
|
-
|