@chalksurf/cli 0.1.0 → 0.2.0
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/README.md +19 -85
- package/dist/bin/chalksurf.js +77 -10
- package/dist/commands/auth.js +31 -19
- package/dist/commands/exercise.js +428 -0
- package/dist/commands/job.js +18 -8
- package/dist/commands/org.js +13 -8
- package/dist/commands/sheet.js +286 -129
- package/dist/lib/api-client.js +9 -3
- package/dist/lib/cli-error.js +37 -1
- package/dist/lib/import-files.js +120 -0
- package/dist/lib/import-output.js +67 -0
- package/dist/lib/manifest.js +183 -19
- package/dist/lib/output.js +35 -2
- package/dist/lib/prompt-secret.js +32 -0
- package/dist/lib/session.js +1 -1
- package/dist/lib/source-resolver.js +72 -10
- package/dist/lib/translation-languages.js +1 -0
- package/dist/lib/user-jobs.js +16 -1
- package/docs/agents.md +195 -0
- package/docs/examples/exercise-import-manifest.json +13 -0
- package/docs/examples/exercise-solution-import-manifest.json +13 -0
- package/docs/examples/sheet-import-manifest.json +33 -0
- package/docs/exit-codes.md +57 -0
- package/docs/manifest.md +129 -0
- package/docs/manual.md +152 -0
- package/package.json +8 -4
- package/schemas/exercise-import-manifest.schema.json +104 -0
- package/schemas/exercise-solution-import-manifest.schema.json +104 -0
- package/schemas/sheet-import-manifest.schema.json +142 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createApiClient } from '../lib/api-client.js';
|
|
3
|
+
import { CliCommandError } from '../lib/cli-error.js';
|
|
4
|
+
import { buildFileImportCommandSources, buildFileImportFormData, normalizeWaitTimeoutMs } from '../lib/import-files.js';
|
|
5
|
+
import { buildCliImportSources, summarizeCliImportJobs, } from '../lib/import-output.js';
|
|
6
|
+
import { loadExerciseImportManifest, loadExerciseSolutionImportManifest } from '../lib/manifest.js';
|
|
7
|
+
import { mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken, resolveRequestedOrganizationId, } from '../lib/session.js';
|
|
8
|
+
import { cleanupResolvedSources, resolveSources } from '../lib/source-resolver.js';
|
|
9
|
+
import { getWaitError, getWaitExitCode, throwSilentExitCode, waitForCliJobs } from '../lib/user-jobs.js';
|
|
10
|
+
const resolveRequestedUuid = ({ flagValue, fallbackValue, label, required = false, }) => {
|
|
11
|
+
const resolvedValue = flagValue?.trim() || fallbackValue?.trim();
|
|
12
|
+
if (!resolvedValue) {
|
|
13
|
+
if (required) {
|
|
14
|
+
throw new CliCommandError(`${label} is required.`, 2);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const parsedValue = z.uuid().safeParse(resolvedValue);
|
|
19
|
+
if (!parsedValue.success) {
|
|
20
|
+
throw new CliCommandError(`${label} must be a valid UUID.`, 2);
|
|
21
|
+
}
|
|
22
|
+
return parsedValue.data;
|
|
23
|
+
};
|
|
24
|
+
const buildExerciseImportRequest = ({ exerciseId, exerciseSheetId, inputMode, organizationId, timeoutMs, wait, }) => {
|
|
25
|
+
return {
|
|
26
|
+
organizationId,
|
|
27
|
+
inputMode,
|
|
28
|
+
wait,
|
|
29
|
+
timeoutMs,
|
|
30
|
+
target: {
|
|
31
|
+
...(exerciseId === undefined ? {} : { exerciseId }),
|
|
32
|
+
...(exerciseSheetId === undefined ? {} : { exerciseSheetId }),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const buildQueuedExerciseImportJob = ({ jobId, sources, }) => {
|
|
37
|
+
return {
|
|
38
|
+
jobId,
|
|
39
|
+
sourceIndexes: sources.map((source) => source.sourceIndex),
|
|
40
|
+
sourceIds: sources.map((source) => source.sourceId).filter((sourceId) => sourceId !== null),
|
|
41
|
+
status: 'queued',
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const buildWaitedExerciseImportJob = ({ jobId, sources, waitedJob, }) => {
|
|
45
|
+
return {
|
|
46
|
+
jobId,
|
|
47
|
+
sourceIndexes: sources.map((source) => source.sourceIndex),
|
|
48
|
+
sourceIds: sources.map((source) => source.sourceId).filter((sourceId) => sourceId !== null),
|
|
49
|
+
status: waitedJob?.status ?? 'pending',
|
|
50
|
+
error: waitedJob?.error,
|
|
51
|
+
exerciseId: waitedJob?.exerciseId,
|
|
52
|
+
exerciseIds: waitedJob?.exerciseIds,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const formatQueuedImportOutput = (job) => {
|
|
56
|
+
return [
|
|
57
|
+
'Queued exercise import.',
|
|
58
|
+
`${job.jobId} ${job.sourceIndexes.length} source${job.sourceIndexes.length === 1 ? '' : 's'}`,
|
|
59
|
+
].join('\n');
|
|
60
|
+
};
|
|
61
|
+
const formatQueuedSolutionImportOutput = (job) => {
|
|
62
|
+
return [
|
|
63
|
+
'Queued exercise solution import.',
|
|
64
|
+
`${job.jobId} ${job.sourceIndexes.length} source${job.sourceIndexes.length === 1 ? '' : 's'}`,
|
|
65
|
+
].join('\n');
|
|
66
|
+
};
|
|
67
|
+
const formatWaitedImportOutput = (job) => {
|
|
68
|
+
if (job.status === 'completed') {
|
|
69
|
+
if (job.exerciseIds && job.exerciseIds.length > 0) {
|
|
70
|
+
return `${job.jobId} completed -> ${job.exerciseIds.length} exercise${job.exerciseIds.length === 1 ? '' : 's'}`;
|
|
71
|
+
}
|
|
72
|
+
if (job.exerciseId) {
|
|
73
|
+
return `${job.jobId} completed -> ${job.exerciseId}`;
|
|
74
|
+
}
|
|
75
|
+
return `${job.jobId} completed`;
|
|
76
|
+
}
|
|
77
|
+
if (job.status === 'failed') {
|
|
78
|
+
return `${job.jobId} failed -> ${job.error ?? 'Unknown error'}`;
|
|
79
|
+
}
|
|
80
|
+
return `${job.jobId} ${job.status ?? 'pending'}`;
|
|
81
|
+
};
|
|
82
|
+
export const registerExerciseCommands = (exerciseYargs, context) => {
|
|
83
|
+
return exerciseYargs
|
|
84
|
+
.command('import [sources..]', 'Import one or more exercises', (importYargs) => importYargs
|
|
85
|
+
.positional('sources', {
|
|
86
|
+
array: true,
|
|
87
|
+
type: 'string',
|
|
88
|
+
describe: 'Local file paths, directories, or HTTP(S) URLs',
|
|
89
|
+
})
|
|
90
|
+
.option('manifest', {
|
|
91
|
+
type: 'string',
|
|
92
|
+
describe: 'Load sources from a manifest file, or "-" for stdin',
|
|
93
|
+
})
|
|
94
|
+
.option('relative-path', {
|
|
95
|
+
type: 'string',
|
|
96
|
+
describe: 'Override the relative path for a single positional source',
|
|
97
|
+
})
|
|
98
|
+
.option('sheet-id', {
|
|
99
|
+
type: 'string',
|
|
100
|
+
describe: 'Append the imported exercises to an existing sheet',
|
|
101
|
+
})
|
|
102
|
+
.option('timeout-ms', {
|
|
103
|
+
type: 'number',
|
|
104
|
+
default: 300000,
|
|
105
|
+
describe: 'Maximum time to wait before timing out',
|
|
106
|
+
})
|
|
107
|
+
.option('wait', {
|
|
108
|
+
type: 'boolean',
|
|
109
|
+
default: false,
|
|
110
|
+
describe: 'Wait for the queued job to finish',
|
|
111
|
+
})
|
|
112
|
+
.example('chalksurf exercise import ./fixtures/problem-set.pdf --sheet-id 00000000-0000-4000-8000-000000000001 --wait', 'Import a problem set into an existing sheet')
|
|
113
|
+
.example('cat exercise-import.json | chalksurf exercise import --manifest - --wait --json', 'Drive imports from a manifest in an agent or CI flow')
|
|
114
|
+
.epilogue('When --wait is set, the command exits after the queued import job reaches a terminal state.'), async (argv) => {
|
|
115
|
+
const config = await context.configStore.load();
|
|
116
|
+
const rawSources = (argv.sources ?? []).map(String);
|
|
117
|
+
const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
|
|
118
|
+
const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
|
|
119
|
+
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
120
|
+
flagValue: argv.baseUrl,
|
|
121
|
+
env: context.env,
|
|
122
|
+
config,
|
|
123
|
+
});
|
|
124
|
+
const resolvedToken = requireResolvedToken({
|
|
125
|
+
env: context.env,
|
|
126
|
+
config,
|
|
127
|
+
});
|
|
128
|
+
const commandSources = await buildFileImportCommandSources({
|
|
129
|
+
cwd: context.cwd,
|
|
130
|
+
loadManifest: loadExerciseImportManifest,
|
|
131
|
+
manifestPath,
|
|
132
|
+
rawSources: normalizedRawSources,
|
|
133
|
+
relativePath: argv.relativePath,
|
|
134
|
+
stdin: context.stdin,
|
|
135
|
+
wait: argv.wait,
|
|
136
|
+
});
|
|
137
|
+
const organizationId = resolveRequestedOrganizationId({
|
|
138
|
+
flagValue: argv.organization,
|
|
139
|
+
fallbackValue: commandSources.organizationId,
|
|
140
|
+
env: context.env,
|
|
141
|
+
config,
|
|
142
|
+
});
|
|
143
|
+
const exerciseSheetId = resolveRequestedUuid({
|
|
144
|
+
flagValue: argv.sheetId,
|
|
145
|
+
fallbackValue: commandSources.exerciseSheetId,
|
|
146
|
+
label: '--sheet-id',
|
|
147
|
+
});
|
|
148
|
+
const apiClient = createApiClient({
|
|
149
|
+
baseUrl: resolvedBaseUrl.value,
|
|
150
|
+
token: resolvedToken.value,
|
|
151
|
+
organizationId,
|
|
152
|
+
});
|
|
153
|
+
let resolvedSources = [];
|
|
154
|
+
try {
|
|
155
|
+
resolvedSources = await resolveSources({
|
|
156
|
+
cwd: context.cwd,
|
|
157
|
+
sources: commandSources.sources,
|
|
158
|
+
});
|
|
159
|
+
const sources = buildCliImportSources({
|
|
160
|
+
resolvedSources,
|
|
161
|
+
});
|
|
162
|
+
const formData = await buildFileImportFormData(resolvedSources);
|
|
163
|
+
const request = buildExerciseImportRequest({
|
|
164
|
+
exerciseSheetId: exerciseSheetId ?? null,
|
|
165
|
+
inputMode: manifestPath ? 'manifest' : 'arguments',
|
|
166
|
+
organizationId: organizationId ?? null,
|
|
167
|
+
timeoutMs: commandSources.wait ? normalizeWaitTimeoutMs(argv.timeoutMs) : null,
|
|
168
|
+
wait: commandSources.wait,
|
|
169
|
+
});
|
|
170
|
+
if (exerciseSheetId) {
|
|
171
|
+
formData.set('exerciseSheetId', exerciseSheetId);
|
|
172
|
+
}
|
|
173
|
+
let importResult;
|
|
174
|
+
try {
|
|
175
|
+
importResult = await apiClient.importExercise(formData);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw mapApiErrorToCliError(error);
|
|
179
|
+
}
|
|
180
|
+
if (!commandSources.wait) {
|
|
181
|
+
const jobs = [
|
|
182
|
+
buildQueuedExerciseImportJob({
|
|
183
|
+
jobId: importResult.jobId,
|
|
184
|
+
sources,
|
|
185
|
+
}),
|
|
186
|
+
];
|
|
187
|
+
const summary = summarizeCliImportJobs({
|
|
188
|
+
jobs,
|
|
189
|
+
sourceCount: sources.length,
|
|
190
|
+
sourceInputCount: commandSources.sources.length,
|
|
191
|
+
timedOut: false,
|
|
192
|
+
});
|
|
193
|
+
context.output.print({
|
|
194
|
+
request,
|
|
195
|
+
sources,
|
|
196
|
+
jobs,
|
|
197
|
+
summary,
|
|
198
|
+
}, (result) => formatQueuedImportOutput(result.jobs[0]), {
|
|
199
|
+
command: 'exercise import',
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const waitTimeoutMs = normalizeWaitTimeoutMs(argv.timeoutMs);
|
|
204
|
+
const waitResult = await waitForCliJobs({
|
|
205
|
+
getUserJob: async (jobId) => await apiClient.getUserJob(jobId),
|
|
206
|
+
jobIds: [importResult.jobId],
|
|
207
|
+
now: context.now,
|
|
208
|
+
sleep: context.sleep,
|
|
209
|
+
timeoutMs: waitTimeoutMs,
|
|
210
|
+
});
|
|
211
|
+
const waitedJob = waitResult.jobs[0];
|
|
212
|
+
const jobs = [
|
|
213
|
+
buildWaitedExerciseImportJob({
|
|
214
|
+
jobId: importResult.jobId,
|
|
215
|
+
sources,
|
|
216
|
+
waitedJob: waitedJob == null
|
|
217
|
+
? undefined
|
|
218
|
+
: {
|
|
219
|
+
...waitedJob,
|
|
220
|
+
id: waitedJob.id,
|
|
221
|
+
status: waitedJob.status,
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
];
|
|
225
|
+
const summary = summarizeCliImportJobs({
|
|
226
|
+
jobs,
|
|
227
|
+
sourceCount: sources.length,
|
|
228
|
+
sourceInputCount: commandSources.sources.length,
|
|
229
|
+
timedOut: waitResult.timedOut,
|
|
230
|
+
});
|
|
231
|
+
const waitError = getWaitError({
|
|
232
|
+
jobs: waitResult.jobs,
|
|
233
|
+
timedOut: waitResult.timedOut,
|
|
234
|
+
});
|
|
235
|
+
context.output.print({
|
|
236
|
+
request: {
|
|
237
|
+
...request,
|
|
238
|
+
timeoutMs: waitTimeoutMs,
|
|
239
|
+
},
|
|
240
|
+
sources,
|
|
241
|
+
jobs,
|
|
242
|
+
summary,
|
|
243
|
+
}, (result) => formatWaitedImportOutput(result.jobs[0]), {
|
|
244
|
+
command: 'exercise import',
|
|
245
|
+
ok: waitError == null,
|
|
246
|
+
error: waitError,
|
|
247
|
+
});
|
|
248
|
+
throwSilentExitCode(getWaitExitCode({ jobs: waitResult.jobs, timedOut: waitResult.timedOut }));
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
await cleanupResolvedSources(resolvedSources);
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.command('import-solution [exerciseId] [sources..]', 'Import one or more exercise solution files', (importSolutionYargs) => importSolutionYargs
|
|
258
|
+
.positional('exerciseId', {
|
|
259
|
+
type: 'string',
|
|
260
|
+
describe: 'Exercise ID to attach the imported solution to',
|
|
261
|
+
})
|
|
262
|
+
.positional('sources', {
|
|
263
|
+
array: true,
|
|
264
|
+
type: 'string',
|
|
265
|
+
describe: 'Local file paths, directories, or HTTP(S) URLs',
|
|
266
|
+
})
|
|
267
|
+
.option('manifest', {
|
|
268
|
+
type: 'string',
|
|
269
|
+
describe: 'Load sources from a manifest file, or "-" for stdin',
|
|
270
|
+
})
|
|
271
|
+
.option('relative-path', {
|
|
272
|
+
type: 'string',
|
|
273
|
+
describe: 'Override the relative path for a single positional source',
|
|
274
|
+
})
|
|
275
|
+
.option('timeout-ms', {
|
|
276
|
+
type: 'number',
|
|
277
|
+
default: 300000,
|
|
278
|
+
describe: 'Maximum time to wait before timing out',
|
|
279
|
+
})
|
|
280
|
+
.option('wait', {
|
|
281
|
+
type: 'boolean',
|
|
282
|
+
default: false,
|
|
283
|
+
describe: 'Wait for the queued job to finish',
|
|
284
|
+
})
|
|
285
|
+
.example('chalksurf exercise import-solution 00000000-0000-4000-8000-000000000001 ./fixtures/solution.pdf --wait', 'Attach a solution file to an existing exercise')
|
|
286
|
+
.example('cat exercise-solution-import.json | chalksurf exercise import-solution --manifest - --wait --json', 'Drive solution imports from a manifest in an agent or CI flow')
|
|
287
|
+
.epilogue('When --wait is set, the command exits after the queued import job reaches a terminal state.'), async (argv) => {
|
|
288
|
+
const config = await context.configStore.load();
|
|
289
|
+
const rawSources = (argv.sources ?? []).map(String);
|
|
290
|
+
const manifestPath = argv.manifest === '' ? '-' : argv.manifest;
|
|
291
|
+
const normalizedRawSources = manifestPath === '-' && rawSources[0] === '-' ? rawSources.slice(1) : rawSources;
|
|
292
|
+
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
293
|
+
flagValue: argv.baseUrl,
|
|
294
|
+
env: context.env,
|
|
295
|
+
config,
|
|
296
|
+
});
|
|
297
|
+
const resolvedToken = requireResolvedToken({
|
|
298
|
+
env: context.env,
|
|
299
|
+
config,
|
|
300
|
+
});
|
|
301
|
+
const commandSources = await buildFileImportCommandSources({
|
|
302
|
+
cwd: context.cwd,
|
|
303
|
+
loadManifest: loadExerciseSolutionImportManifest,
|
|
304
|
+
manifestPath,
|
|
305
|
+
rawSources: normalizedRawSources,
|
|
306
|
+
relativePath: argv.relativePath,
|
|
307
|
+
stdin: context.stdin,
|
|
308
|
+
wait: argv.wait,
|
|
309
|
+
});
|
|
310
|
+
const organizationId = resolveRequestedOrganizationId({
|
|
311
|
+
flagValue: argv.organization,
|
|
312
|
+
fallbackValue: commandSources.organizationId,
|
|
313
|
+
env: context.env,
|
|
314
|
+
config,
|
|
315
|
+
});
|
|
316
|
+
const exerciseId = resolveRequestedUuid({
|
|
317
|
+
flagValue: argv.exerciseId,
|
|
318
|
+
fallbackValue: commandSources.exerciseId,
|
|
319
|
+
label: 'exerciseId',
|
|
320
|
+
required: true,
|
|
321
|
+
});
|
|
322
|
+
const apiClient = createApiClient({
|
|
323
|
+
baseUrl: resolvedBaseUrl.value,
|
|
324
|
+
token: resolvedToken.value,
|
|
325
|
+
organizationId,
|
|
326
|
+
});
|
|
327
|
+
let resolvedSources = [];
|
|
328
|
+
try {
|
|
329
|
+
resolvedSources = await resolveSources({
|
|
330
|
+
cwd: context.cwd,
|
|
331
|
+
sources: commandSources.sources,
|
|
332
|
+
});
|
|
333
|
+
const sources = buildCliImportSources({
|
|
334
|
+
resolvedSources,
|
|
335
|
+
});
|
|
336
|
+
const formData = await buildFileImportFormData(resolvedSources);
|
|
337
|
+
formData.set('exerciseId', exerciseId);
|
|
338
|
+
const request = buildExerciseImportRequest({
|
|
339
|
+
exerciseId,
|
|
340
|
+
inputMode: manifestPath ? 'manifest' : 'arguments',
|
|
341
|
+
organizationId: organizationId ?? null,
|
|
342
|
+
timeoutMs: commandSources.wait ? normalizeWaitTimeoutMs(argv.timeoutMs) : null,
|
|
343
|
+
wait: commandSources.wait,
|
|
344
|
+
});
|
|
345
|
+
let importResult;
|
|
346
|
+
try {
|
|
347
|
+
importResult = await apiClient.importExerciseSolution(formData);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
throw mapApiErrorToCliError(error);
|
|
351
|
+
}
|
|
352
|
+
if (!commandSources.wait) {
|
|
353
|
+
const jobs = [
|
|
354
|
+
buildQueuedExerciseImportJob({
|
|
355
|
+
jobId: importResult.jobId,
|
|
356
|
+
sources,
|
|
357
|
+
}),
|
|
358
|
+
];
|
|
359
|
+
const summary = summarizeCliImportJobs({
|
|
360
|
+
jobs,
|
|
361
|
+
sourceCount: sources.length,
|
|
362
|
+
sourceInputCount: commandSources.sources.length,
|
|
363
|
+
timedOut: false,
|
|
364
|
+
});
|
|
365
|
+
context.output.print({
|
|
366
|
+
request,
|
|
367
|
+
sources,
|
|
368
|
+
jobs,
|
|
369
|
+
summary,
|
|
370
|
+
}, (result) => formatQueuedSolutionImportOutput(result.jobs[0]), {
|
|
371
|
+
command: 'exercise import-solution',
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const waitTimeoutMs = normalizeWaitTimeoutMs(argv.timeoutMs);
|
|
376
|
+
const waitResult = await waitForCliJobs({
|
|
377
|
+
getUserJob: async (jobId) => await apiClient.getUserJob(jobId),
|
|
378
|
+
jobIds: [importResult.jobId],
|
|
379
|
+
now: context.now,
|
|
380
|
+
sleep: context.sleep,
|
|
381
|
+
timeoutMs: waitTimeoutMs,
|
|
382
|
+
});
|
|
383
|
+
const waitedJob = waitResult.jobs[0];
|
|
384
|
+
const jobs = [
|
|
385
|
+
buildWaitedExerciseImportJob({
|
|
386
|
+
jobId: importResult.jobId,
|
|
387
|
+
sources,
|
|
388
|
+
waitedJob: waitedJob == null
|
|
389
|
+
? undefined
|
|
390
|
+
: {
|
|
391
|
+
...waitedJob,
|
|
392
|
+
id: waitedJob.id,
|
|
393
|
+
status: waitedJob.status,
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
];
|
|
397
|
+
const summary = summarizeCliImportJobs({
|
|
398
|
+
jobs,
|
|
399
|
+
sourceCount: sources.length,
|
|
400
|
+
sourceInputCount: commandSources.sources.length,
|
|
401
|
+
timedOut: waitResult.timedOut,
|
|
402
|
+
});
|
|
403
|
+
const waitError = getWaitError({
|
|
404
|
+
jobs: waitResult.jobs,
|
|
405
|
+
timedOut: waitResult.timedOut,
|
|
406
|
+
});
|
|
407
|
+
context.output.print({
|
|
408
|
+
request: {
|
|
409
|
+
...request,
|
|
410
|
+
timeoutMs: waitTimeoutMs,
|
|
411
|
+
},
|
|
412
|
+
sources,
|
|
413
|
+
jobs,
|
|
414
|
+
summary,
|
|
415
|
+
}, (result) => formatWaitedImportOutput(result.jobs[0]), {
|
|
416
|
+
command: 'exercise import-solution',
|
|
417
|
+
ok: waitError == null,
|
|
418
|
+
error: waitError,
|
|
419
|
+
});
|
|
420
|
+
throwSilentExitCode(getWaitExitCode({ jobs: waitResult.jobs, timedOut: waitResult.timedOut }));
|
|
421
|
+
}
|
|
422
|
+
finally {
|
|
423
|
+
await cleanupResolvedSources(resolvedSources);
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
.demandCommand(1)
|
|
427
|
+
.strict();
|
|
428
|
+
};
|
package/dist/commands/job.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createApiClient } from '../lib/api-client.js';
|
|
2
2
|
import { CliCommandError } from '../lib/cli-error.js';
|
|
3
3
|
import { mapApiErrorToCliError, requireResolvedBaseUrl, requireResolvedToken, resolveRequestedOrganizationId, } from '../lib/session.js';
|
|
4
|
-
import { formatCliJobSummary, getWaitExitCode, serializeCliJob, throwSilentExitCode, waitForCliJobs, } from '../lib/user-jobs.js';
|
|
4
|
+
import { formatCliJobSummary, getWaitError, getWaitExitCode, serializeCliJob, throwSilentExitCode, waitForCliJobs, } from '../lib/user-jobs.js';
|
|
5
5
|
const normalizeTimeoutMs = (timeoutMs) => {
|
|
6
6
|
const normalizedTimeoutMs = timeoutMs ?? 300000;
|
|
7
7
|
if (!Number.isFinite(normalizedTimeoutMs) || normalizedTimeoutMs <= 0) {
|
|
@@ -11,10 +11,13 @@ const normalizeTimeoutMs = (timeoutMs) => {
|
|
|
11
11
|
};
|
|
12
12
|
export const registerJobCommands = (jobYargs, context) => {
|
|
13
13
|
return jobYargs
|
|
14
|
-
.command('get <jobId>', 'Fetch a single job', (getYargs) => getYargs
|
|
14
|
+
.command('get <jobId>', 'Fetch a single job', (getYargs) => getYargs
|
|
15
|
+
.positional('jobId', {
|
|
15
16
|
type: 'string',
|
|
16
17
|
describe: 'Job ID to fetch',
|
|
17
|
-
})
|
|
18
|
+
})
|
|
19
|
+
.example('chalksurf job get job_123', 'Read one job in human-readable mode')
|
|
20
|
+
.example('chalksurf job get job_123 --json', 'Read one job in machine-readable mode'), async (argv) => {
|
|
18
21
|
const config = await context.configStore.load();
|
|
19
22
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
20
23
|
flagValue: argv.baseUrl,
|
|
@@ -34,7 +37,6 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
34
37
|
baseUrl: resolvedBaseUrl.value,
|
|
35
38
|
token: resolvedToken.value,
|
|
36
39
|
organizationId,
|
|
37
|
-
fetchImpl: context.fetchImpl,
|
|
38
40
|
});
|
|
39
41
|
let job;
|
|
40
42
|
try {
|
|
@@ -44,7 +46,9 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
44
46
|
throw mapApiErrorToCliError(error);
|
|
45
47
|
}
|
|
46
48
|
const serializedJob = serializeCliJob(job);
|
|
47
|
-
context.output.print({ job: serializedJob }, (result) => formatCliJobSummary(result.job)
|
|
49
|
+
context.output.print({ job: serializedJob }, (result) => formatCliJobSummary(result.job), {
|
|
50
|
+
command: 'job get',
|
|
51
|
+
});
|
|
48
52
|
})
|
|
49
53
|
.command('wait <jobIds..>', 'Poll jobs until they complete, fail, or time out', (waitYargs) => waitYargs
|
|
50
54
|
.positional('jobIds', {
|
|
@@ -56,7 +60,9 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
56
60
|
type: 'number',
|
|
57
61
|
default: 300000,
|
|
58
62
|
describe: 'Maximum time to wait before timing out',
|
|
59
|
-
})
|
|
63
|
+
})
|
|
64
|
+
.example('chalksurf job wait job_123 job_124', 'Wait for one or more jobs to reach a terminal state')
|
|
65
|
+
.example('chalksurf job wait job_123 --timeout-ms 60000 --json', 'Poll a job for up to 60 seconds'), async (argv) => {
|
|
60
66
|
const config = await context.configStore.load();
|
|
61
67
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
62
68
|
flagValue: argv.baseUrl,
|
|
@@ -76,7 +82,6 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
76
82
|
baseUrl: resolvedBaseUrl.value,
|
|
77
83
|
token: resolvedToken.value,
|
|
78
84
|
organizationId,
|
|
79
|
-
fetchImpl: context.fetchImpl,
|
|
80
85
|
});
|
|
81
86
|
const timeoutMs = normalizeTimeoutMs(argv.timeoutMs);
|
|
82
87
|
const waitResult = await waitForCliJobs({
|
|
@@ -86,7 +91,12 @@ export const registerJobCommands = (jobYargs, context) => {
|
|
|
86
91
|
sleep: context.sleep,
|
|
87
92
|
timeoutMs,
|
|
88
93
|
});
|
|
89
|
-
|
|
94
|
+
const waitError = getWaitError(waitResult);
|
|
95
|
+
context.output.print(waitResult, (result) => result.jobs.map((job) => formatCliJobSummary(job)).join('\n'), {
|
|
96
|
+
command: 'job wait',
|
|
97
|
+
ok: waitError == null,
|
|
98
|
+
error: waitError,
|
|
99
|
+
});
|
|
90
100
|
throwSilentExitCode(getWaitExitCode(waitResult));
|
|
91
101
|
})
|
|
92
102
|
.demandCommand(1)
|
package/dist/commands/org.js
CHANGED
|
@@ -13,7 +13,9 @@ const serializeOrganization = ({ organizationId, organizationName, organizationT
|
|
|
13
13
|
};
|
|
14
14
|
export const registerOrgCommands = (orgYargs, context) => {
|
|
15
15
|
return orgYargs
|
|
16
|
-
.command('list', 'List organizations available to the authenticated user', () =>
|
|
16
|
+
.command('list', 'List organizations available to the authenticated user', (listYargs) => listYargs
|
|
17
|
+
.example('chalksurf org list', 'List organizations for the current session')
|
|
18
|
+
.example('chalksurf org list --json', 'Inspect the available organizations in machine-readable mode'), async (argv) => {
|
|
17
19
|
const config = await context.configStore.load();
|
|
18
20
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
19
21
|
flagValue: argv.baseUrl,
|
|
@@ -27,7 +29,6 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
27
29
|
const apiClient = createApiClient({
|
|
28
30
|
baseUrl: resolvedBaseUrl.value,
|
|
29
31
|
token: resolvedToken.value,
|
|
30
|
-
fetchImpl: context.fetchImpl,
|
|
31
32
|
});
|
|
32
33
|
let profile;
|
|
33
34
|
try {
|
|
@@ -53,16 +54,19 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
53
54
|
organizations,
|
|
54
55
|
selectedOrganizationId: resolvedOrganization.organization?.organizationId ?? null,
|
|
55
56
|
selectedOrganizationSource: resolvedOrganization.source,
|
|
56
|
-
warnings: resolvedOrganization.warnings,
|
|
57
57
|
}, (result) => result.organizations
|
|
58
58
|
.map((organization) => `${organization.selected ? '*' : ' '} ${organization.name} (${organization.id}) [${organization.type}, ${organization.role}]`)
|
|
59
|
-
.join('\n')
|
|
59
|
+
.join('\n'), {
|
|
60
|
+
command: 'org list',
|
|
61
|
+
});
|
|
60
62
|
})
|
|
61
63
|
.command('use <organizationId>', 'Set the default organization used by the CLI', (useYargs) => {
|
|
62
|
-
return useYargs
|
|
64
|
+
return useYargs
|
|
65
|
+
.positional('organizationId', {
|
|
63
66
|
type: 'string',
|
|
64
67
|
describe: 'Organization ID to store as the default',
|
|
65
|
-
})
|
|
68
|
+
})
|
|
69
|
+
.example('chalksurf org use org_123', 'Store org_123 as the default organization for future commands');
|
|
66
70
|
}, async (argv) => {
|
|
67
71
|
const config = await context.configStore.load();
|
|
68
72
|
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
@@ -77,7 +81,6 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
77
81
|
const apiClient = createApiClient({
|
|
78
82
|
baseUrl: resolvedBaseUrl.value,
|
|
79
83
|
token: resolvedToken.value,
|
|
80
|
-
fetchImpl: context.fetchImpl,
|
|
81
84
|
});
|
|
82
85
|
let profile;
|
|
83
86
|
try {
|
|
@@ -99,7 +102,9 @@ export const registerOrgCommands = (orgYargs, context) => {
|
|
|
99
102
|
...organization,
|
|
100
103
|
selected: true,
|
|
101
104
|
}),
|
|
102
|
-
}, (result) => `Default organization set to ${result.organization.name} (${result.organization.id})
|
|
105
|
+
}, (result) => `Default organization set to ${result.organization.name} (${result.organization.id}).`, {
|
|
106
|
+
command: 'org use',
|
|
107
|
+
});
|
|
103
108
|
})
|
|
104
109
|
.demandCommand(1)
|
|
105
110
|
.strict();
|