@chalksurf/cli 0.1.0 → 0.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/README.md +31 -94
- package/dist/bin/chalksurf.js +84 -10
- package/dist/commands/auth.js +43 -24
- package/dist/commands/exercise.js +428 -0
- package/dist/commands/job.js +20 -10
- package/dist/commands/org.js +20 -11
- package/dist/commands/profile.js +97 -0
- package/dist/commands/sheet.js +287 -130
- package/dist/lib/api-client.js +9 -3
- package/dist/lib/cli-error.js +37 -1
- package/dist/lib/config-store.js +188 -10
- 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 +203 -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 +169 -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
package/dist/lib/config-store.js
CHANGED
|
@@ -5,7 +5,11 @@ import { CliCommandError } from './cli-error.js';
|
|
|
5
5
|
export const chalksurfBaseUrlEnvVar = 'CHALKSURF_BASE_URL';
|
|
6
6
|
export const chalksurfTokenEnvVar = 'CHALKSURF_TOKEN';
|
|
7
7
|
export const chalksurfOrganizationIdEnvVar = 'CHALKSURF_ORGANIZATION_ID';
|
|
8
|
+
export const chalksurfProfileEnvVar = 'CHALKSURF_PROFILE';
|
|
8
9
|
export const chalksurfConfigPathEnvVar = 'CHALKSURF_CONFIG_PATH';
|
|
10
|
+
export const cliConfigSchemaVersion = 2;
|
|
11
|
+
export const implicitDefaultProfileName = 'default';
|
|
12
|
+
const profileNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
9
13
|
const normalizeOptionalString = (value) => {
|
|
10
14
|
const trimmedValue = value?.trim();
|
|
11
15
|
return trimmedValue ? trimmedValue : undefined;
|
|
@@ -14,13 +18,91 @@ const normalizeStoredBaseUrl = (value) => {
|
|
|
14
18
|
const normalizedValue = normalizeOptionalString(value);
|
|
15
19
|
return normalizedValue?.replace(/\/+$/, '') || undefined;
|
|
16
20
|
};
|
|
17
|
-
const
|
|
21
|
+
const isRecord = (value) => {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
};
|
|
24
|
+
export const assertValidProfileName = (profileName) => {
|
|
25
|
+
if (!profileNamePattern.test(profileName)) {
|
|
26
|
+
throw new CliCommandError(`Invalid profile "${profileName}". Profile names must start with a letter or number and contain only letters, numbers, ".", "_", or "-".`, 2);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const normalizeProfileName = (value) => {
|
|
30
|
+
const profileName = normalizeOptionalString(value);
|
|
31
|
+
if (!profileName) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
assertValidProfileName(profileName);
|
|
35
|
+
return profileName;
|
|
36
|
+
};
|
|
37
|
+
const sanitizeProfileConfig = (config) => {
|
|
18
38
|
return Object.fromEntries(Object.entries({
|
|
19
39
|
token: normalizeOptionalString(config.token),
|
|
20
40
|
baseUrl: normalizeStoredBaseUrl(config.baseUrl),
|
|
21
41
|
organizationId: normalizeOptionalString(config.organizationId),
|
|
22
42
|
}).filter(([, value]) => value !== undefined));
|
|
23
43
|
};
|
|
44
|
+
const hasProfileConfigValues = (config) => {
|
|
45
|
+
return Boolean(config.baseUrl || config.token || config.organizationId);
|
|
46
|
+
};
|
|
47
|
+
const readProfileConfig = (value) => {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
return sanitizeProfileConfig({
|
|
52
|
+
baseUrl: typeof value.baseUrl === 'string' ? value.baseUrl : undefined,
|
|
53
|
+
organizationId: typeof value.organizationId === 'string' ? value.organizationId : undefined,
|
|
54
|
+
token: typeof value.token === 'string' ? value.token : undefined,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
const sanitizeRootConfig = (config) => {
|
|
58
|
+
const profiles = Object.fromEntries(Object.entries(config.profiles)
|
|
59
|
+
.map(([profileName, profileConfig]) => {
|
|
60
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
61
|
+
const sanitizedProfileConfig = sanitizeProfileConfig(profileConfig);
|
|
62
|
+
return normalizedProfileName && hasProfileConfigValues(sanitizedProfileConfig)
|
|
63
|
+
? [normalizedProfileName, sanitizedProfileConfig]
|
|
64
|
+
: null;
|
|
65
|
+
})
|
|
66
|
+
.filter((entry) => entry !== null));
|
|
67
|
+
const defaultProfile = normalizeProfileName(config.defaultProfile);
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
70
|
+
...(defaultProfile && profiles[defaultProfile] ? { defaultProfile } : {}),
|
|
71
|
+
profiles,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const readRootConfig = (value) => {
|
|
75
|
+
if (!isRecord(value)) {
|
|
76
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
77
|
+
}
|
|
78
|
+
if (isRecord(value.profiles)) {
|
|
79
|
+
const profiles = Object.fromEntries(Object.entries(value.profiles)
|
|
80
|
+
.map(([profileName, profileConfig]) => {
|
|
81
|
+
const sanitizedProfileConfig = readProfileConfig(profileConfig);
|
|
82
|
+
return hasProfileConfigValues(sanitizedProfileConfig)
|
|
83
|
+
? [profileName, sanitizedProfileConfig]
|
|
84
|
+
: null;
|
|
85
|
+
})
|
|
86
|
+
.filter((entry) => entry !== null));
|
|
87
|
+
return sanitizeRootConfig({
|
|
88
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
89
|
+
defaultProfile: typeof value.defaultProfile === 'string' ? value.defaultProfile : undefined,
|
|
90
|
+
profiles,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const legacyProfile = readProfileConfig(value);
|
|
94
|
+
if (!hasProfileConfigValues(legacyProfile)) {
|
|
95
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
99
|
+
defaultProfile: implicitDefaultProfileName,
|
|
100
|
+
profiles: {
|
|
101
|
+
[implicitDefaultProfileName]: legacyProfile,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const rootConfigHasProfiles = (config) => Object.keys(config.profiles).length > 0;
|
|
24
106
|
const getDefaultConfigPath = ({ env, platform, homeDirectory, }) => {
|
|
25
107
|
const explicitConfigPath = normalizeOptionalString(env[chalksurfConfigPathEnvVar]);
|
|
26
108
|
if (explicitConfigPath) {
|
|
@@ -73,6 +155,24 @@ export const resolveToken = ({ env, config }) => {
|
|
|
73
155
|
}
|
|
74
156
|
return { source: 'none' };
|
|
75
157
|
};
|
|
158
|
+
export const resolveProfile = ({ flagValue, env, config, }) => {
|
|
159
|
+
const flagProfileName = normalizeProfileName(flagValue);
|
|
160
|
+
if (flagProfileName) {
|
|
161
|
+
return { profileName: flagProfileName, profileSource: 'flag' };
|
|
162
|
+
}
|
|
163
|
+
const envProfileName = normalizeProfileName(env[chalksurfProfileEnvVar]);
|
|
164
|
+
if (envProfileName) {
|
|
165
|
+
return { profileName: envProfileName, profileSource: 'env' };
|
|
166
|
+
}
|
|
167
|
+
const defaultProfile = normalizeProfileName(config.defaultProfile);
|
|
168
|
+
if (defaultProfile) {
|
|
169
|
+
return { profileName: defaultProfile, profileSource: 'config' };
|
|
170
|
+
}
|
|
171
|
+
if (!rootConfigHasProfiles(config)) {
|
|
172
|
+
return { profileName: implicitDefaultProfileName, profileSource: 'implicit-default' };
|
|
173
|
+
}
|
|
174
|
+
throw new CliCommandError(`No active ChalkSurf profile selected. Pass --profile, set ${chalksurfProfileEnvVar}, or run "chalksurf profile use <profile>".`, 2);
|
|
175
|
+
};
|
|
76
176
|
export const resolveOrganization = ({ flagValue, env, config, profile, }) => {
|
|
77
177
|
const flagOrganizationId = normalizeOptionalString(flagValue);
|
|
78
178
|
if (flagOrganizationId) {
|
|
@@ -134,14 +234,23 @@ export const resolveOrganization = ({ flagValue, env, config, profile, }) => {
|
|
|
134
234
|
export const createConfigStore = ({ configPath, env = process.env, platform = process.platform, homeDirectory = homedir(), fs = { chmod, mkdir, readFile, rm, writeFile }, } = {}) => {
|
|
135
235
|
const resolvedConfigPath = configPath || getDefaultConfigPath({ env, platform, homeDirectory });
|
|
136
236
|
const load = async () => {
|
|
237
|
+
const rootConfig = await loadRoot();
|
|
238
|
+
const resolvedProfile = resolveProfile({ env, config: rootConfig });
|
|
239
|
+
const profileConfig = rootConfig.profiles[resolvedProfile.profileName] ?? {};
|
|
240
|
+
return {
|
|
241
|
+
...profileConfig,
|
|
242
|
+
...resolvedProfile,
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
const loadRoot = async () => {
|
|
137
246
|
try {
|
|
138
247
|
const configContents = await fs.readFile(resolvedConfigPath, 'utf8');
|
|
139
248
|
const parsedConfig = JSON.parse(configContents);
|
|
140
|
-
return
|
|
249
|
+
return readRootConfig(parsedConfig);
|
|
141
250
|
}
|
|
142
251
|
catch (error) {
|
|
143
252
|
if (error.code === 'ENOENT') {
|
|
144
|
-
return {};
|
|
253
|
+
return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
|
|
145
254
|
}
|
|
146
255
|
if (error instanceof SyntaxError) {
|
|
147
256
|
throw new CliCommandError(`Failed to parse CLI config at ${resolvedConfigPath}.`, 2);
|
|
@@ -149,9 +258,18 @@ export const createConfigStore = ({ configPath, env = process.env, platform = pr
|
|
|
149
258
|
throw error;
|
|
150
259
|
}
|
|
151
260
|
};
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
261
|
+
const loadProfile = async ({ profileName } = {}) => {
|
|
262
|
+
const rootConfig = await loadRoot();
|
|
263
|
+
const resolvedProfile = resolveProfile({ flagValue: profileName, env, config: rootConfig });
|
|
264
|
+
const profileConfig = rootConfig.profiles[resolvedProfile.profileName] ?? {};
|
|
265
|
+
return {
|
|
266
|
+
...profileConfig,
|
|
267
|
+
...resolvedProfile,
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
const saveRoot = async (config) => {
|
|
271
|
+
const sanitizedConfig = sanitizeRootConfig(config);
|
|
272
|
+
if (!rootConfigHasProfiles(sanitizedConfig)) {
|
|
155
273
|
await fs.rm(resolvedConfigPath, { force: true });
|
|
156
274
|
return sanitizedConfig;
|
|
157
275
|
}
|
|
@@ -162,15 +280,75 @@ export const createConfigStore = ({ configPath, env = process.env, platform = pr
|
|
|
162
280
|
await fs.chmod(resolvedConfigPath, 0o600);
|
|
163
281
|
return sanitizedConfig;
|
|
164
282
|
};
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
283
|
+
const save = async (config) => {
|
|
284
|
+
const rootConfig = await loadRoot();
|
|
285
|
+
const nextProfileConfig = sanitizeProfileConfig(config);
|
|
286
|
+
const nextRootConfig = {
|
|
287
|
+
...rootConfig,
|
|
288
|
+
profiles: {
|
|
289
|
+
...rootConfig.profiles,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
if (hasProfileConfigValues(nextProfileConfig)) {
|
|
293
|
+
nextRootConfig.profiles[config.profileName] = nextProfileConfig;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
delete nextRootConfig.profiles[config.profileName];
|
|
297
|
+
}
|
|
298
|
+
if (config.profileSource === 'implicit-default' && hasProfileConfigValues(nextProfileConfig)) {
|
|
299
|
+
nextRootConfig.defaultProfile = config.profileName;
|
|
300
|
+
}
|
|
301
|
+
return await saveRoot(nextRootConfig);
|
|
302
|
+
};
|
|
303
|
+
const update = async ({ profileName }, updater) => {
|
|
304
|
+
const currentConfig = await loadProfile({ profileName });
|
|
305
|
+
const nextProfileConfig = await updater(currentConfig);
|
|
306
|
+
return await save({
|
|
307
|
+
...nextProfileConfig,
|
|
308
|
+
profileName: currentConfig.profileName,
|
|
309
|
+
profileSource: currentConfig.profileSource,
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
const setDefaultProfile = async (profileName) => {
|
|
313
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
314
|
+
if (!normalizedProfileName) {
|
|
315
|
+
throw new CliCommandError('Profile name is required.', 2);
|
|
316
|
+
}
|
|
317
|
+
const rootConfig = await loadRoot();
|
|
318
|
+
if (!rootConfig.profiles[normalizedProfileName]) {
|
|
319
|
+
throw new CliCommandError(`Profile "${normalizedProfileName}" does not exist. Run "chalksurf auth login --profile ${normalizedProfileName} --base-url <url>" first.`, 2);
|
|
320
|
+
}
|
|
321
|
+
return await saveRoot({
|
|
322
|
+
...rootConfig,
|
|
323
|
+
defaultProfile: normalizedProfileName,
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
const deleteProfile = async (profileName) => {
|
|
327
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
328
|
+
if (!normalizedProfileName) {
|
|
329
|
+
throw new CliCommandError('Profile name is required.', 2);
|
|
330
|
+
}
|
|
331
|
+
const rootConfig = await loadRoot();
|
|
332
|
+
if (!rootConfig.profiles[normalizedProfileName]) {
|
|
333
|
+
throw new CliCommandError(`Profile "${normalizedProfileName}" does not exist.`, 2);
|
|
334
|
+
}
|
|
335
|
+
const profiles = { ...rootConfig.profiles };
|
|
336
|
+
delete profiles[normalizedProfileName];
|
|
337
|
+
return await saveRoot({
|
|
338
|
+
schemaVersion: cliConfigSchemaVersion,
|
|
339
|
+
...(rootConfig.defaultProfile === normalizedProfileName ? {} : { defaultProfile: rootConfig.defaultProfile }),
|
|
340
|
+
profiles,
|
|
341
|
+
});
|
|
169
342
|
};
|
|
170
343
|
return {
|
|
171
344
|
path: resolvedConfigPath,
|
|
172
345
|
load,
|
|
346
|
+
loadProfile,
|
|
347
|
+
loadRoot,
|
|
173
348
|
save,
|
|
349
|
+
saveRoot,
|
|
174
350
|
update,
|
|
351
|
+
setDefaultProfile,
|
|
352
|
+
deleteProfile,
|
|
175
353
|
};
|
|
176
354
|
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { openAsBlob } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { extname, resolve } from 'node:path';
|
|
4
|
+
import { CliCommandError } from './cli-error.js';
|
|
5
|
+
const mimeTypesByExtension = {
|
|
6
|
+
'.avif': 'image/avif',
|
|
7
|
+
'.doc': 'application/msword',
|
|
8
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
9
|
+
'.gif': 'image/gif',
|
|
10
|
+
'.htm': 'text/html',
|
|
11
|
+
'.html': 'text/html',
|
|
12
|
+
'.jpeg': 'image/jpeg',
|
|
13
|
+
'.jpg': 'image/jpeg',
|
|
14
|
+
'.md': 'text/markdown',
|
|
15
|
+
'.pdf': 'application/pdf',
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.svg': 'image/svg+xml',
|
|
18
|
+
'.tex': 'text/plain',
|
|
19
|
+
'.tif': 'image/tiff',
|
|
20
|
+
'.tiff': 'image/tiff',
|
|
21
|
+
'.txt': 'text/plain',
|
|
22
|
+
'.webp': 'image/webp',
|
|
23
|
+
};
|
|
24
|
+
const guessMimeType = (fileName) => {
|
|
25
|
+
return mimeTypesByExtension[extname(fileName).toLowerCase()] ?? 'application/octet-stream';
|
|
26
|
+
};
|
|
27
|
+
const isHttpUrl = (value) => {
|
|
28
|
+
try {
|
|
29
|
+
const parsedUrl = new URL(value);
|
|
30
|
+
return ['http:', 'https:'].includes(parsedUrl.protocol);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const buildFileImportFormData = async (resolvedSources) => {
|
|
37
|
+
const formData = new FormData();
|
|
38
|
+
await Promise.all(resolvedSources.map(async (resolvedSource, index) => {
|
|
39
|
+
const mimeType = resolvedSource.mimeType ?? guessMimeType(resolvedSource.fileName);
|
|
40
|
+
const fileBlob = await openAsBlob(resolvedSource.filePath, { type: mimeType });
|
|
41
|
+
const file = new File([fileBlob], resolvedSource.fileName, { type: mimeType });
|
|
42
|
+
formData.set(`file-${index}`, file);
|
|
43
|
+
formData.set(`path-${index}`, resolvedSource.relativePath);
|
|
44
|
+
}));
|
|
45
|
+
return formData;
|
|
46
|
+
};
|
|
47
|
+
export const buildFileImportCommandSources = async ({ cwd, loadManifest, manifestPath, rawSources, relativePath, stdin, wait, }) => {
|
|
48
|
+
if (manifestPath && rawSources.length > 0) {
|
|
49
|
+
throw new CliCommandError('Pass positional sources or --manifest, not both.', 2);
|
|
50
|
+
}
|
|
51
|
+
if (manifestPath && relativePath) {
|
|
52
|
+
throw new CliCommandError('--relative-path can only be used with a single positional source.', 2);
|
|
53
|
+
}
|
|
54
|
+
if (manifestPath) {
|
|
55
|
+
const manifest = await loadManifest({
|
|
56
|
+
cwd,
|
|
57
|
+
manifestPath,
|
|
58
|
+
stdin,
|
|
59
|
+
});
|
|
60
|
+
const { organizationId, sources, wait: manifestWait, ...manifestExtras } = manifest;
|
|
61
|
+
return {
|
|
62
|
+
organizationId,
|
|
63
|
+
sources,
|
|
64
|
+
wait: wait || manifestWait === true,
|
|
65
|
+
...manifestExtras,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (rawSources.length === 0) {
|
|
69
|
+
throw new CliCommandError('At least one source or --manifest is required.', 2);
|
|
70
|
+
}
|
|
71
|
+
if (relativePath && rawSources.length !== 1) {
|
|
72
|
+
throw new CliCommandError('--relative-path can only be used with a single positional source.', 2);
|
|
73
|
+
}
|
|
74
|
+
const sources = [];
|
|
75
|
+
for (const rawSource of rawSources) {
|
|
76
|
+
if (isHttpUrl(rawSource)) {
|
|
77
|
+
sources.push({
|
|
78
|
+
kind: 'url',
|
|
79
|
+
url: rawSource,
|
|
80
|
+
relativePath,
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const pathStats = await stat(resolve(cwd, rawSource));
|
|
86
|
+
if (pathStats.isDirectory()) {
|
|
87
|
+
if (relativePath) {
|
|
88
|
+
throw new CliCommandError('--relative-path cannot be used with directory sources.', 2);
|
|
89
|
+
}
|
|
90
|
+
sources.push({
|
|
91
|
+
kind: 'directory',
|
|
92
|
+
path: rawSource,
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error.code !== 'ENOENT') {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
sources.push({
|
|
103
|
+
kind: 'local',
|
|
104
|
+
path: rawSource,
|
|
105
|
+
relativePath,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
organizationId: undefined,
|
|
110
|
+
sources,
|
|
111
|
+
wait: wait === true,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
export const normalizeWaitTimeoutMs = (timeoutMs) => {
|
|
115
|
+
const normalizedTimeoutMs = timeoutMs ?? 300000;
|
|
116
|
+
if (!Number.isFinite(normalizedTimeoutMs) || normalizedTimeoutMs <= 0) {
|
|
117
|
+
throw new CliCommandError('--timeout-ms must be a positive number.', 2);
|
|
118
|
+
}
|
|
119
|
+
return normalizedTimeoutMs;
|
|
120
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const buildCliImportSources = ({ resolvedSources, getExtra, }) => {
|
|
2
|
+
return resolvedSources.map((resolvedSource, sourceIndex) => ({
|
|
3
|
+
sourceIndex,
|
|
4
|
+
sourceInputIndex: resolvedSource.sourceInputIndex,
|
|
5
|
+
sourceId: resolvedSource.sourceInput.sourceId ?? null,
|
|
6
|
+
kind: resolvedSource.kind,
|
|
7
|
+
input: resolvedSource.input,
|
|
8
|
+
fileName: resolvedSource.fileName,
|
|
9
|
+
relativePath: resolvedSource.relativePath,
|
|
10
|
+
...(getExtra ? getExtra(resolvedSource) : {}),
|
|
11
|
+
}));
|
|
12
|
+
};
|
|
13
|
+
export const summarizeCliImportJobs = ({ jobs, sourceCount, sourceInputCount, timedOut, }) => {
|
|
14
|
+
const summary = {
|
|
15
|
+
sourceCount,
|
|
16
|
+
sourceInputCount,
|
|
17
|
+
jobCount: jobs.length,
|
|
18
|
+
queued: 0,
|
|
19
|
+
pending: 0,
|
|
20
|
+
inProgress: 0,
|
|
21
|
+
completed: 0,
|
|
22
|
+
failed: 0,
|
|
23
|
+
timedOut,
|
|
24
|
+
};
|
|
25
|
+
for (const job of jobs) {
|
|
26
|
+
if (job.status === 'queued') {
|
|
27
|
+
summary.queued += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (job.status === 'pending') {
|
|
31
|
+
summary.pending += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (job.status === 'in_progress') {
|
|
35
|
+
summary.inProgress += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (job.status === 'completed') {
|
|
39
|
+
summary.completed += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
summary.failed += 1;
|
|
43
|
+
}
|
|
44
|
+
return summary;
|
|
45
|
+
};
|
|
46
|
+
export const formatCliImportSummary = (summary) => {
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (summary.queued > 0) {
|
|
49
|
+
parts.push(`${summary.queued} queued`);
|
|
50
|
+
}
|
|
51
|
+
if (summary.pending > 0) {
|
|
52
|
+
parts.push(`${summary.pending} pending`);
|
|
53
|
+
}
|
|
54
|
+
if (summary.inProgress > 0) {
|
|
55
|
+
parts.push(`${summary.inProgress} in progress`);
|
|
56
|
+
}
|
|
57
|
+
if (summary.completed > 0) {
|
|
58
|
+
parts.push(`${summary.completed} completed`);
|
|
59
|
+
}
|
|
60
|
+
if (summary.failed > 0) {
|
|
61
|
+
parts.push(`${summary.failed} failed`);
|
|
62
|
+
}
|
|
63
|
+
if (summary.timedOut) {
|
|
64
|
+
parts.push('timed out');
|
|
65
|
+
}
|
|
66
|
+
return `Summary: ${parts.join(', ') || '0 jobs'}.`;
|
|
67
|
+
};
|