@chalksurf/cli 0.2.2 → 0.2.4

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.
@@ -1,86 +0,0 @@
1
- const normalizeBaseUrl = (baseUrl) => {
2
- return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
3
- };
4
- const getErrorMessage = (payload, status) => {
5
- return payload?.error?.message ?? `API request failed with status ${status}`;
6
- };
7
- const getResponseData = async ({ response, batched }) => {
8
- const body = (await response.json());
9
- const payload = batched ? body[0] : body;
10
- if (!response.ok) {
11
- throw new Error(getErrorMessage(payload, response.status));
12
- }
13
- if (payload?.error) {
14
- throw new Error(payload.error.message);
15
- }
16
- if (!payload?.result) {
17
- throw new Error('API response is missing a result payload');
18
- }
19
- return payload.result.data;
20
- };
21
- export const createApiClient = ({ baseUrl, token, organizationId, }) => {
22
- const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
23
- const createHeaders = ({ includeJsonContentType }) => {
24
- const headers = new Headers();
25
- if (token) {
26
- headers.set('authorization', `Bearer ${token}`);
27
- }
28
- if (organizationId) {
29
- headers.set('x-organization-id', organizationId);
30
- }
31
- if (includeJsonContentType) {
32
- headers.set('content-type', 'application/json');
33
- }
34
- return headers;
35
- };
36
- const query = async (procedureName, input) => {
37
- const url = new URL(`trpc/${procedureName}`, normalizedBaseUrl);
38
- url.searchParams.set('batch', '1');
39
- url.searchParams.set('input', JSON.stringify({ 0: input ?? null }));
40
- const response = await fetch(url.toString(), {
41
- method: 'GET',
42
- headers: createHeaders({ includeJsonContentType: false }),
43
- });
44
- return await getResponseData({ response, batched: true });
45
- };
46
- const mutation = async (procedureName, input) => {
47
- const isFormDataInput = input instanceof FormData;
48
- const response = await fetch(new URL(`trpc/${procedureName}`, normalizedBaseUrl).toString(), {
49
- method: 'POST',
50
- headers: createHeaders({ includeJsonContentType: !isFormDataInput }),
51
- body: isFormDataInput ? input : JSON.stringify(input ?? {}),
52
- });
53
- return await getResponseData({ response, batched: false });
54
- };
55
- return {
56
- query,
57
- mutation,
58
- getUserProfile: async () => {
59
- return await query('getUserProfile', null);
60
- },
61
- listUserJobs: async (input) => {
62
- return await query('listUserJobs', input ?? null);
63
- },
64
- getUserJob: async (id) => {
65
- return await query('getUserJob', { id });
66
- },
67
- searchExercises: async (input) => {
68
- return await mutation('searchExercises', input);
69
- },
70
- searchExerciseSheets: async (input) => {
71
- return await mutation('searchExerciseSheets', input);
72
- },
73
- importExerciseSheet: async (formData) => {
74
- return await mutation('importExerciseSheet', formData);
75
- },
76
- importExercise: async (formData) => {
77
- return await mutation('importExercise', formData);
78
- },
79
- importExerciseSolution: async (formData) => {
80
- return await mutation('importExerciseSolution', formData);
81
- },
82
- importExerciseSheetSolutions: async (formData) => {
83
- return await mutation('importExerciseSheetSolutions', formData);
84
- },
85
- };
86
- };
@@ -1,46 +0,0 @@
1
- const cliErrorCodeByExitCode = {
2
- 1: 'unexpected_error',
3
- 2: 'usage_error',
4
- 3: 'not_authenticated',
5
- 4: 'source_resolution_failed',
6
- 5: 'api_error',
7
- 6: 'wait_timed_out',
8
- 7: 'job_failed',
9
- };
10
- const isRetryableCliErrorCode = (code) => {
11
- return ['api_error', 'wait_timed_out'].includes(code);
12
- };
13
- export const getCliErrorCode = (exitCode) => {
14
- return cliErrorCodeByExitCode[exitCode] ?? 'unexpected_error';
15
- };
16
- export const createSerializableCliError = ({ code, exitCode, message, retryable, }) => {
17
- const resolvedCode = code ?? getCliErrorCode(exitCode);
18
- return {
19
- code: resolvedCode,
20
- exitCode,
21
- message,
22
- retryable: retryable ?? isRetryableCliErrorCode(resolvedCode),
23
- };
24
- };
25
- export class CliCommandError extends Error {
26
- code;
27
- exitCode;
28
- retryable;
29
- shouldReport;
30
- constructor(message, exitCode, shouldReport = true, options = {}) {
31
- super(message);
32
- this.name = 'CliCommandError';
33
- this.code = options.code ?? getCliErrorCode(exitCode);
34
- this.exitCode = exitCode;
35
- this.retryable = options.retryable ?? isRetryableCliErrorCode(this.code);
36
- this.shouldReport = shouldReport;
37
- }
38
- }
39
- export const serializeCliError = (error) => {
40
- return createSerializableCliError({
41
- code: error.code,
42
- exitCode: error.exitCode,
43
- message: error.message,
44
- retryable: error.retryable,
45
- });
46
- };
@@ -1,61 +0,0 @@
1
- import { CliCommandError } from './cli-error.js';
2
- export const cliDefaultLimit = 20;
3
- export const cliMaxLimit = 100;
4
- const normalizeOptionalString = (value) => {
5
- const normalizedValue = value?.trim();
6
- return normalizedValue ? normalizedValue : undefined;
7
- };
8
- export const normalizeOptionalText = (value) => {
9
- return normalizeOptionalString(value) ?? null;
10
- };
11
- export const normalizeLimit = (value) => {
12
- const normalizedValue = value ?? cliDefaultLimit;
13
- if (!Number.isInteger(normalizedValue) || normalizedValue < 1 || normalizedValue > cliMaxLimit) {
14
- throw new CliCommandError(`--limit must be an integer between 1 and ${cliMaxLimit}.`, 2);
15
- }
16
- return normalizedValue;
17
- };
18
- export const normalizeOffset = (value) => {
19
- const normalizedValue = value ?? 0;
20
- if (!Number.isInteger(normalizedValue) || normalizedValue < 0) {
21
- throw new CliCommandError('--offset must be a non-negative integer.', 2);
22
- }
23
- return normalizedValue;
24
- };
25
- export const normalizeOptionalInteger = ({ label, value }) => {
26
- if (value === undefined) {
27
- return null;
28
- }
29
- if (!Number.isInteger(value)) {
30
- throw new CliCommandError(`${label} must be an integer.`, 2);
31
- }
32
- return value;
33
- };
34
- export const normalizeChoiceValues = ({ allowedValues, label, values, }) => {
35
- if (!values || values.length === 0) {
36
- return undefined;
37
- }
38
- const normalizedValues = values.map((value) => value.trim()).filter((value) => value.length > 0);
39
- const invalidValue = normalizedValues.find((value) => !allowedValues.includes(value));
40
- if (invalidValue) {
41
- throw new CliCommandError(`${label} must be one of: ${allowedValues.join(', ')}`, 2);
42
- }
43
- return Array.from(new Set(normalizedValues));
44
- };
45
- export const normalizeStringValues = (values) => {
46
- if (!values || values.length === 0) {
47
- return [];
48
- }
49
- return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
50
- };
51
- export const ownershipOptions = ['own', 'public', 'all'];
52
- export const normalizeOwnership = (value) => {
53
- const normalizedValue = normalizeOptionalString(value) ?? 'own';
54
- if (!ownershipOptions.includes(normalizedValue)) {
55
- throw new CliCommandError(`--ownership must be one of: ${ownershipOptions.join(', ')}`, 2);
56
- }
57
- return normalizedValue;
58
- };
59
- export const toApiOwnership = (ownership) => {
60
- return ownership === 'all' ? null : ownership;
61
- };
@@ -1,354 +0,0 @@
1
- import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
- import { homedir } from 'node:os';
3
- import { dirname, join } from 'node:path';
4
- import { CliCommandError } from './cli-error.js';
5
- export const chalksurfBaseUrlEnvVar = 'CHALKSURF_BASE_URL';
6
- export const chalksurfTokenEnvVar = 'CHALKSURF_TOKEN';
7
- export const chalksurfOrganizationIdEnvVar = 'CHALKSURF_ORGANIZATION_ID';
8
- export const chalksurfProfileEnvVar = 'CHALKSURF_PROFILE';
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._-]*$/;
13
- const normalizeOptionalString = (value) => {
14
- const trimmedValue = value?.trim();
15
- return trimmedValue ? trimmedValue : undefined;
16
- };
17
- const normalizeStoredBaseUrl = (value) => {
18
- const normalizedValue = normalizeOptionalString(value);
19
- return normalizedValue?.replace(/\/+$/, '') || undefined;
20
- };
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) => {
38
- return Object.fromEntries(Object.entries({
39
- token: normalizeOptionalString(config.token),
40
- baseUrl: normalizeStoredBaseUrl(config.baseUrl),
41
- organizationId: normalizeOptionalString(config.organizationId),
42
- }).filter(([, value]) => value !== undefined));
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;
106
- const getDefaultConfigPath = ({ env, platform, homeDirectory, }) => {
107
- const explicitConfigPath = normalizeOptionalString(env[chalksurfConfigPathEnvVar]);
108
- if (explicitConfigPath) {
109
- return explicitConfigPath;
110
- }
111
- if (platform === 'win32') {
112
- return join(env.APPDATA || join(homeDirectory, 'AppData', 'Roaming'), 'chalksurf', 'config.json');
113
- }
114
- if (platform === 'darwin') {
115
- return join(homeDirectory, 'Library', 'Application Support', 'chalksurf', 'config.json');
116
- }
117
- return join(env.XDG_CONFIG_HOME || join(homeDirectory, '.config'), 'chalksurf', 'config.json');
118
- };
119
- const findOrganizationMembership = ({ memberships, organizationId, }) => {
120
- return memberships.find((membership) => membership.organizationId === organizationId) ?? null;
121
- };
122
- const getFallbackOrganization = (memberships) => {
123
- const personalOrganization = memberships.find((membership) => membership.organizationType === 'user') ?? memberships[0] ?? null;
124
- if (!personalOrganization) {
125
- return null;
126
- }
127
- return {
128
- organization: personalOrganization,
129
- source: personalOrganization.organizationType === 'user' ? 'default-personal' : 'default-first',
130
- };
131
- };
132
- export const resolveBaseUrl = ({ flagValue, env, config, }) => {
133
- const normalizedFlagValue = normalizeStoredBaseUrl(flagValue);
134
- if (normalizedFlagValue) {
135
- return { value: normalizedFlagValue, source: 'flag' };
136
- }
137
- const normalizedEnvValue = normalizeStoredBaseUrl(env[chalksurfBaseUrlEnvVar]);
138
- if (normalizedEnvValue) {
139
- return { value: normalizedEnvValue, source: 'env' };
140
- }
141
- const normalizedConfigValue = normalizeStoredBaseUrl(config.baseUrl);
142
- if (normalizedConfigValue) {
143
- return { value: normalizedConfigValue, source: 'config' };
144
- }
145
- return { source: 'none' };
146
- };
147
- export const resolveToken = ({ env, config }) => {
148
- const normalizedEnvValue = normalizeOptionalString(env[chalksurfTokenEnvVar]);
149
- if (normalizedEnvValue) {
150
- return { value: normalizedEnvValue, source: 'env' };
151
- }
152
- const normalizedConfigValue = normalizeOptionalString(config.token);
153
- if (normalizedConfigValue) {
154
- return { value: normalizedConfigValue, source: 'config' };
155
- }
156
- return { source: 'none' };
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
- };
176
- export const resolveOrganization = ({ flagValue, env, config, profile, }) => {
177
- const flagOrganizationId = normalizeOptionalString(flagValue);
178
- if (flagOrganizationId) {
179
- const organization = findOrganizationMembership({
180
- memberships: profile.organizationMemberships,
181
- organizationId: flagOrganizationId,
182
- });
183
- if (!organization) {
184
- throw new CliCommandError(`Organization "${flagOrganizationId}" is not accessible to the current user.`, 2);
185
- }
186
- return { organization, source: 'flag', warnings: [] };
187
- }
188
- const envOrganizationId = normalizeOptionalString(env[chalksurfOrganizationIdEnvVar]);
189
- if (envOrganizationId) {
190
- const organization = findOrganizationMembership({
191
- memberships: profile.organizationMemberships,
192
- organizationId: envOrganizationId,
193
- });
194
- if (!organization) {
195
- throw new CliCommandError(`Organization "${envOrganizationId}" from ${chalksurfOrganizationIdEnvVar} is not accessible to the current user.`, 2);
196
- }
197
- return { organization, source: 'env', warnings: [] };
198
- }
199
- const configOrganizationId = normalizeOptionalString(config.organizationId);
200
- if (configOrganizationId) {
201
- const organization = findOrganizationMembership({
202
- memberships: profile.organizationMemberships,
203
- organizationId: configOrganizationId,
204
- });
205
- if (organization) {
206
- return { organization, source: 'config', warnings: [] };
207
- }
208
- const fallbackOrganization = getFallbackOrganization(profile.organizationMemberships);
209
- if (!fallbackOrganization) {
210
- return {
211
- organization: null,
212
- source: 'none',
213
- warnings: [`Stored organization "${configOrganizationId}" is no longer accessible.`],
214
- };
215
- }
216
- return {
217
- organization: fallbackOrganization.organization,
218
- source: fallbackOrganization.source,
219
- warnings: [
220
- `Stored organization "${configOrganizationId}" is no longer accessible; using "${fallbackOrganization.organization.organizationName}" instead.`,
221
- ],
222
- };
223
- }
224
- const fallbackOrganization = getFallbackOrganization(profile.organizationMemberships);
225
- if (!fallbackOrganization) {
226
- return { organization: null, source: 'none', warnings: [] };
227
- }
228
- return {
229
- organization: fallbackOrganization.organization,
230
- source: fallbackOrganization.source,
231
- warnings: [],
232
- };
233
- };
234
- export const createConfigStore = ({ configPath, env = process.env, platform = process.platform, homeDirectory = homedir(), fs = { chmod, mkdir, readFile, rm, writeFile }, } = {}) => {
235
- const resolvedConfigPath = configPath || getDefaultConfigPath({ env, platform, homeDirectory });
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 () => {
246
- try {
247
- const configContents = await fs.readFile(resolvedConfigPath, 'utf8');
248
- const parsedConfig = JSON.parse(configContents);
249
- return readRootConfig(parsedConfig);
250
- }
251
- catch (error) {
252
- if (error.code === 'ENOENT') {
253
- return { schemaVersion: cliConfigSchemaVersion, profiles: {} };
254
- }
255
- if (error instanceof SyntaxError) {
256
- throw new CliCommandError(`Failed to parse CLI config at ${resolvedConfigPath}.`, 2);
257
- }
258
- throw error;
259
- }
260
- };
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)) {
273
- await fs.rm(resolvedConfigPath, { force: true });
274
- return sanitizedConfig;
275
- }
276
- await fs.mkdir(dirname(resolvedConfigPath), { recursive: true, mode: 0o700 });
277
- await fs.writeFile(resolvedConfigPath, `${JSON.stringify(sanitizedConfig, null, 2)}\n`, {
278
- mode: 0o600,
279
- });
280
- await fs.chmod(resolvedConfigPath, 0o600);
281
- return sanitizedConfig;
282
- };
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
- });
342
- };
343
- return {
344
- path: resolvedConfigPath,
345
- load,
346
- loadProfile,
347
- loadRoot,
348
- save,
349
- saveRoot,
350
- update,
351
- setDefaultProfile,
352
- deleteProfile,
353
- };
354
- };
@@ -1,120 +0,0 @@
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
- };