@crouton-kit/crouter 0.2.6 → 0.3.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/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +9 -9
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +19 -19
- package/dist/cli.js +42 -37
- package/dist/commands/__tests__/human.test.d.ts +1 -0
- package/dist/commands/__tests__/human.test.js +214 -0
- package/dist/commands/__tests__/skill.test.d.ts +1 -0
- package/dist/commands/__tests__/skill.test.js +287 -0
- package/dist/commands/debug.d.ts +3 -0
- package/dist/commands/debug.js +179 -0
- package/dist/commands/flow.d.ts +2 -0
- package/dist/commands/flow.js +24 -0
- package/dist/commands/human.d.ts +2 -0
- package/dist/commands/human.js +480 -0
- package/dist/commands/job.d.ts +2 -0
- package/dist/commands/job.js +669 -0
- package/dist/commands/pkg.d.ts +2 -0
- package/dist/commands/pkg.js +1021 -0
- package/dist/commands/plan.d.ts +4 -2
- package/dist/commands/plan.js +306 -22
- package/dist/commands/skill.d.ts +2 -2
- package/dist/commands/skill.js +607 -456
- package/dist/commands/spec.d.ts +3 -2
- package/dist/commands/spec.js +283 -10
- package/dist/commands/sys.d.ts +2 -0
- package/dist/commands/sys.js +712 -0
- package/dist/core/__tests__/argv-parser.test.d.ts +1 -0
- package/dist/core/__tests__/argv-parser.test.js +199 -0
- package/dist/core/__tests__/flow-leaves.test.d.ts +1 -0
- package/dist/core/__tests__/flow-leaves.test.js +248 -0
- package/dist/core/__tests__/job.test.d.ts +1 -0
- package/dist/core/__tests__/job.test.js +346 -0
- package/dist/core/__tests__/pkg.test.d.ts +1 -0
- package/dist/core/__tests__/pkg.test.js +218 -0
- package/dist/core/__tests__/sys.test.d.ts +1 -0
- package/dist/core/__tests__/sys.test.js +208 -0
- package/dist/core/artifact.d.ts +29 -18
- package/dist/core/artifact.js +78 -221
- package/dist/core/auto-update.js +11 -3
- package/dist/core/command.d.ts +36 -0
- package/dist/core/command.js +287 -0
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.js +5 -0
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +4 -0
- package/dist/core/help.d.ts +98 -0
- package/dist/core/help.js +163 -0
- package/dist/core/io.d.ts +29 -0
- package/dist/core/io.js +83 -0
- package/dist/core/jobs.d.ts +87 -0
- package/dist/core/jobs.js +353 -0
- package/dist/core/pagination.d.ts +33 -0
- package/dist/core/pagination.js +89 -0
- package/dist/core/self-update.d.ts +21 -0
- package/dist/{commands/update.js → core/self-update.js} +28 -63
- package/dist/core/spawn.d.ts +47 -65
- package/dist/core/spawn.js +78 -228
- package/dist/prompts/agent.d.ts +10 -5
- package/dist/prompts/agent.js +51 -74
- package/dist/prompts/debug.d.ts +8 -0
- package/dist/prompts/debug.js +37 -0
- package/dist/prompts/review.js +4 -11
- package/dist/prompts/skill.d.ts +0 -1
- package/dist/prompts/skill.js +95 -149
- package/package.json +4 -2
- package/dist/commands/agent.d.ts +0 -2
- package/dist/commands/agent.js +0 -265
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -146
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -268
- package/dist/commands/marketplace.d.ts +0 -2
- package/dist/commands/marketplace.js +0 -365
- package/dist/commands/plugin.d.ts +0 -2
- package/dist/commands/plugin.js +0 -367
- package/dist/commands/update.d.ts +0 -4
- package/dist/prompts/plan.d.ts +0 -1
- package/dist/prompts/plan.js +0 -175
- package/dist/prompts/spec.d.ts +0 -1
- package/dist/prompts/spec.js +0 -153
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// Tests for the job subtree argv migration.
|
|
2
|
+
// Exercises parseArgv against each leaf's param schema directly — no subprocess,
|
|
3
|
+
// no tmux, no filesystem side-effects from the handler.
|
|
4
|
+
//
|
|
5
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/job.test.ts
|
|
6
|
+
import { test, describe } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { parseArgv } from '../command.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Param schemas extracted from each leaf (mirrors job.ts exactly)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const startPromptParams = [
|
|
16
|
+
{ kind: 'stdin', name: 'prompt', required: true, constraint: '' },
|
|
17
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
18
|
+
];
|
|
19
|
+
const startForkParams = [
|
|
20
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
21
|
+
];
|
|
22
|
+
const startPlannerParams = [
|
|
23
|
+
{ kind: 'positional', name: 'spec_path', type: 'path', required: true, constraint: '' },
|
|
24
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
25
|
+
];
|
|
26
|
+
const startImplementerParams = [
|
|
27
|
+
{ kind: 'positional', name: 'plan_path', type: 'path', required: true, constraint: '' },
|
|
28
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
29
|
+
];
|
|
30
|
+
const startReviewerParams = [
|
|
31
|
+
{ kind: 'positional', name: 'artifact_path', type: 'path', required: true, constraint: '' },
|
|
32
|
+
{ kind: 'flag', name: 'kind', type: 'enum', choices: ['plan', 'spec'], required: true, constraint: '' },
|
|
33
|
+
{ kind: 'flag', name: 'spec-path', type: 'path', required: false, constraint: '' },
|
|
34
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
35
|
+
];
|
|
36
|
+
const readListParams = [
|
|
37
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 20, constraint: '' },
|
|
38
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
|
|
39
|
+
];
|
|
40
|
+
const readStatusParams = [
|
|
41
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
42
|
+
];
|
|
43
|
+
const readResultParams = [
|
|
44
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
45
|
+
{ kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: '' },
|
|
46
|
+
];
|
|
47
|
+
const readLogsParams = [
|
|
48
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
49
|
+
{ kind: 'flag', name: 'since', type: 'string', required: false, constraint: '' },
|
|
50
|
+
{ kind: 'flag', name: 'until', type: 'string', required: false, constraint: '' },
|
|
51
|
+
{ kind: 'flag', name: 'level', type: 'enum', choices: ['debug', 'info', 'warn', 'error'], required: false, default: 'info', constraint: '' },
|
|
52
|
+
{ kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: '' },
|
|
53
|
+
];
|
|
54
|
+
const submitParams = [
|
|
55
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
56
|
+
{ kind: 'context-file', name: 'result', required: true, constraint: '' },
|
|
57
|
+
{ kind: 'flag', name: 'kill-pane', type: 'bool', required: false, constraint: '' },
|
|
58
|
+
];
|
|
59
|
+
const cancelParams = [
|
|
60
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
61
|
+
];
|
|
62
|
+
const failParams = [
|
|
63
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
64
|
+
];
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
let tmpDir;
|
|
69
|
+
function setup() {
|
|
70
|
+
tmpDir = join(tmpdir(), `crtr-job-test-${Date.now()}`);
|
|
71
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
function teardown() {
|
|
74
|
+
if (tmpDir !== undefined) {
|
|
75
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function writeTmpJson(name, obj) {
|
|
79
|
+
const p = join(tmpDir, name);
|
|
80
|
+
writeFileSync(p, JSON.stringify(obj), 'utf8');
|
|
81
|
+
return p;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// job start prompt
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
describe('job start prompt', () => {
|
|
87
|
+
// stdin is handled by readStdinRaw() which requires actual stdin — we only
|
|
88
|
+
// test the non-stdin flag parsing here.
|
|
89
|
+
test('--cwd flag parsed as string', async () => {
|
|
90
|
+
// stdin param will be read but we can't pipe here — skip stdin assertion,
|
|
91
|
+
// test that cwd parses correctly alongside other tokens.
|
|
92
|
+
// We test the flag-only shape: no stdin params in isolation.
|
|
93
|
+
const flagOnlyParams = [
|
|
94
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
95
|
+
];
|
|
96
|
+
const result = await parseArgv(flagOnlyParams, ['--cwd', '/tmp/mydir']);
|
|
97
|
+
assert.equal(result['cwd'], '/tmp/mydir');
|
|
98
|
+
});
|
|
99
|
+
test('cwd absent → undefined', async () => {
|
|
100
|
+
const flagOnlyParams = [
|
|
101
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
102
|
+
];
|
|
103
|
+
const result = await parseArgv(flagOnlyParams, []);
|
|
104
|
+
assert.equal(result['cwd'], undefined);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// job start fork
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
describe('job start fork', () => {
|
|
111
|
+
test('no args parses cleanly', async () => {
|
|
112
|
+
const result = await parseArgv(startForkParams, []);
|
|
113
|
+
assert.equal(result['cwd'], undefined);
|
|
114
|
+
});
|
|
115
|
+
test('--cwd parsed', async () => {
|
|
116
|
+
const result = await parseArgv(startForkParams, ['--cwd', '/workspace']);
|
|
117
|
+
assert.equal(result['cwd'], '/workspace');
|
|
118
|
+
});
|
|
119
|
+
test('unknown positional rejected', async () => {
|
|
120
|
+
await assert.rejects(() => parseArgv(startForkParams, ['extra-pos']), (err) => { assert.match(err.message, /takes no positional/); return true; });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// job start planner
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
describe('job start planner', () => {
|
|
127
|
+
test('positional spec_path required', async () => {
|
|
128
|
+
await assert.rejects(() => parseArgv(startPlannerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
129
|
+
});
|
|
130
|
+
test('positional parsed as spec_path (camelCase: specPath? no — underscore stays as-is)', async () => {
|
|
131
|
+
const result = await parseArgv(startPlannerParams, ['/tmp/spec.md']);
|
|
132
|
+
// flagNameToKey converts kebab to camel; underscores are unaffected
|
|
133
|
+
assert.equal(result['spec_path'], '/tmp/spec.md');
|
|
134
|
+
});
|
|
135
|
+
test('--cwd optional', async () => {
|
|
136
|
+
const result = await parseArgv(startPlannerParams, ['/tmp/spec.md', '--cwd', '/src']);
|
|
137
|
+
assert.equal(result['cwd'], '/src');
|
|
138
|
+
assert.equal(result['spec_path'], '/tmp/spec.md');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// job start implementer
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
describe('job start implementer', () => {
|
|
145
|
+
test('positional plan_path required', async () => {
|
|
146
|
+
await assert.rejects(() => parseArgv(startImplementerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
147
|
+
});
|
|
148
|
+
test('positional parsed', async () => {
|
|
149
|
+
const result = await parseArgv(startImplementerParams, ['/tmp/plan.md']);
|
|
150
|
+
assert.equal(result['plan_path'], '/tmp/plan.md');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// job start reviewer
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
describe('job start reviewer', () => {
|
|
157
|
+
test('positional + --kind required', async () => {
|
|
158
|
+
await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
159
|
+
});
|
|
160
|
+
test('valid kind: plan', async () => {
|
|
161
|
+
const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'plan']);
|
|
162
|
+
assert.equal(result['artifact_path'], '/tmp/artifact.md');
|
|
163
|
+
assert.equal(result['kind'], 'plan');
|
|
164
|
+
});
|
|
165
|
+
test('valid kind: spec', async () => {
|
|
166
|
+
const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'spec']);
|
|
167
|
+
assert.equal(result['kind'], 'spec');
|
|
168
|
+
});
|
|
169
|
+
test('invalid kind throws invalid_type', async () => {
|
|
170
|
+
await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'bad']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
171
|
+
});
|
|
172
|
+
test('--spec-path optional, becomes specPath', async () => {
|
|
173
|
+
const result = await parseArgv(startReviewerParams, [
|
|
174
|
+
'/tmp/artifact.md', '--kind', 'plan', '--spec-path', '/tmp/spec.md',
|
|
175
|
+
]);
|
|
176
|
+
assert.equal(result['specPath'], '/tmp/spec.md');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// job read list
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
describe('job read list', () => {
|
|
183
|
+
test('defaults: limit=20, cursor=undefined', async () => {
|
|
184
|
+
const result = await parseArgv(readListParams, []);
|
|
185
|
+
assert.equal(result['limit'], 20);
|
|
186
|
+
assert.equal(result['cursor'], undefined);
|
|
187
|
+
});
|
|
188
|
+
test('--limit N parsed as int', async () => {
|
|
189
|
+
const result = await parseArgv(readListParams, ['--limit', '50']);
|
|
190
|
+
assert.equal(result['limit'], 50);
|
|
191
|
+
});
|
|
192
|
+
test('--limit with non-integer throws', async () => {
|
|
193
|
+
await assert.rejects(() => parseArgv(readListParams, ['--limit', 'abc']), (err) => { assert.match(err.message, /must be an integer/); return true; });
|
|
194
|
+
});
|
|
195
|
+
test('--cursor opaque token', async () => {
|
|
196
|
+
const result = await parseArgv(readListParams, ['--cursor', 'tok_abc123']);
|
|
197
|
+
assert.equal(result['cursor'], 'tok_abc123');
|
|
198
|
+
});
|
|
199
|
+
test('--limit and --cursor together', async () => {
|
|
200
|
+
const result = await parseArgv(readListParams, ['--limit', '5', '--cursor', 'next_page']);
|
|
201
|
+
assert.equal(result['limit'], 5);
|
|
202
|
+
assert.equal(result['cursor'], 'next_page');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// job read status
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
describe('job read status', () => {
|
|
209
|
+
test('positional job_id required', async () => {
|
|
210
|
+
await assert.rejects(() => parseArgv(readStatusParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
211
|
+
});
|
|
212
|
+
test('positional job_id parsed', async () => {
|
|
213
|
+
const result = await parseArgv(readStatusParams, ['job-abc-123']);
|
|
214
|
+
assert.equal(result['job_id'], 'job-abc-123');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// job read result
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
describe('job read result', () => {
|
|
221
|
+
test('positional job_id required', async () => {
|
|
222
|
+
await assert.rejects(() => parseArgv(readResultParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
223
|
+
});
|
|
224
|
+
test('positional job_id without --wait', async () => {
|
|
225
|
+
const result = await parseArgv(readResultParams, ['job-xyz']);
|
|
226
|
+
assert.equal(result['job_id'], 'job-xyz');
|
|
227
|
+
assert.equal(result['wait'], false);
|
|
228
|
+
});
|
|
229
|
+
test('--wait presence = true', async () => {
|
|
230
|
+
const result = await parseArgv(readResultParams, ['job-xyz', '--wait']);
|
|
231
|
+
assert.equal(result['wait'], true);
|
|
232
|
+
});
|
|
233
|
+
test('--wait=value rejected (bool takes no value)', async () => {
|
|
234
|
+
await assert.rejects(() => parseArgv(readResultParams, ['job-xyz', '--wait=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// job read logs
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
describe('job read logs', () => {
|
|
241
|
+
test('positional job_id required', async () => {
|
|
242
|
+
await assert.rejects(() => parseArgv(readLogsParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
243
|
+
});
|
|
244
|
+
test('defaults: level=info, follow=false', async () => {
|
|
245
|
+
const result = await parseArgv(readLogsParams, ['job-abc']);
|
|
246
|
+
assert.equal(result['job_id'], 'job-abc');
|
|
247
|
+
assert.equal(result['level'], 'info');
|
|
248
|
+
assert.equal(result['follow'], false);
|
|
249
|
+
});
|
|
250
|
+
test('--follow presence = true', async () => {
|
|
251
|
+
const result = await parseArgv(readLogsParams, ['job-abc', '--follow']);
|
|
252
|
+
assert.equal(result['follow'], true);
|
|
253
|
+
});
|
|
254
|
+
test('--follow=value rejected', async () => {
|
|
255
|
+
await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--follow=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
|
|
256
|
+
});
|
|
257
|
+
test('--level valid enum', async () => {
|
|
258
|
+
const result = await parseArgv(readLogsParams, ['job-abc', '--level', 'debug']);
|
|
259
|
+
assert.equal(result['level'], 'debug');
|
|
260
|
+
});
|
|
261
|
+
test('--level invalid enum throws', async () => {
|
|
262
|
+
await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--level', 'trace']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
263
|
+
});
|
|
264
|
+
test('--since and --until optional strings', async () => {
|
|
265
|
+
const result = await parseArgv(readLogsParams, [
|
|
266
|
+
'job-abc', '--since', '2025-01-01T00:00:00Z', '--until', '2025-01-02T00:00:00Z',
|
|
267
|
+
]);
|
|
268
|
+
assert.equal(result['since'], '2025-01-01T00:00:00Z');
|
|
269
|
+
assert.equal(result['until'], '2025-01-02T00:00:00Z');
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// job submit
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
describe('job submit', () => {
|
|
276
|
+
// setup/teardown wraps tests via explicit calls since node:test lacks
|
|
277
|
+
// per-describe lifecycle hooks in older versions.
|
|
278
|
+
test('--context-file with valid JSON object', async () => {
|
|
279
|
+
setup();
|
|
280
|
+
try {
|
|
281
|
+
const p = writeTmpJson('result.json', { status: 'done', summary: 'all good' });
|
|
282
|
+
const result = await parseArgv(submitParams, ['job-abc', '--context-file', p]);
|
|
283
|
+
assert.equal(result['job_id'], 'job-abc');
|
|
284
|
+
assert.deepEqual(result['result'], { status: 'done', summary: 'all good' });
|
|
285
|
+
assert.equal(result['killPane'], false);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
teardown();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
test('--kill-pane presence = true, killPane key', async () => {
|
|
292
|
+
setup();
|
|
293
|
+
try {
|
|
294
|
+
const p = writeTmpJson('result.json', { status: 'done' });
|
|
295
|
+
const result = await parseArgv(submitParams, ['job-abc', '--context-file', p, '--kill-pane']);
|
|
296
|
+
assert.equal(result['killPane'], true);
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
teardown();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
test('missing --context-file throws missing_parameter', async () => {
|
|
303
|
+
await assert.rejects(() => parseArgv(submitParams, ['job-abc']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
304
|
+
});
|
|
305
|
+
test('missing positional job_id throws missing_parameter', async () => {
|
|
306
|
+
await assert.rejects(() => parseArgv(submitParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
307
|
+
});
|
|
308
|
+
test('--context-file with non-existent file throws invalid_type', async () => {
|
|
309
|
+
await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', '/no/such/file.json']), (err) => { assert.match(err.message, /cannot read file/); return true; });
|
|
310
|
+
});
|
|
311
|
+
test('--context-file with invalid JSON throws invalid_type', async () => {
|
|
312
|
+
setup();
|
|
313
|
+
try {
|
|
314
|
+
const p = join(tmpDir, 'bad.json');
|
|
315
|
+
writeFileSync(p, 'not json', 'utf8');
|
|
316
|
+
await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', p]), (err) => { assert.match(err.message, /not valid JSON/); return true; });
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
teardown();
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// job cancel
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
describe('job cancel', () => {
|
|
327
|
+
test('positional job_id required', async () => {
|
|
328
|
+
await assert.rejects(() => parseArgv(cancelParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
329
|
+
});
|
|
330
|
+
test('positional job_id parsed', async () => {
|
|
331
|
+
const result = await parseArgv(cancelParams, ['job-abc-123']);
|
|
332
|
+
assert.equal(result['job_id'], 'job-abc-123');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// job _fail
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
describe('job _fail', () => {
|
|
339
|
+
test('positional job_id required', async () => {
|
|
340
|
+
await assert.rejects(() => parseArgv(failParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
341
|
+
});
|
|
342
|
+
test('positional job_id parsed', async () => {
|
|
343
|
+
const result = await parseArgv(failParams, ['job-fail-001']);
|
|
344
|
+
assert.equal(result['job_id'], 'job-fail-001');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Tests for the pkg subtree argv migration.
|
|
2
|
+
// Exercises parseArgv against the param schemas declared in pkg.ts leaves.
|
|
3
|
+
// No subprocess spawning; no real FS writes. Tests are schema/parsing level only.
|
|
4
|
+
import { test, describe } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { parseArgv } from '../command.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// plugin manage install — positional source + optional flags
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
describe('pkg plugin manage install', () => {
|
|
11
|
+
const params = [
|
|
12
|
+
{ kind: 'positional', name: 'source', type: 'string', required: true, constraint: 'Git URL.' },
|
|
13
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
|
|
14
|
+
{ kind: 'flag', name: 'ref', type: 'string', required: false, constraint: '' },
|
|
15
|
+
];
|
|
16
|
+
test('positional source is required', async () => {
|
|
17
|
+
await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
18
|
+
});
|
|
19
|
+
test('parses positional source', async () => {
|
|
20
|
+
const result = await parseArgv(params, ['https://github.com/org/my-plugin.git']);
|
|
21
|
+
assert.equal(result['source'], 'https://github.com/org/my-plugin.git');
|
|
22
|
+
});
|
|
23
|
+
test('parses positional source with --scope flag', async () => {
|
|
24
|
+
const result = await parseArgv(params, ['https://github.com/org/repo.git', '--scope', 'user']);
|
|
25
|
+
assert.equal(result['source'], 'https://github.com/org/repo.git');
|
|
26
|
+
assert.equal(result['scope'], 'user');
|
|
27
|
+
});
|
|
28
|
+
test('parses --ref flag', async () => {
|
|
29
|
+
const result = await parseArgv(params, ['https://example.com/repo.git', '--ref', 'v1.2.3']);
|
|
30
|
+
assert.equal(result['ref'], 'v1.2.3');
|
|
31
|
+
});
|
|
32
|
+
test('rejects invalid scope enum', async () => {
|
|
33
|
+
await assert.rejects(() => parseArgv(params, ['https://example.com/repo.git', '--scope', 'all']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// plugin manage remove / enable / disable — positional name + optional scope
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
describe('pkg plugin manage remove/enable/disable', () => {
|
|
40
|
+
const params = [
|
|
41
|
+
{ kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
|
|
42
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
|
|
43
|
+
];
|
|
44
|
+
test('parses positional name', async () => {
|
|
45
|
+
const result = await parseArgv(params, ['my-plugin']);
|
|
46
|
+
assert.equal(result['name'], 'my-plugin');
|
|
47
|
+
});
|
|
48
|
+
test('parses name with scope', async () => {
|
|
49
|
+
const result = await parseArgv(params, ['my-plugin', '--scope', 'project']);
|
|
50
|
+
assert.equal(result['name'], 'my-plugin');
|
|
51
|
+
assert.equal(result['scope'], 'project');
|
|
52
|
+
});
|
|
53
|
+
test('missing name throws missing_parameter', async () => {
|
|
54
|
+
await assert.rejects(() => parseArgv(params, ['--scope', 'user']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// plugin manage update — optional --name flag
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
describe('pkg plugin manage update', () => {
|
|
61
|
+
const params = [
|
|
62
|
+
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: '' },
|
|
63
|
+
];
|
|
64
|
+
test('no args is valid (update-all path)', async () => {
|
|
65
|
+
const result = await parseArgv(params, []);
|
|
66
|
+
assert.equal(result['name'], undefined);
|
|
67
|
+
});
|
|
68
|
+
test('--name sets the name', async () => {
|
|
69
|
+
const result = await parseArgv(params, ['--name', 'my-plugin']);
|
|
70
|
+
assert.equal(result['name'], 'my-plugin');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// plugin inspect list — --include-disabled bool flag + pagination
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
describe('pkg plugin inspect list', () => {
|
|
77
|
+
const params = [
|
|
78
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: '' },
|
|
79
|
+
{ kind: 'flag', name: 'include-disabled', type: 'bool', required: false, constraint: '' },
|
|
80
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
|
|
81
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
|
|
82
|
+
];
|
|
83
|
+
test('no args returns defaults', async () => {
|
|
84
|
+
const result = await parseArgv(params, []);
|
|
85
|
+
assert.equal(result['includeDisabled'], false);
|
|
86
|
+
assert.equal(result['limit'], 50);
|
|
87
|
+
assert.equal(result['scope'], undefined);
|
|
88
|
+
assert.equal(result['cursor'], undefined);
|
|
89
|
+
});
|
|
90
|
+
test('--include-disabled presence sets true', async () => {
|
|
91
|
+
const result = await parseArgv(params, ['--include-disabled']);
|
|
92
|
+
assert.equal(result['includeDisabled'], true);
|
|
93
|
+
});
|
|
94
|
+
test('--include-disabled camelCase key', async () => {
|
|
95
|
+
const result = await parseArgv(params, ['--include-disabled']);
|
|
96
|
+
assert.ok('includeDisabled' in result);
|
|
97
|
+
assert.ok(!('include-disabled' in result));
|
|
98
|
+
});
|
|
99
|
+
test('--limit parsed as int', async () => {
|
|
100
|
+
const result = await parseArgv(params, ['--limit', '25']);
|
|
101
|
+
assert.equal(result['limit'], 25);
|
|
102
|
+
});
|
|
103
|
+
test('--cursor passed through', async () => {
|
|
104
|
+
const result = await parseArgv(params, ['--cursor', 'user:some-plugin']);
|
|
105
|
+
assert.equal(result['cursor'], 'user:some-plugin');
|
|
106
|
+
});
|
|
107
|
+
test('--scope all is valid', async () => {
|
|
108
|
+
const result = await parseArgv(params, ['--scope', 'all']);
|
|
109
|
+
assert.equal(result['scope'], 'all');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// plugin inspect show — positional name
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
describe('pkg plugin inspect show', () => {
|
|
116
|
+
const params = [
|
|
117
|
+
{ kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
|
|
118
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
|
|
119
|
+
];
|
|
120
|
+
test('parses positional name', async () => {
|
|
121
|
+
const result = await parseArgv(params, ['my-plugin']);
|
|
122
|
+
assert.equal(result['name'], 'my-plugin');
|
|
123
|
+
});
|
|
124
|
+
test('missing name throws', async () => {
|
|
125
|
+
await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// market manage install — --marketplace + --plugin required flags
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
describe('pkg market manage install', () => {
|
|
132
|
+
const params = [
|
|
133
|
+
{ kind: 'flag', name: 'marketplace', type: 'string', required: true, constraint: '' },
|
|
134
|
+
{ kind: 'flag', name: 'plugin', type: 'string', required: true, constraint: '' },
|
|
135
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
|
|
136
|
+
];
|
|
137
|
+
test('parses --marketplace and --plugin together', async () => {
|
|
138
|
+
const result = await parseArgv(params, ['--marketplace', 'official', '--plugin', 'my-plugin']);
|
|
139
|
+
assert.equal(result['marketplace'], 'official');
|
|
140
|
+
assert.equal(result['plugin'], 'my-plugin');
|
|
141
|
+
});
|
|
142
|
+
test('missing --marketplace throws', async () => {
|
|
143
|
+
await assert.rejects(() => parseArgv(params, ['--plugin', 'my-plugin']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
144
|
+
});
|
|
145
|
+
test('missing --plugin throws', async () => {
|
|
146
|
+
await assert.rejects(() => parseArgv(params, ['--marketplace', 'official']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
147
|
+
});
|
|
148
|
+
test('parses all three flags', async () => {
|
|
149
|
+
const result = await parseArgv(params, ['--marketplace', 'official', '--plugin', 'my-plugin', '--scope', 'user']);
|
|
150
|
+
assert.equal(result['marketplace'], 'official');
|
|
151
|
+
assert.equal(result['plugin'], 'my-plugin');
|
|
152
|
+
assert.equal(result['scope'], 'user');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// market manage update — optional --marketplace flag
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
describe('pkg market manage update', () => {
|
|
159
|
+
const params = [
|
|
160
|
+
{ kind: 'flag', name: 'marketplace', type: 'string', required: false, constraint: '' },
|
|
161
|
+
];
|
|
162
|
+
test('no args is valid (update-all path)', async () => {
|
|
163
|
+
const result = await parseArgv(params, []);
|
|
164
|
+
assert.equal(result['marketplace'], undefined);
|
|
165
|
+
});
|
|
166
|
+
test('--marketplace sets name', async () => {
|
|
167
|
+
const result = await parseArgv(params, ['--marketplace', 'official']);
|
|
168
|
+
assert.equal(result['marketplace'], 'official');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// market manage remove — positional name
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
describe('pkg market manage remove', () => {
|
|
175
|
+
const params = [
|
|
176
|
+
{ kind: 'positional', name: 'name', type: 'string', required: true, constraint: '' },
|
|
177
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
|
|
178
|
+
];
|
|
179
|
+
test('parses positional name', async () => {
|
|
180
|
+
const result = await parseArgv(params, ['official']);
|
|
181
|
+
assert.equal(result['name'], 'official');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// market inspect list / browse — pagination flags
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
describe('pkg market inspect list pagination', () => {
|
|
188
|
+
const params = [
|
|
189
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: '' },
|
|
190
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
|
|
191
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
|
|
192
|
+
];
|
|
193
|
+
test('default limit is 50', async () => {
|
|
194
|
+
const result = await parseArgv(params, []);
|
|
195
|
+
assert.equal(result['limit'], 50);
|
|
196
|
+
});
|
|
197
|
+
test('--limit and --cursor parsed together', async () => {
|
|
198
|
+
const result = await parseArgv(params, ['--limit', '10', '--cursor', 'tok123']);
|
|
199
|
+
assert.equal(result['limit'], 10);
|
|
200
|
+
assert.equal(result['cursor'], 'tok123');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('pkg market inspect browse', () => {
|
|
204
|
+
const params = [
|
|
205
|
+
{ kind: 'flag', name: 'marketplace', type: 'string', required: false, constraint: '' },
|
|
206
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
|
|
207
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
|
|
208
|
+
];
|
|
209
|
+
test('--marketplace optional', async () => {
|
|
210
|
+
const result = await parseArgv(params, []);
|
|
211
|
+
assert.equal(result['marketplace'], undefined);
|
|
212
|
+
});
|
|
213
|
+
test('--marketplace with pagination', async () => {
|
|
214
|
+
const result = await parseArgv(params, ['--marketplace', 'official', '--limit', '20']);
|
|
215
|
+
assert.equal(result['marketplace'], 'official');
|
|
216
|
+
assert.equal(result['limit'], 20);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|