@dittowords/cli 3.10.1 → 4.0.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 +125 -36
- package/bin/add-project.js +101 -33
- package/bin/add-project.js.map +1 -1
- package/bin/api.js +49 -15
- package/bin/api.js.map +1 -1
- package/bin/component-folders.js +56 -7
- package/bin/component-folders.js.map +1 -1
- package/bin/config.js +206 -170
- package/bin/config.js.map +1 -1
- package/bin/config.test.js +92 -0
- package/bin/config.test.js.map +1 -0
- package/bin/consts.js +64 -18
- package/bin/consts.js.map +1 -1
- package/bin/ditto.js +250 -200
- package/bin/ditto.js.map +1 -1
- package/bin/generate-suggestions.js +155 -78
- package/bin/generate-suggestions.js.map +1 -1
- package/bin/generate-suggestions.test.js +189 -0
- package/bin/generate-suggestions.test.js.map +1 -0
- package/bin/http/fetchComponentFolders.js +60 -8
- package/bin/http/fetchComponentFolders.js.map +1 -1
- package/bin/http/fetchComponents.js +65 -18
- package/bin/http/fetchComponents.js.map +1 -1
- package/bin/http/fetchVariants.js +74 -14
- package/bin/http/fetchVariants.js.map +1 -1
- package/bin/http/importComponents.js +100 -49
- package/bin/http/importComponents.js.map +1 -1
- package/bin/importComponents.js +61 -10
- package/bin/importComponents.js.map +1 -1
- package/bin/init/init.js +120 -44
- package/bin/init/init.js.map +1 -1
- package/bin/init/project.js +160 -83
- package/bin/init/project.js.map +1 -1
- package/bin/init/project.test.js +49 -0
- package/bin/init/project.test.js.map +1 -0
- package/bin/init/token.js +134 -74
- package/bin/init/token.js.map +1 -1
- package/bin/init/token.test.js +69 -0
- package/bin/init/token.test.js.map +1 -0
- package/bin/output.js +72 -30
- package/bin/output.js.map +1 -1
- package/bin/pull.js +424 -213
- package/bin/pull.js.map +1 -1
- package/bin/pull.test.js +410 -0
- package/bin/pull.test.js.map +1 -0
- package/bin/remove-project.js +91 -27
- package/bin/remove-project.js.map +1 -1
- package/bin/replace.js +140 -100
- package/bin/replace.js.map +1 -1
- package/bin/replace.test.js +155 -0
- package/bin/replace.test.js.map +1 -0
- package/bin/sentry-test.js.map +1 -0
- package/bin/types.js +20 -2
- package/bin/types.js.map +1 -1
- package/bin/utils/cleanFileName.js +32 -8
- package/bin/utils/cleanFileName.js.map +1 -1
- package/bin/utils/createSentryContext.js +43 -0
- package/bin/utils/createSentryContext.js.map +1 -0
- package/bin/utils/generateJsDriver.js +114 -51
- package/bin/utils/generateJsDriver.js.map +1 -1
- package/bin/utils/getSelectedProjects.js +58 -52
- package/bin/utils/getSelectedProjects.js.map +1 -1
- package/bin/utils/processMetaOption.js +36 -11
- package/bin/utils/processMetaOption.js.map +1 -1
- package/bin/utils/processMetaOption.test.js +45 -0
- package/bin/utils/processMetaOption.test.js.map +1 -0
- package/bin/utils/projectsToText.js +52 -19
- package/bin/utils/projectsToText.js.map +1 -1
- package/bin/utils/promptForProject.js +89 -36
- package/bin/utils/promptForProject.js.map +1 -1
- package/bin/utils/quit.js +36 -7
- package/bin/utils/quit.js.map +1 -1
- package/bin/utils/sourcesToText.js +51 -19
- package/bin/utils/sourcesToText.js.map +1 -1
- package/etsc.config.js +13 -0
- package/lib/config.ts +27 -8
- package/lib/ditto.ts +6 -0
- package/lib/init/project.ts +3 -3
- package/lib/pull.ts +190 -52
- package/lib/types.ts +24 -7
- package/lib/utils/createSentryContext.ts +20 -0
- package/lib/utils/generateJsDriver.ts +40 -6
- package/lib/utils/quit.ts +2 -3
- package/package.json +10 -6
- package/tsconfig.json +4 -1
- package/bin/lib/add-project.js +0 -36
- package/bin/lib/add-project.js.map +0 -1
- package/bin/lib/api.js +0 -20
- package/bin/lib/api.js.map +0 -1
- package/bin/lib/config.js +0 -202
- package/bin/lib/config.js.map +0 -1
- package/bin/lib/consts.js +0 -21
- package/bin/lib/consts.js.map +0 -1
- package/bin/lib/ditto.js +0 -121
- package/bin/lib/ditto.js.map +0 -1
- package/bin/lib/generate-suggestions.js +0 -71
- package/bin/lib/generate-suggestions.js.map +0 -1
- package/bin/lib/http/fetchComponents.js +0 -13
- package/bin/lib/http/fetchComponents.js.map +0 -1
- package/bin/lib/http/fetchVariants.js +0 -26
- package/bin/lib/http/fetchVariants.js.map +0 -1
- package/bin/lib/init/init.js +0 -50
- package/bin/lib/init/init.js.map +0 -1
- package/bin/lib/init/project.js +0 -108
- package/bin/lib/init/project.js.map +0 -1
- package/bin/lib/init/token.js +0 -91
- package/bin/lib/init/token.js.map +0 -1
- package/bin/lib/output.js +0 -34
- package/bin/lib/output.js.map +0 -1
- package/bin/lib/pull.js +0 -264
- package/bin/lib/pull.js.map +0 -1
- package/bin/lib/remove-project.js +0 -35
- package/bin/lib/remove-project.js.map +0 -1
- package/bin/lib/replace.js +0 -107
- package/bin/lib/replace.js.map +0 -1
- package/bin/lib/types.js +0 -3
- package/bin/lib/types.js.map +0 -1
- package/bin/lib/utils/cleanFileName.js +0 -11
- package/bin/lib/utils/cleanFileName.js.map +0 -1
- package/bin/lib/utils/generateJsDriver.js +0 -56
- package/bin/lib/utils/generateJsDriver.js.map +0 -1
- package/bin/lib/utils/getSelectedProjects.js +0 -61
- package/bin/lib/utils/getSelectedProjects.js.map +0 -1
- package/bin/lib/utils/processMetaOption.js +0 -15
- package/bin/lib/utils/processMetaOption.js.map +0 -1
- package/bin/lib/utils/projectsToText.js +0 -25
- package/bin/lib/utils/projectsToText.js.map +0 -1
- package/bin/lib/utils/promptForProject.js +0 -43
- package/bin/lib/utils/promptForProject.js.map +0 -1
- package/bin/lib/utils/quit.js +0 -10
- package/bin/lib/utils/quit.js.map +0 -1
- package/bin/lib/utils/sourcesToText.js +0 -25
- package/bin/lib/utils/sourcesToText.js.map +0 -1
- package/bin/package.json +0 -76
package/lib/pull.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
4
|
import ora from "ora";
|
|
5
|
+
import * as Sentry from "@sentry/node";
|
|
5
6
|
|
|
6
7
|
import { createApiClient } from "./api";
|
|
7
8
|
import config from "./config";
|
|
@@ -11,9 +12,25 @@ import { collectAndSaveToken } from "./init/token";
|
|
|
11
12
|
import sourcesToText from "./utils/sourcesToText";
|
|
12
13
|
import { generateJsDriver } from "./utils/generateJsDriver";
|
|
13
14
|
import { cleanFileName } from "./utils/cleanFileName";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
SourceInformation,
|
|
17
|
+
Token,
|
|
18
|
+
Project,
|
|
19
|
+
SupportedFormat,
|
|
20
|
+
ComponentFolder,
|
|
21
|
+
ComponentSource,
|
|
22
|
+
Source,
|
|
23
|
+
} from "./types";
|
|
15
24
|
import { fetchVariants } from "./http/fetchVariants";
|
|
16
|
-
import {
|
|
25
|
+
import { quit } from "./utils/quit";
|
|
26
|
+
import { AxiosError } from "axios";
|
|
27
|
+
import { fetchComponentFolders } from "./http/fetchComponentFolders";
|
|
28
|
+
|
|
29
|
+
const ensureEndsWithNewLine = (str: string) =>
|
|
30
|
+
str + (/[\r\n]$/.test(str) ? "" : "\n");
|
|
31
|
+
|
|
32
|
+
const writeFile = (path: string, data: string) =>
|
|
33
|
+
new Promise((r) => fs.writeFile(path, ensureEndsWithNewLine(data), r));
|
|
17
34
|
|
|
18
35
|
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
19
36
|
"flat",
|
|
@@ -111,13 +128,21 @@ async function downloadAndSaveVariant(
|
|
|
111
128
|
const api = createApiClient();
|
|
112
129
|
const params: Record<string, string | null> = { variant: variantApiId };
|
|
113
130
|
if (format) params.format = format;
|
|
114
|
-
if (status) params.status = status;
|
|
115
131
|
if (richText) params.includeRichText = richText.toString();
|
|
116
132
|
|
|
133
|
+
// Root-level status gets set as the default if specified
|
|
134
|
+
if (status) params.status = status;
|
|
135
|
+
|
|
117
136
|
const savedMessages = await Promise.all(
|
|
118
|
-
projects.map(async (
|
|
119
|
-
const
|
|
120
|
-
|
|
137
|
+
projects.map(async (project) => {
|
|
138
|
+
const projectParams = { ...params };
|
|
139
|
+
// If project-level status is specified, overrides root-level status
|
|
140
|
+
if (project.status) projectParams.status = project.status;
|
|
141
|
+
if (project.exclude_components)
|
|
142
|
+
projectParams.exclude_components = String(project.exclude_components);
|
|
143
|
+
|
|
144
|
+
const { data } = await api.get(`/projects/${project.id}`, {
|
|
145
|
+
params: projectParams,
|
|
121
146
|
headers: { Authorization: `token ${token}` },
|
|
122
147
|
});
|
|
123
148
|
|
|
@@ -128,7 +153,7 @@ async function downloadAndSaveVariant(
|
|
|
128
153
|
const extension = getFormatExtension(format);
|
|
129
154
|
|
|
130
155
|
const filename = cleanFileName(
|
|
131
|
-
fileName + ("__" + (variantApiId || "base")) + extension
|
|
156
|
+
project.fileName + ("__" + (variantApiId || "base")) + extension
|
|
132
157
|
);
|
|
133
158
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
134
159
|
|
|
@@ -142,7 +167,7 @@ async function downloadAndSaveVariant(
|
|
|
142
167
|
return "";
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
|
|
170
|
+
await writeFile(filepath, dataString);
|
|
146
171
|
return getSavedMessage(filename);
|
|
147
172
|
})
|
|
148
173
|
);
|
|
@@ -179,18 +204,26 @@ async function downloadAndSaveBase(
|
|
|
179
204
|
const api = createApiClient();
|
|
180
205
|
const params = { ...options?.meta };
|
|
181
206
|
if (format) params.format = format;
|
|
182
|
-
if (status) params.status = status;
|
|
183
207
|
if (richText) params.includeRichText = richText.toString();
|
|
184
208
|
|
|
209
|
+
// Root-level status gets set as the default if specified
|
|
210
|
+
if (status) params.status = status;
|
|
211
|
+
|
|
185
212
|
const savedMessages = await Promise.all(
|
|
186
|
-
projects.map(async (
|
|
187
|
-
const
|
|
188
|
-
|
|
213
|
+
projects.map(async (project) => {
|
|
214
|
+
const projectParams = { ...params };
|
|
215
|
+
// If project-level status is specified, overrides root-level status
|
|
216
|
+
if (project.status) projectParams.status = project.status;
|
|
217
|
+
if (project.exclude_components)
|
|
218
|
+
projectParams.exclude_components = String(project.exclude_components);
|
|
219
|
+
|
|
220
|
+
const { data } = await api.get(`/projects/${project.id}`, {
|
|
221
|
+
params: projectParams,
|
|
189
222
|
headers: { Authorization: `token ${token}` },
|
|
190
223
|
});
|
|
191
224
|
|
|
192
225
|
const extension = getFormatExtension(format);
|
|
193
|
-
const filename = cleanFileName(`${fileName}__base${extension}`);
|
|
226
|
+
const filename = cleanFileName(`${project.fileName}__base${extension}`);
|
|
194
227
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
195
228
|
|
|
196
229
|
let dataString = data;
|
|
@@ -203,7 +236,7 @@ async function downloadAndSaveBase(
|
|
|
203
236
|
return "";
|
|
204
237
|
}
|
|
205
238
|
|
|
206
|
-
|
|
239
|
+
await writeFile(filepath, dataString);
|
|
207
240
|
return getSavedMessage(filename);
|
|
208
241
|
})
|
|
209
242
|
);
|
|
@@ -242,7 +275,8 @@ async function downloadAndSave(
|
|
|
242
275
|
shouldFetchComponentLibrary,
|
|
243
276
|
status,
|
|
244
277
|
richText,
|
|
245
|
-
componentFolders,
|
|
278
|
+
componentFolders: specifiedComponentFolders,
|
|
279
|
+
componentRoot,
|
|
246
280
|
} = source;
|
|
247
281
|
|
|
248
282
|
const formats = getFormat(formatFromSource);
|
|
@@ -251,7 +285,17 @@ async function downloadAndSave(
|
|
|
251
285
|
const spinner = ora(msg);
|
|
252
286
|
spinner.start();
|
|
253
287
|
|
|
254
|
-
const variants = await
|
|
288
|
+
const [variants, allComponentFoldersResponse] = await Promise.all([
|
|
289
|
+
fetchVariants(source),
|
|
290
|
+
fetchComponentFolders(),
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
const allComponentFolders = Object.entries(
|
|
294
|
+
allComponentFoldersResponse
|
|
295
|
+
).reduce(
|
|
296
|
+
(acc, [id, name]) => acc.concat([{ id, name }]),
|
|
297
|
+
[] as ComponentFolder[]
|
|
298
|
+
);
|
|
255
299
|
|
|
256
300
|
try {
|
|
257
301
|
msg += cleanOutputFiles();
|
|
@@ -262,6 +306,60 @@ async function downloadAndSave(
|
|
|
262
306
|
|
|
263
307
|
const meta = options ? options.meta : {};
|
|
264
308
|
|
|
309
|
+
const rootRequest = {
|
|
310
|
+
id: "__root__",
|
|
311
|
+
name: "Root",
|
|
312
|
+
// componentRoot can be a boolean or an object
|
|
313
|
+
status:
|
|
314
|
+
typeof source.componentRoot === "object"
|
|
315
|
+
? source.componentRoot.status
|
|
316
|
+
: undefined,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
let componentFolderRequests: ComponentFolder[] = [];
|
|
320
|
+
|
|
321
|
+
// there's a lot of complex logic here, and it's tempting to want to
|
|
322
|
+
// simplify it. however, it's difficult to get rid of the complexity
|
|
323
|
+
// without sacrificing specificity and expressiveness.
|
|
324
|
+
//
|
|
325
|
+
// if folders specified..
|
|
326
|
+
if (specifiedComponentFolders) {
|
|
327
|
+
switch (componentRoot) {
|
|
328
|
+
// .. and no root specified, you only get components in the specified folders
|
|
329
|
+
case undefined:
|
|
330
|
+
case false:
|
|
331
|
+
componentFolderRequests.push(...specifiedComponentFolders);
|
|
332
|
+
break;
|
|
333
|
+
// .. and root specified, you get components in folders and the root
|
|
334
|
+
default:
|
|
335
|
+
componentFolderRequests.push(...specifiedComponentFolders);
|
|
336
|
+
componentFolderRequests.push(rootRequest);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// if no folders specified..
|
|
341
|
+
else {
|
|
342
|
+
switch (componentRoot) {
|
|
343
|
+
// .. and no root specified, you get all components including those in folders
|
|
344
|
+
case undefined:
|
|
345
|
+
componentFolderRequests.push(...allComponentFolders);
|
|
346
|
+
componentFolderRequests.push(rootRequest);
|
|
347
|
+
break;
|
|
348
|
+
// .. and root specified as false, you only get components in folders
|
|
349
|
+
case false:
|
|
350
|
+
componentFolderRequests.push(...allComponentFolders);
|
|
351
|
+
break;
|
|
352
|
+
// .. and root specified as true or config object, you only get components in the root
|
|
353
|
+
default:
|
|
354
|
+
componentFolderRequests.push(rootRequest);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// this array is populated while fetching from the component library and is used when
|
|
360
|
+
// generating the index.js driver file
|
|
361
|
+
const componentSources: ComponentSource[] = [];
|
|
362
|
+
|
|
265
363
|
async function fetchComponentLibrary(format: SupportedFormat) {
|
|
266
364
|
// Always include a variant with an apiID of undefined to ensure that we
|
|
267
365
|
// fetch the base text for the component library.
|
|
@@ -271,41 +369,70 @@ async function downloadAndSave(
|
|
|
271
369
|
if (options?.meta)
|
|
272
370
|
Object.entries(options.meta).forEach(([k, v]) => params.append(k, v));
|
|
273
371
|
if (format) params.append("format", format);
|
|
274
|
-
if (status) params.append("status", status);
|
|
275
372
|
if (richText) params.append("includeRichText", richText.toString());
|
|
276
|
-
if (componentFolders) {
|
|
277
|
-
componentFolders.forEach(({ id }) => params.append("folder_id[]", id));
|
|
278
|
-
}
|
|
279
373
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
374
|
+
// Root-level status gets set as the default if specified
|
|
375
|
+
if (status) params.append("status", status);
|
|
376
|
+
|
|
377
|
+
const messagePromises: Promise<string>[] = [];
|
|
284
378
|
|
|
285
|
-
|
|
379
|
+
componentVariants.forEach(({ apiID: variantApiId }) => {
|
|
380
|
+
messagePromises.push(
|
|
381
|
+
...componentFolderRequests.map(async (componentFolder) => {
|
|
382
|
+
const componentFolderParams = new URLSearchParams(params);
|
|
286
383
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const namePostfix = `__${variantApiId || "base"}`;
|
|
384
|
+
if (variantApiId)
|
|
385
|
+
componentFolderParams.append("variant", variantApiId);
|
|
290
386
|
|
|
291
|
-
|
|
292
|
-
|
|
387
|
+
// If folder-level status is specified, overrides root-level status
|
|
388
|
+
if (componentFolder.status)
|
|
389
|
+
componentFolderParams.append("status", componentFolder.status);
|
|
293
390
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
391
|
+
const url =
|
|
392
|
+
componentFolder.id === "__root__"
|
|
393
|
+
? "/components?root_only=true"
|
|
394
|
+
: `/component-folders/${componentFolder.id}/components`;
|
|
298
395
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
396
|
+
const { data } = await api.get(url, {
|
|
397
|
+
params: componentFolderParams,
|
|
398
|
+
});
|
|
303
399
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
400
|
+
const nameExt = getFormatExtension(format);
|
|
401
|
+
const nameBase = "components";
|
|
402
|
+
const nameFolder = `__${componentFolder.name}`;
|
|
403
|
+
const namePostfix = `__${variantApiId || "base"}`;
|
|
404
|
+
|
|
405
|
+
const fileName = cleanFileName(
|
|
406
|
+
`${nameBase}${nameFolder}${namePostfix}${nameExt}`
|
|
407
|
+
);
|
|
408
|
+
const filePath = path.join(consts.TEXT_DIR, fileName);
|
|
409
|
+
|
|
410
|
+
let dataString = data;
|
|
411
|
+
if (nameExt === ".json") {
|
|
412
|
+
dataString = JSON.stringify(data, null, 2);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
416
|
+
if (!dataIsValid(dataString)) {
|
|
417
|
+
return "";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
await writeFile(filePath, dataString);
|
|
421
|
+
|
|
422
|
+
componentSources.push({
|
|
423
|
+
type: "components",
|
|
424
|
+
id: "ditto_component_library",
|
|
425
|
+
name: "ditto_component_library",
|
|
426
|
+
fileName,
|
|
427
|
+
variant: variantApiId || "base",
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return getSavedMessage(fileName);
|
|
431
|
+
})
|
|
432
|
+
);
|
|
433
|
+
});
|
|
308
434
|
|
|
435
|
+
const messages = await Promise.all(messagePromises);
|
|
309
436
|
msg += messages.join("");
|
|
310
437
|
}
|
|
311
438
|
|
|
@@ -343,14 +470,7 @@ async function downloadAndSave(
|
|
|
343
470
|
}
|
|
344
471
|
}
|
|
345
472
|
|
|
346
|
-
const sources = [...validProjects];
|
|
347
|
-
if (shouldFetchComponentLibrary) {
|
|
348
|
-
sources.push({
|
|
349
|
-
id: "ditto_component_library",
|
|
350
|
-
name: "Ditto Component Library",
|
|
351
|
-
fileName: "ditto-component-library",
|
|
352
|
-
});
|
|
353
|
-
}
|
|
473
|
+
const sources: Source[] = [...validProjects, ...componentSources];
|
|
354
474
|
|
|
355
475
|
if (formats.some((f) => JSON_FORMATS.includes(f)))
|
|
356
476
|
msg += generateJsDriver(sources);
|
|
@@ -402,12 +522,30 @@ export interface PullOptions {
|
|
|
402
522
|
meta?: Record<string, string>;
|
|
403
523
|
}
|
|
404
524
|
|
|
405
|
-
export const pull = (options?: PullOptions) => {
|
|
525
|
+
export const pull = async (options?: PullOptions) => {
|
|
406
526
|
const meta = options ? options.meta : {};
|
|
407
527
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
408
528
|
const sourceInformation = config.parseSourceInformation();
|
|
409
529
|
|
|
410
|
-
|
|
530
|
+
try {
|
|
531
|
+
return await downloadAndSave(sourceInformation, token, { meta });
|
|
532
|
+
} catch (e) {
|
|
533
|
+
const eventId = Sentry.captureException(e);
|
|
534
|
+
const eventStr = `\n\nError ID: ${output.info(eventId)}`;
|
|
535
|
+
if (e instanceof AxiosError) {
|
|
536
|
+
return quit(
|
|
537
|
+
output.errorText(
|
|
538
|
+
"Something went wrong connecting to Ditto servers. Please contact support or try again later."
|
|
539
|
+
) + eventStr
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return quit(
|
|
544
|
+
output.errorText(
|
|
545
|
+
"Something went wrong. Please contact support or try again later."
|
|
546
|
+
) + eventStr
|
|
547
|
+
);
|
|
548
|
+
}
|
|
411
549
|
};
|
|
412
550
|
|
|
413
551
|
export default {
|
package/lib/types.ts
CHANGED
|
@@ -3,13 +3,22 @@ export interface Project {
|
|
|
3
3
|
id: string;
|
|
4
4
|
url?: string;
|
|
5
5
|
fileName?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
exclude_components?: boolean;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
export type
|
|
10
|
+
export type ComponentSource = ComponentFolder & {
|
|
11
|
+
type: "components";
|
|
12
|
+
fileName: string;
|
|
13
|
+
variant: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type Source = (Project & { type?: undefined }) | ComponentSource;
|
|
9
17
|
|
|
10
|
-
interface ComponentFolder {
|
|
18
|
+
export interface ComponentFolder {
|
|
11
19
|
id: string;
|
|
12
20
|
name: string;
|
|
21
|
+
status?: string;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
export type SupportedFormat =
|
|
@@ -20,12 +29,16 @@ export type SupportedFormat =
|
|
|
20
29
|
| "ios-stringsdict"
|
|
21
30
|
| "icu";
|
|
22
31
|
|
|
32
|
+
type ComponentsSourceBool = boolean;
|
|
33
|
+
type ComponentsSourceConfig = {
|
|
34
|
+
root?: boolean | { status: string };
|
|
35
|
+
folders?: ComponentFolder[];
|
|
36
|
+
};
|
|
37
|
+
type ComponentsSource = ComponentsSourceBool | ComponentsSourceConfig;
|
|
38
|
+
|
|
23
39
|
export interface ConfigYAML {
|
|
24
40
|
sources?: {
|
|
25
|
-
components?:
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
folders?: ComponentFolder[];
|
|
28
|
-
};
|
|
41
|
+
components?: ComponentsSource;
|
|
29
42
|
projects?: Project[];
|
|
30
43
|
};
|
|
31
44
|
format?: SupportedFormat;
|
|
@@ -42,13 +55,17 @@ export interface ConfigYAML {
|
|
|
42
55
|
|
|
43
56
|
export interface SourceInformation {
|
|
44
57
|
hasSourceData: boolean;
|
|
58
|
+
hasTopLevelProjectsField: boolean;
|
|
59
|
+
hasTopLevelComponentsField: boolean;
|
|
60
|
+
hasComponentLibraryInProjects: boolean;
|
|
45
61
|
validProjects: Project[];
|
|
46
62
|
shouldFetchComponentLibrary: boolean;
|
|
47
63
|
variants: boolean;
|
|
48
64
|
format: string | string[] | undefined;
|
|
49
65
|
status: string | undefined;
|
|
50
66
|
richText: boolean | undefined;
|
|
51
|
-
|
|
67
|
+
componentRoot: boolean | { status: string } | undefined;
|
|
68
|
+
componentFolders: ComponentFolder[] | undefined;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
export type Token = string | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type SentryContext = Record<
|
|
2
|
+
string,
|
|
3
|
+
string | number | boolean | null | undefined
|
|
4
|
+
>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sentry context only supports properties one layer deep
|
|
8
|
+
*/
|
|
9
|
+
export function createSentryContext(obj: unknown) {
|
|
10
|
+
if (typeof obj !== "object") return {};
|
|
11
|
+
|
|
12
|
+
const ctx: SentryContext = {};
|
|
13
|
+
for (const key in obj) {
|
|
14
|
+
const k = key as keyof typeof obj;
|
|
15
|
+
const r = obj[k];
|
|
16
|
+
ctx[k] = typeof r === "object" || Array.isArray(r) ? JSON.stringify(r) : r;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
@@ -24,10 +24,6 @@ const stringifySourceId = (projectId: string) =>
|
|
|
24
24
|
|
|
25
25
|
// TODO: support ESM
|
|
26
26
|
export function generateJsDriver(sources: Source[]) {
|
|
27
|
-
const fileNames = fs
|
|
28
|
-
.readdirSync(consts.TEXT_DIR)
|
|
29
|
-
.filter((fileName) => /\.json$/.test(fileName));
|
|
30
|
-
|
|
31
27
|
const sourceIdsByName: Record<string, string> = sources.reduce(
|
|
32
28
|
(agg, source) => {
|
|
33
29
|
if (source.fileName) {
|
|
@@ -39,7 +35,14 @@ export function generateJsDriver(sources: Source[]) {
|
|
|
39
35
|
{}
|
|
40
36
|
);
|
|
41
37
|
|
|
42
|
-
const
|
|
38
|
+
const projectFileNames = fs
|
|
39
|
+
.readdirSync(consts.TEXT_DIR)
|
|
40
|
+
.filter(
|
|
41
|
+
(fileName) => /\.json$/.test(fileName) && !/^components__/.test(fileName)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
type DriverFile = Record<string, Record<string, string | object>>;
|
|
45
|
+
const data: DriverFile = projectFileNames.reduce(
|
|
43
46
|
(obj: Record<string, Record<string, string>>, fileName) => {
|
|
44
47
|
const [sourceId, rest] = fileName.split("__");
|
|
45
48
|
const [variantApiId] = rest.split(".");
|
|
@@ -57,9 +60,40 @@ export function generateJsDriver(sources: Source[]) {
|
|
|
57
60
|
{}
|
|
58
61
|
);
|
|
59
62
|
|
|
63
|
+
// Create arrays of stringified "...require()" statements,
|
|
64
|
+
// each of which corresponds to one of the component files
|
|
65
|
+
// (which are created on a per-component-folder basis)
|
|
66
|
+
const componentData: Record<string, string[]> = {};
|
|
67
|
+
sources
|
|
68
|
+
.filter((s) => s.type === "components")
|
|
69
|
+
.forEach((componentSource) => {
|
|
70
|
+
if (componentSource.type !== "components") return;
|
|
71
|
+
componentData[componentSource.variant] ??= [];
|
|
72
|
+
componentData[componentSource.variant].push(
|
|
73
|
+
`...require('./${componentSource.fileName}')`
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
// Convert each array of stringified "...require()" statements
|
|
77
|
+
// into a unified string, and set it on the final data object
|
|
78
|
+
// that will be written to the driver file
|
|
79
|
+
Object.keys(componentData).forEach((key) => {
|
|
80
|
+
data.ditto_component_library ??= {};
|
|
81
|
+
|
|
82
|
+
let str = "{";
|
|
83
|
+
componentData[key].forEach((k, i) => {
|
|
84
|
+
str += k;
|
|
85
|
+
if (i < componentData[key].length - 1) str += ", ";
|
|
86
|
+
});
|
|
87
|
+
str += "}";
|
|
88
|
+
data.ditto_component_library[key] = str;
|
|
89
|
+
});
|
|
90
|
+
|
|
60
91
|
let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
|
|
61
92
|
// remove quotes around require statements
|
|
62
|
-
.replace(/"require\((.*)\)"/g, "require($1)")
|
|
93
|
+
.replace(/"require\((.*)\)"/g, "require($1)")
|
|
94
|
+
// remove quotes around opening & closing curlies
|
|
95
|
+
.replace(/"\{/g, "{")
|
|
96
|
+
.replace(/\}"/g, "}");
|
|
63
97
|
|
|
64
98
|
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
65
99
|
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
package/lib/utils/quit.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dittowords/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Command Line Interface for Ditto (dittowords.com).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "bin/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
8
|
+
"prepublishOnly": "ENV=production etsc && sentry-cli sourcemaps inject ./bin && npx sentry-cli sourcemaps upload ./bin --release=\"$(cat package.json | jq -r '.version')\"",
|
|
9
9
|
"prepare": "husky install",
|
|
10
|
-
"start": "
|
|
11
|
-
"sync": "
|
|
12
|
-
"dev": "
|
|
10
|
+
"start": "etsc && node bin/ditto.js",
|
|
11
|
+
"sync": "etsc && node bin/ditto.js pull",
|
|
12
|
+
"dev": "etsc --watch"
|
|
13
13
|
},
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"url": "https://github.com/dittowords/cli/issues"
|
|
20
20
|
},
|
|
21
21
|
"author": "Ditto Tech Inc.",
|
|
22
|
-
"license": "MIT",
|
|
23
22
|
"keywords": [
|
|
24
23
|
"ditto",
|
|
25
24
|
"dittowords",
|
|
@@ -36,11 +35,15 @@
|
|
|
36
35
|
"devDependencies": {
|
|
37
36
|
"@babel/preset-env": "^7.20.2",
|
|
38
37
|
"@babel/preset-typescript": "^7.18.6",
|
|
38
|
+
"@sentry/cli": "^2.20.5",
|
|
39
39
|
"@tsconfig/node16": "^1.0.3",
|
|
40
40
|
"@types/jest": "^26.0.9",
|
|
41
41
|
"@types/js-yaml": "^4.0.5",
|
|
42
42
|
"@types/node": "^18.0.0",
|
|
43
43
|
"babel-jest": "^29.3.1",
|
|
44
|
+
"dotenv": "^16.3.1",
|
|
45
|
+
"esbuild": "^0.19.2",
|
|
46
|
+
"esbuild-node-tsc": "^2.0.5",
|
|
44
47
|
"husky": "^7.0.4",
|
|
45
48
|
"jest": "^29.3.1",
|
|
46
49
|
"lint-staged": "^11.2.4",
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
"@babel/parser": "^7.21.4",
|
|
56
59
|
"@babel/traverse": "^7.21.4",
|
|
57
60
|
"@babel/types": "^7.21.4",
|
|
61
|
+
"@sentry/node": "^7.64.0",
|
|
58
62
|
"@types/babel-traverse": "^6.25.7",
|
|
59
63
|
"@types/fs-extra": "^11.0.1",
|
|
60
64
|
"axios": "^0.27.2",
|
package/tsconfig.json
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
"resolveJsonModule": true,
|
|
8
8
|
"strictNullChecks": true,
|
|
9
9
|
"sourceMap": true,
|
|
10
|
+
// Set `sourceRoot` to "/" to strip the build path prefix from
|
|
11
|
+
// generated source code references. This will improve issue grouping in Sentry.
|
|
12
|
+
"sourceRoot": "/",
|
|
10
13
|
"strict": true
|
|
11
14
|
},
|
|
12
|
-
"include": ["lib
|
|
15
|
+
"include": ["lib/**/*.ts"]
|
|
13
16
|
}
|
package/bin/lib/add-project.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const project_1 = require("./init/project");
|
|
7
|
-
const projectsToText_1 = __importDefault(require("./utils/projectsToText"));
|
|
8
|
-
const getSelectedProjects_1 = require("./utils/getSelectedProjects");
|
|
9
|
-
const output_1 = __importDefault(require("./output"));
|
|
10
|
-
const quit_1 = require("./utils/quit");
|
|
11
|
-
const addProject = async () => {
|
|
12
|
-
const projects = (0, getSelectedProjects_1.getSelectedProjects)();
|
|
13
|
-
const usingComponents = (0, getSelectedProjects_1.getIsUsingComponents)();
|
|
14
|
-
try {
|
|
15
|
-
if (usingComponents) {
|
|
16
|
-
if (projects.length) {
|
|
17
|
-
console.log(`\nYou're currently syncing text from the ${output_1.default.info("Component Library")} and from the following projects: ${(0, projectsToText_1.default)(projects)}`);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
console.log(`\nYou're currently only syncing text from the ${output_1.default.info("Component Library")}`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
else if (projects.length) {
|
|
24
|
-
console.log(`\nYou're currently set up to sync text from the following projects: ${(0, projectsToText_1.default)(projects)}`);
|
|
25
|
-
}
|
|
26
|
-
await (0, project_1.collectAndSaveSource)({
|
|
27
|
-
components: false,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
console.log(`\nSorry, there was an error adding a project to your workspace: `, error);
|
|
32
|
-
(0, quit_1.quit)("Project selection was not updated.");
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
exports.default = addProject;
|
|
36
|
-
//# sourceMappingURL=add-project.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"add-project.js","sourceRoot":"","sources":["../../lib/add-project.ts"],"names":[],"mappings":";;;;;AAAA,4CAAsD;AACtD,4EAAoD;AACpD,qEAGqC;AACrC,sDAA8B;AAC9B,uCAAoC;AAEpC,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;IAC5B,MAAM,QAAQ,GAAG,IAAA,yCAAmB,GAAE,CAAC;IACvC,MAAM,eAAe,GAAG,IAAA,0CAAoB,GAAE,CAAC;IAE/C,IAAI;QACF,IAAI,eAAe,EAAE;YACnB,IAAI,QAAQ,CAAC,MAAM,EAAE;gBACnB,OAAO,CAAC,GAAG,CACT,4CAA4C,gBAAM,CAAC,IAAI,CACrD,mBAAmB,CACpB,qCAAqC,IAAA,wBAAc,EAAC,QAAQ,CAAC,EAAE,CACjE,CAAC;aACH;iBAAM;gBACL,OAAO,CAAC,GAAG,CACT,iDAAiD,gBAAM,CAAC,IAAI,CAC1D,mBAAmB,CACpB,EAAE,CACJ,CAAC;aACH;SACF;aAAM,IAAI,QAAQ,CAAC,MAAM,EAAE;YAC1B,OAAO,CAAC,GAAG,CACT,uEAAuE,IAAA,wBAAc,EACnF,QAAQ,CACT,EAAE,CACJ,CAAC;SACH;QACD,MAAM,IAAA,8BAAoB,EAAC;YACzB,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;KACJ;IAAC,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,GAAG,CACT,kEAAkE,EAClE,KAAK,CACN,CAAC;QACF,IAAA,WAAI,EAAC,oCAAoC,CAAC,CAAC;KAC5C;AACH,CAAC,CAAC;AAEF,kBAAe,UAAU,CAAC"}
|
package/bin/lib/api.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.create = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
-
const config_1 = __importDefault(require("./config"));
|
|
9
|
-
const consts_1 = __importDefault(require("./consts"));
|
|
10
|
-
const create = (token) => {
|
|
11
|
-
return axios_1.default.create({
|
|
12
|
-
baseURL: consts_1.default.API_HOST,
|
|
13
|
-
headers: {
|
|
14
|
-
Authorization: `token ${token}`,
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
exports.create = create;
|
|
19
|
-
exports.default = (0, exports.create)(config_1.default.getToken(consts_1.default.CONFIG_FILE, consts_1.default.API_HOST));
|
|
20
|
-
//# sourceMappingURL=api.js.map
|
package/bin/lib/api.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../lib/api.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAE1B,sDAA8B;AAC9B,sDAA8B;AAEvB,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,EAAE;IACvC,OAAO,eAAK,CAAC,MAAM,CAAC;QAClB,OAAO,EAAE,gBAAM,CAAC,QAAQ;QACxB,OAAO,EAAE;YACP,aAAa,EAAE,SAAS,KAAK,EAAE;SAChC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAPW,QAAA,MAAM,UAOjB;AAEF,kBAAe,IAAA,cAAM,EAAC,gBAAM,CAAC,QAAQ,CAAC,gBAAM,CAAC,WAAW,EAAE,gBAAM,CAAC,QAAQ,CAAC,CAAC,CAAC"}
|