@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.
Files changed (134) hide show
  1. package/README.md +125 -36
  2. package/bin/add-project.js +101 -33
  3. package/bin/add-project.js.map +1 -1
  4. package/bin/api.js +49 -15
  5. package/bin/api.js.map +1 -1
  6. package/bin/component-folders.js +56 -7
  7. package/bin/component-folders.js.map +1 -1
  8. package/bin/config.js +206 -170
  9. package/bin/config.js.map +1 -1
  10. package/bin/config.test.js +92 -0
  11. package/bin/config.test.js.map +1 -0
  12. package/bin/consts.js +64 -18
  13. package/bin/consts.js.map +1 -1
  14. package/bin/ditto.js +250 -200
  15. package/bin/ditto.js.map +1 -1
  16. package/bin/generate-suggestions.js +155 -78
  17. package/bin/generate-suggestions.js.map +1 -1
  18. package/bin/generate-suggestions.test.js +189 -0
  19. package/bin/generate-suggestions.test.js.map +1 -0
  20. package/bin/http/fetchComponentFolders.js +60 -8
  21. package/bin/http/fetchComponentFolders.js.map +1 -1
  22. package/bin/http/fetchComponents.js +65 -18
  23. package/bin/http/fetchComponents.js.map +1 -1
  24. package/bin/http/fetchVariants.js +74 -14
  25. package/bin/http/fetchVariants.js.map +1 -1
  26. package/bin/http/importComponents.js +100 -49
  27. package/bin/http/importComponents.js.map +1 -1
  28. package/bin/importComponents.js +61 -10
  29. package/bin/importComponents.js.map +1 -1
  30. package/bin/init/init.js +120 -44
  31. package/bin/init/init.js.map +1 -1
  32. package/bin/init/project.js +160 -83
  33. package/bin/init/project.js.map +1 -1
  34. package/bin/init/project.test.js +49 -0
  35. package/bin/init/project.test.js.map +1 -0
  36. package/bin/init/token.js +134 -74
  37. package/bin/init/token.js.map +1 -1
  38. package/bin/init/token.test.js +69 -0
  39. package/bin/init/token.test.js.map +1 -0
  40. package/bin/output.js +72 -30
  41. package/bin/output.js.map +1 -1
  42. package/bin/pull.js +424 -213
  43. package/bin/pull.js.map +1 -1
  44. package/bin/pull.test.js +410 -0
  45. package/bin/pull.test.js.map +1 -0
  46. package/bin/remove-project.js +91 -27
  47. package/bin/remove-project.js.map +1 -1
  48. package/bin/replace.js +140 -100
  49. package/bin/replace.js.map +1 -1
  50. package/bin/replace.test.js +155 -0
  51. package/bin/replace.test.js.map +1 -0
  52. package/bin/sentry-test.js.map +1 -0
  53. package/bin/types.js +20 -2
  54. package/bin/types.js.map +1 -1
  55. package/bin/utils/cleanFileName.js +32 -8
  56. package/bin/utils/cleanFileName.js.map +1 -1
  57. package/bin/utils/createSentryContext.js +43 -0
  58. package/bin/utils/createSentryContext.js.map +1 -0
  59. package/bin/utils/generateJsDriver.js +114 -51
  60. package/bin/utils/generateJsDriver.js.map +1 -1
  61. package/bin/utils/getSelectedProjects.js +58 -52
  62. package/bin/utils/getSelectedProjects.js.map +1 -1
  63. package/bin/utils/processMetaOption.js +36 -11
  64. package/bin/utils/processMetaOption.js.map +1 -1
  65. package/bin/utils/processMetaOption.test.js +45 -0
  66. package/bin/utils/processMetaOption.test.js.map +1 -0
  67. package/bin/utils/projectsToText.js +52 -19
  68. package/bin/utils/projectsToText.js.map +1 -1
  69. package/bin/utils/promptForProject.js +89 -36
  70. package/bin/utils/promptForProject.js.map +1 -1
  71. package/bin/utils/quit.js +36 -7
  72. package/bin/utils/quit.js.map +1 -1
  73. package/bin/utils/sourcesToText.js +51 -19
  74. package/bin/utils/sourcesToText.js.map +1 -1
  75. package/etsc.config.js +13 -0
  76. package/lib/config.ts +27 -8
  77. package/lib/ditto.ts +6 -0
  78. package/lib/init/project.ts +3 -3
  79. package/lib/pull.ts +190 -52
  80. package/lib/types.ts +24 -7
  81. package/lib/utils/createSentryContext.ts +20 -0
  82. package/lib/utils/generateJsDriver.ts +40 -6
  83. package/lib/utils/quit.ts +2 -3
  84. package/package.json +10 -6
  85. package/tsconfig.json +4 -1
  86. package/bin/lib/add-project.js +0 -36
  87. package/bin/lib/add-project.js.map +0 -1
  88. package/bin/lib/api.js +0 -20
  89. package/bin/lib/api.js.map +0 -1
  90. package/bin/lib/config.js +0 -202
  91. package/bin/lib/config.js.map +0 -1
  92. package/bin/lib/consts.js +0 -21
  93. package/bin/lib/consts.js.map +0 -1
  94. package/bin/lib/ditto.js +0 -121
  95. package/bin/lib/ditto.js.map +0 -1
  96. package/bin/lib/generate-suggestions.js +0 -71
  97. package/bin/lib/generate-suggestions.js.map +0 -1
  98. package/bin/lib/http/fetchComponents.js +0 -13
  99. package/bin/lib/http/fetchComponents.js.map +0 -1
  100. package/bin/lib/http/fetchVariants.js +0 -26
  101. package/bin/lib/http/fetchVariants.js.map +0 -1
  102. package/bin/lib/init/init.js +0 -50
  103. package/bin/lib/init/init.js.map +0 -1
  104. package/bin/lib/init/project.js +0 -108
  105. package/bin/lib/init/project.js.map +0 -1
  106. package/bin/lib/init/token.js +0 -91
  107. package/bin/lib/init/token.js.map +0 -1
  108. package/bin/lib/output.js +0 -34
  109. package/bin/lib/output.js.map +0 -1
  110. package/bin/lib/pull.js +0 -264
  111. package/bin/lib/pull.js.map +0 -1
  112. package/bin/lib/remove-project.js +0 -35
  113. package/bin/lib/remove-project.js.map +0 -1
  114. package/bin/lib/replace.js +0 -107
  115. package/bin/lib/replace.js.map +0 -1
  116. package/bin/lib/types.js +0 -3
  117. package/bin/lib/types.js.map +0 -1
  118. package/bin/lib/utils/cleanFileName.js +0 -11
  119. package/bin/lib/utils/cleanFileName.js.map +0 -1
  120. package/bin/lib/utils/generateJsDriver.js +0 -56
  121. package/bin/lib/utils/generateJsDriver.js.map +0 -1
  122. package/bin/lib/utils/getSelectedProjects.js +0 -61
  123. package/bin/lib/utils/getSelectedProjects.js.map +0 -1
  124. package/bin/lib/utils/processMetaOption.js +0 -15
  125. package/bin/lib/utils/processMetaOption.js.map +0 -1
  126. package/bin/lib/utils/projectsToText.js +0 -25
  127. package/bin/lib/utils/projectsToText.js.map +0 -1
  128. package/bin/lib/utils/promptForProject.js +0 -43
  129. package/bin/lib/utils/promptForProject.js.map +0 -1
  130. package/bin/lib/utils/quit.js +0 -10
  131. package/bin/lib/utils/quit.js.map +0 -1
  132. package/bin/lib/utils/sourcesToText.js +0 -25
  133. package/bin/lib/utils/sourcesToText.js.map +0 -1
  134. 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 { SourceInformation, Token, Project, SupportedFormat } from "./types";
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 { kMaxLength } from "buffer";
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 ({ id, fileName }: Project) => {
119
- const { data } = await api.get(`/projects/${id}`, {
120
- params,
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
- fs.writeFileSync(filepath, dataString);
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 ({ id, fileName }: Project) => {
187
- const { data } = await api.get(`/projects/${id}`, {
188
- params,
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
- fs.writeFileSync(filepath, dataString);
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 fetchVariants(source);
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
- const messages = await Promise.all(
281
- componentVariants.map(async ({ apiID: variantApiId }) => {
282
- const p = new URLSearchParams(params);
283
- if (variantApiId) p.append("variant", variantApiId);
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
- const { data } = await api.get(`/components`, { params: p });
379
+ componentVariants.forEach(({ apiID: variantApiId }) => {
380
+ messagePromises.push(
381
+ ...componentFolderRequests.map(async (componentFolder) => {
382
+ const componentFolderParams = new URLSearchParams(params);
286
383
 
287
- const nameExt = getFormatExtension(format);
288
- const nameBase = "ditto-component-library";
289
- const namePostfix = `__${variantApiId || "base"}`;
384
+ if (variantApiId)
385
+ componentFolderParams.append("variant", variantApiId);
290
386
 
291
- const fileName = cleanFileName(`${nameBase}${namePostfix}${nameExt}`);
292
- const filePath = path.join(consts.TEXT_DIR, fileName);
387
+ // If folder-level status is specified, overrides root-level status
388
+ if (componentFolder.status)
389
+ componentFolderParams.append("status", componentFolder.status);
293
390
 
294
- let dataString = data;
295
- if (nameExt === ".json") {
296
- dataString = JSON.stringify(data, null, 2);
297
- }
391
+ const url =
392
+ componentFolder.id === "__root__"
393
+ ? "/components?root_only=true"
394
+ : `/component-folders/${componentFolder.id}/components`;
298
395
 
299
- const dataIsValid = getFormatDataIsValid[format];
300
- if (!dataIsValid(dataString)) {
301
- return "";
302
- }
396
+ const { data } = await api.get(url, {
397
+ params: componentFolderParams,
398
+ });
303
399
 
304
- await new Promise((r) => fs.writeFile(filePath, dataString, r));
305
- return getSavedMessage(fileName);
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
- return downloadAndSave(sourceInformation, token, { meta });
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 Source = Project;
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
- componentFolders: ComponentFolder[] | null;
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 data = fileNames.reduce(
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
@@ -1,5 +1,4 @@
1
- export function quit(message: string, exitCode = 2) {
2
- console.log(`\n${message}\n`);
1
+ export function quit(message: string | null, exitCode = 2) {
2
+ if (message) console.log(`\n${message}\n`);
3
3
  process.exitCode = exitCode;
4
- process.exit();
5
4
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@dittowords/cli",
3
- "version": "3.10.1",
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
- "prepublish": "tsc",
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": "tsc && node bin/ditto.js",
11
- "sync": "tsc && node bin/ditto.js pull",
12
- "dev": "tsc --watch"
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/ditto.ts"]
15
+ "include": ["lib/**/*.ts"]
13
16
  }
@@ -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
@@ -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"}