@dittowords/cli 4.5.2 → 5.0.0-beta.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 (171) hide show
  1. package/README.md +6 -5
  2. package/bin/ditto.js +110 -285
  3. package/package.json +16 -10
  4. package/.github/actions/install-node-dependencies/action.yml +0 -24
  5. package/.github/workflows/required-checks.yml +0 -24
  6. package/.husky/pre-commit +0 -4
  7. package/.prettierignore +0 -0
  8. package/.prettierrc.json +0 -1
  9. package/__mocks__/fs.js +0 -2
  10. package/babel.config.js +0 -6
  11. package/bin/__mocks__/api.js +0 -48
  12. package/bin/__mocks__/api.js.map +0 -1
  13. package/bin/add-project.js +0 -104
  14. package/bin/add-project.js.map +0 -1
  15. package/bin/api.js +0 -54
  16. package/bin/api.js.map +0 -1
  17. package/bin/component-folders.js +0 -59
  18. package/bin/component-folders.js.map +0 -1
  19. package/bin/config.js +0 -242
  20. package/bin/config.js.map +0 -1
  21. package/bin/config.test.js +0 -93
  22. package/bin/config.test.js.map +0 -1
  23. package/bin/consts.js +0 -57
  24. package/bin/consts.js.map +0 -1
  25. package/bin/ditto.js.map +0 -1
  26. package/bin/generate-suggestions.js +0 -183
  27. package/bin/generate-suggestions.js.map +0 -1
  28. package/bin/generate-suggestions.test.js +0 -200
  29. package/bin/generate-suggestions.test.js.map +0 -1
  30. package/bin/http/__mocks__/fetchComponentFolders.js +0 -71
  31. package/bin/http/__mocks__/fetchComponentFolders.js.map +0 -1
  32. package/bin/http/__mocks__/fetchComponents.js +0 -73
  33. package/bin/http/__mocks__/fetchComponents.js.map +0 -1
  34. package/bin/http/__mocks__/fetchVariants.js +0 -71
  35. package/bin/http/__mocks__/fetchVariants.js.map +0 -1
  36. package/bin/http/fetchComponentFolders.js +0 -64
  37. package/bin/http/fetchComponentFolders.js.map +0 -1
  38. package/bin/http/fetchComponents.js +0 -78
  39. package/bin/http/fetchComponents.js.map +0 -1
  40. package/bin/http/fetchVariants.js +0 -87
  41. package/bin/http/fetchVariants.js.map +0 -1
  42. package/bin/http/http.test.js +0 -159
  43. package/bin/http/http.test.js.map +0 -1
  44. package/bin/http/importComponents.js +0 -114
  45. package/bin/http/importComponents.js.map +0 -1
  46. package/bin/importComponents.js +0 -65
  47. package/bin/importComponents.js.map +0 -1
  48. package/bin/init/init.js +0 -126
  49. package/bin/init/init.js.map +0 -1
  50. package/bin/init/project.js +0 -182
  51. package/bin/init/project.js.map +0 -1
  52. package/bin/init/project.test.js +0 -26
  53. package/bin/init/project.test.js.map +0 -1
  54. package/bin/init/token.js +0 -196
  55. package/bin/init/token.js.map +0 -1
  56. package/bin/init/token.test.js +0 -147
  57. package/bin/init/token.test.js.map +0 -1
  58. package/bin/output.js +0 -76
  59. package/bin/output.js.map +0 -1
  60. package/bin/pull-lib.test.js +0 -379
  61. package/bin/pull-lib.test.js.map +0 -1
  62. package/bin/pull.js +0 -562
  63. package/bin/pull.js.map +0 -1
  64. package/bin/pull.test.js +0 -151
  65. package/bin/pull.test.js.map +0 -1
  66. package/bin/remove-project.js +0 -99
  67. package/bin/remove-project.js.map +0 -1
  68. package/bin/replace.js +0 -171
  69. package/bin/replace.js.map +0 -1
  70. package/bin/replace.test.js +0 -197
  71. package/bin/replace.test.js.map +0 -1
  72. package/bin/types.js +0 -21
  73. package/bin/types.js.map +0 -1
  74. package/bin/utils/cleanFileName.js +0 -40
  75. package/bin/utils/cleanFileName.js.map +0 -1
  76. package/bin/utils/cleanFileName.test.js +0 -15
  77. package/bin/utils/cleanFileName.test.js.map +0 -1
  78. package/bin/utils/createSentryContext.js +0 -43
  79. package/bin/utils/createSentryContext.js.map +0 -1
  80. package/bin/utils/determineModuleType.js +0 -79
  81. package/bin/utils/determineModuleType.js.map +0 -1
  82. package/bin/utils/determineModuleType.test.js +0 -60
  83. package/bin/utils/determineModuleType.test.js.map +0 -1
  84. package/bin/utils/generateIOSBundles.js +0 -147
  85. package/bin/utils/generateIOSBundles.js.map +0 -1
  86. package/bin/utils/generateJsDriver.js +0 -178
  87. package/bin/utils/generateJsDriver.js.map +0 -1
  88. package/bin/utils/generateJsDriverTypeFile.js +0 -105
  89. package/bin/utils/generateJsDriverTypeFile.js.map +0 -1
  90. package/bin/utils/generateSwiftDriver.js +0 -93
  91. package/bin/utils/generateSwiftDriver.js.map +0 -1
  92. package/bin/utils/getSelectedProjects.js +0 -67
  93. package/bin/utils/getSelectedProjects.js.map +0 -1
  94. package/bin/utils/processMetaOption.js +0 -40
  95. package/bin/utils/processMetaOption.js.map +0 -1
  96. package/bin/utils/processMetaOption.test.js +0 -45
  97. package/bin/utils/processMetaOption.test.js.map +0 -1
  98. package/bin/utils/projectsToText.js +0 -58
  99. package/bin/utils/projectsToText.js.map +0 -1
  100. package/bin/utils/promptForProject.js +0 -96
  101. package/bin/utils/promptForProject.js.map +0 -1
  102. package/bin/utils/quit.js +0 -73
  103. package/bin/utils/quit.js.map +0 -1
  104. package/bin/utils/sourcesToText.js +0 -57
  105. package/bin/utils/sourcesToText.js.map +0 -1
  106. package/etsc.config.js +0 -13
  107. package/jest.config.ts +0 -16
  108. package/jsconfig.json +0 -5
  109. package/lib/__mocks__/api.ts +0 -12
  110. package/lib/add-project.ts +0 -48
  111. package/lib/api.ts +0 -16
  112. package/lib/component-folders.ts +0 -9
  113. package/lib/config.test.ts +0 -79
  114. package/lib/config.ts +0 -279
  115. package/lib/consts.ts +0 -22
  116. package/lib/ditto.ts +0 -285
  117. package/lib/generate-suggestions.test.ts +0 -169
  118. package/lib/generate-suggestions.ts +0 -166
  119. package/lib/http/__mocks__/fetchComponentFolders.ts +0 -23
  120. package/lib/http/__mocks__/fetchComponents.ts +0 -24
  121. package/lib/http/__mocks__/fetchVariants.ts +0 -21
  122. package/lib/http/fetchComponentFolders.ts +0 -23
  123. package/lib/http/fetchComponents.ts +0 -43
  124. package/lib/http/fetchVariants.ts +0 -42
  125. package/lib/http/http.test.ts +0 -122
  126. package/lib/http/importComponents.ts +0 -79
  127. package/lib/importComponents.ts +0 -24
  128. package/lib/init/init.ts +0 -79
  129. package/lib/init/project.test.ts +0 -26
  130. package/lib/init/project.ts +0 -136
  131. package/lib/init/token.test.ts +0 -99
  132. package/lib/init/token.ts +0 -156
  133. package/lib/output.ts +0 -21
  134. package/lib/pull-lib.test.ts +0 -367
  135. package/lib/pull.test.ts +0 -117
  136. package/lib/pull.ts +0 -629
  137. package/lib/remove-project.ts +0 -44
  138. package/lib/replace.test.ts +0 -157
  139. package/lib/replace.ts +0 -140
  140. package/lib/types.ts +0 -83
  141. package/lib/utils/cleanFileName.test.ts +0 -11
  142. package/lib/utils/cleanFileName.ts +0 -8
  143. package/lib/utils/createSentryContext.ts +0 -20
  144. package/lib/utils/determineModuleType.test.ts +0 -48
  145. package/lib/utils/determineModuleType.ts +0 -55
  146. package/lib/utils/generateIOSBundles.ts +0 -122
  147. package/lib/utils/generateJsDriver.ts +0 -207
  148. package/lib/utils/generateJsDriverTypeFile.ts +0 -75
  149. package/lib/utils/generateSwiftDriver.ts +0 -48
  150. package/lib/utils/getSelectedProjects.ts +0 -36
  151. package/lib/utils/processMetaOption.test.ts +0 -18
  152. package/lib/utils/processMetaOption.ts +0 -16
  153. package/lib/utils/projectsToText.ts +0 -29
  154. package/lib/utils/promptForProject.ts +0 -61
  155. package/lib/utils/quit.ts +0 -7
  156. package/lib/utils/sourcesToText.ts +0 -25
  157. package/pull_request_template.md +0 -20
  158. package/testfiles/en.json +0 -5
  159. package/testfiles/es.json +0 -5
  160. package/testfiles/fr.json +0 -5
  161. package/testfiles/test1.jsx +0 -18
  162. package/testfiles/test2.jsx +0 -9
  163. package/testing/.gitkeep +0 -0
  164. package/testing/fixtures/bad-yaml.yml +0 -6
  165. package/testing/fixtures/ditto-config-no-token +0 -2
  166. package/testing/fixtures/project-config-empty-projects.yml +0 -1
  167. package/testing/fixtures/project-config-no-id.yml +0 -2
  168. package/testing/fixtures/project-config-no-name.yml +0 -2
  169. package/testing/fixtures/project-config-pull.yml +0 -0
  170. package/testing/fixtures/project-config-working.yml +0 -3
  171. package/tsconfig.json +0 -16
package/lib/pull.ts DELETED
@@ -1,629 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- import ora from "ora";
5
- import * as Sentry from "@sentry/node";
6
-
7
- import { createApiClient } from "./api";
8
- import config from "./config";
9
- import consts from "./consts";
10
- import output from "./output";
11
- import { collectAndSaveToken } from "./init/token";
12
- import sourcesToText from "./utils/sourcesToText";
13
- import { generateJsDriver } from "./utils/generateJsDriver";
14
- import { cleanFileName } from "./utils/cleanFileName";
15
- import {
16
- SourceInformation,
17
- Token,
18
- Project,
19
- SupportedFormat,
20
- SupportedExtension,
21
- ComponentFolder,
22
- ComponentSource,
23
- Source,
24
- } from "./types";
25
- import { fetchVariants } from "./http/fetchVariants";
26
- import { quit } from "./utils/quit";
27
- import { AxiosError } from "axios";
28
- import { fetchComponentFolders } from "./http/fetchComponentFolders";
29
- import { generateSwiftDriver } from "./utils/generateSwiftDriver";
30
- import { generateIOSBundles } from "./utils/generateIOSBundles";
31
-
32
- interface IRequestOptions {
33
- projects: Project[];
34
- format: SupportedFormat;
35
- status: string | undefined;
36
- richText?: boolean | undefined;
37
- token?: Token;
38
- options?: PullOptions;
39
- }
40
-
41
- interface IRequestOptionsWithVariants extends IRequestOptions {
42
- variants: { apiID: string }[];
43
- }
44
-
45
- const ensureEndsWithNewLine = (str: string) =>
46
- str + (/[\r\n]$/.test(str) ? "" : "\n");
47
-
48
- export const writeFile = (path: string, data: string) =>
49
- new Promise((r) => fs.writeFile(path, ensureEndsWithNewLine(data), r));
50
-
51
- const SUPPORTED_FORMATS: SupportedFormat[] = [
52
- "flat",
53
- "nested",
54
- "structured",
55
- "android",
56
- "ios-strings",
57
- "ios-stringsdict",
58
- "icu",
59
- ];
60
-
61
- export type JSONFormat = "flat" | "nested" | "structured" | "icu";
62
-
63
- const IOS_FORMATS: SupportedFormat[] = ["ios-strings", "ios-stringsdict"];
64
- const JSON_FORMATS: JSONFormat[] = ["flat", "nested", "structured", "icu"];
65
-
66
- const getJsonFormat = (formats: string[]): JSONFormat => {
67
- // edge case: multiple json formats specified
68
- // we should grab the last one
69
- const jsonFormats = formats.filter((f) =>
70
- JSON_FORMATS.includes(f as JSONFormat)
71
- ) as JSONFormat[];
72
-
73
- return jsonFormats[jsonFormats.length - 1] || "flat";
74
- };
75
-
76
- const FORMAT_EXTENSIONS: Record<SupportedFormat, SupportedExtension> = {
77
- flat: ".json",
78
- nested: ".json",
79
- structured: ".json",
80
- android: ".xml",
81
- "ios-strings": ".strings",
82
- "ios-stringsdict": ".stringsdict",
83
- icu: ".json",
84
- };
85
-
86
- const getJsonFormatIsValid = (data: string) => {
87
- try {
88
- return Object.keys(JSON.parse(data)).some(
89
- (k) => !k.startsWith("__variant")
90
- );
91
- } catch {
92
- return false;
93
- }
94
- };
95
-
96
- // exported for test usage only
97
- export const getFormatDataIsValid = {
98
- flat: getJsonFormatIsValid,
99
- nested: getJsonFormatIsValid,
100
- structured: getJsonFormatIsValid,
101
- icu: getJsonFormatIsValid,
102
- android: (data: string) => data.includes("<string"),
103
- "ios-strings": (data: string) => data.includes(`" = "`),
104
- "ios-stringsdict": (data: string) => data.includes("<key>"),
105
- };
106
-
107
- const getFormat = (
108
- formatFromSource: string | string[] | undefined
109
- ): SupportedFormat[] => {
110
- const formats = (
111
- Array.isArray(formatFromSource) ? formatFromSource : [formatFromSource]
112
- ).filter((format) =>
113
- SUPPORTED_FORMATS.includes(format as SupportedFormat)
114
- ) as SupportedFormat[];
115
-
116
- if (formats.length) {
117
- return formats;
118
- }
119
-
120
- return ["flat"];
121
- };
122
-
123
- const getFormatExtension = (format: SupportedFormat) => {
124
- return FORMAT_EXTENSIONS[format];
125
- };
126
-
127
- const DEFAULT_FORMAT_KEYS = ["projects", "exported_at"];
128
- const hasVariantData = (data: any) => {
129
- const hasTopLevelKeys =
130
- Object.keys(data).filter((key) => !DEFAULT_FORMAT_KEYS.includes(key))
131
- .length > 0;
132
-
133
- const hasProjectKeys = data.projects && Object.keys(data.projects).length > 0;
134
-
135
- return hasTopLevelKeys || hasProjectKeys;
136
- };
137
-
138
- async function askForAnotherToken() {
139
- config.deleteToken(consts.CONFIG_FILE, consts.API_HOST);
140
- const message =
141
- "Looks like the API key you have saved no longer works. Please enter another one.";
142
- await collectAndSaveToken(message);
143
- }
144
-
145
- /**
146
- * For a given variant:
147
- * - if format is unspecified, fetch data for all projects from `/projects` and
148
- * save in `{variantApiId}.json`
149
- * - if format is `flat` or `structured`, fetch data for each project from `/project/:project_id` and
150
- * save in `{projectName}-${variantApiId}.json`
151
- */
152
- async function downloadAndSaveVariant(
153
- variantApiId: string | null,
154
- requestOptions: IRequestOptions
155
- ) {
156
- const { projects, format, status, richText, token } = requestOptions;
157
- const api = createApiClient();
158
- const params: Record<string, string | null> = { variant: variantApiId };
159
- if (format) params.format = format;
160
- if (richText) params.includeRichText = richText.toString();
161
-
162
- // Root-level status gets set as the default if specified
163
- if (status) params.status = status;
164
-
165
- const savedMessages = await Promise.all(
166
- projects.map(async (project) => {
167
- const projectParams = { ...params };
168
- // If project-level status is specified, overrides root-level status
169
- if (project.status) projectParams.status = project.status;
170
- if (project.exclude_components)
171
- projectParams.exclude_components = String(project.exclude_components);
172
-
173
- const { data } = await api.get(`/v1/projects/${project.id}`, {
174
- params: projectParams,
175
- headers: { Authorization: `token ${token}` },
176
- });
177
-
178
- if (!hasVariantData(data)) {
179
- return "";
180
- }
181
-
182
- const extension = getFormatExtension(format);
183
-
184
- const filename = cleanFileName(
185
- project.fileName + ("__" + (variantApiId || "base")) + extension
186
- );
187
- const filepath = path.join(consts.TEXT_DIR, filename);
188
-
189
- let dataString = data;
190
- if (extension === ".json") {
191
- dataString = JSON.stringify(data, null, 2);
192
- }
193
-
194
- const dataIsValid = getFormatDataIsValid[format];
195
- if (!dataIsValid(dataString)) {
196
- return "";
197
- }
198
-
199
- await writeFile(filepath, dataString);
200
- return getSavedMessage(filename);
201
- })
202
- );
203
-
204
- return savedMessages.join("");
205
- }
206
-
207
- async function downloadAndSaveVariants(
208
- requestOptions: IRequestOptionsWithVariants
209
- ) {
210
- const messages = await Promise.all([
211
- downloadAndSaveVariant(null, requestOptions),
212
- ...requestOptions.variants.map(({ apiID }: { apiID: string }) =>
213
- downloadAndSaveVariant(apiID, requestOptions)
214
- ),
215
- ]);
216
-
217
- return messages.join("");
218
- }
219
-
220
- async function downloadAndSaveBase(requestOptions: IRequestOptions) {
221
- const { projects, format, status, richText, token, options } = requestOptions;
222
-
223
- const api = createApiClient();
224
- const params = { ...options?.meta };
225
- if (format) params.format = format;
226
- if (richText) params.includeRichText = richText.toString();
227
-
228
- // Root-level status gets set as the default if specified
229
- if (status) params.status = status;
230
-
231
- const savedMessages = await Promise.all(
232
- projects.map(async (project) => {
233
- const projectParams = { ...params };
234
- // If project-level status is specified, overrides root-level status
235
- if (project.status) projectParams.status = project.status;
236
- if (project.exclude_components)
237
- projectParams.exclude_components = String(project.exclude_components);
238
-
239
- const { data } = await api.get(`/v1/projects/${project.id}`, {
240
- params: projectParams,
241
- headers: { Authorization: `token ${token}` },
242
- });
243
-
244
- const extension = getFormatExtension(format);
245
- const filename = cleanFileName(`${project.fileName}__base${extension}`);
246
- const filepath = path.join(consts.TEXT_DIR, filename);
247
-
248
- let dataString = data;
249
- if (extension === ".json") {
250
- dataString = JSON.stringify(data, null, 2);
251
- }
252
-
253
- const dataIsValid = getFormatDataIsValid[format];
254
- if (!dataIsValid(dataString)) {
255
- return "";
256
- }
257
-
258
- await writeFile(filepath, dataString);
259
- return getSavedMessage(filename);
260
- })
261
- );
262
-
263
- return savedMessages.join("");
264
- }
265
-
266
- function getSavedMessage(file: string) {
267
- return `Successfully saved to ${output.info(file)}\n`;
268
- }
269
-
270
- function cleanOutputFiles() {
271
- if (!fs.existsSync(consts.TEXT_DIR)) {
272
- fs.mkdirSync(consts.TEXT_DIR);
273
- }
274
-
275
- const directoryContents = fs.readdirSync(consts.TEXT_DIR, {
276
- withFileTypes: true,
277
- });
278
-
279
- directoryContents.forEach((item) => {
280
- if (item.isDirectory() && /\.lproj$/.test(item.name)) {
281
- return fs.rmSync(path.resolve(consts.TEXT_DIR, item.name), {
282
- recursive: true,
283
- force: true,
284
- });
285
- }
286
-
287
- if (
288
- item.isFile() &&
289
- /\.js(on)?|\.ts|\.xml|\.strings(dict)?$|\.swift$/.test(item.name)
290
- ) {
291
- return fs.unlinkSync(path.resolve(consts.TEXT_DIR, item.name));
292
- }
293
- });
294
-
295
- return "Cleaning old output files..\n";
296
- }
297
-
298
- async function downloadAndSave(
299
- source: SourceInformation,
300
- token?: Token,
301
- options?: PullOptions
302
- ) {
303
- const api = createApiClient();
304
- const {
305
- validProjects,
306
- format: formatFromSource,
307
- shouldFetchComponentLibrary,
308
- status,
309
- richText,
310
- componentFolders: specifiedComponentFolders,
311
- componentRoot,
312
- localeByVariantApiId,
313
- disableJsDriver,
314
- } = source;
315
-
316
- const formats = getFormat(formatFromSource);
317
-
318
- const hasJSONFormat = formats.some((f) =>
319
- JSON_FORMATS.includes(f as JSONFormat)
320
- );
321
- const hasIOSFormat = formats.some((f) => IOS_FORMATS.includes(f));
322
- const shouldGenerateIOSBundles = hasIOSFormat && localeByVariantApiId;
323
-
324
- const shouldLogOutputFiles = !shouldGenerateIOSBundles;
325
-
326
- let msg = "";
327
- const spinner = ora(msg);
328
- spinner.start();
329
-
330
- const [variants, allComponentFoldersResponse] = await Promise.all([
331
- fetchVariants(source, options),
332
- fetchComponentFolders({}),
333
- ]);
334
-
335
- const allComponentFolders = Object.entries(
336
- allComponentFoldersResponse
337
- ).reduce(
338
- (acc, [id, name]) => acc.concat([{ id, name }]),
339
- [] as ComponentFolder[]
340
- );
341
-
342
- try {
343
- msg += cleanOutputFiles();
344
- msg += `\nFetching the latest text from ${sourcesToText(
345
- validProjects,
346
- shouldFetchComponentLibrary
347
- )}\n`;
348
-
349
- const meta = options ? options.meta : {};
350
-
351
- const rootRequest = {
352
- id: "__root__",
353
- name: "Root",
354
- // componentRoot can be a boolean or an object
355
- status:
356
- typeof source.componentRoot === "object"
357
- ? source.componentRoot.status
358
- : undefined,
359
- };
360
-
361
- let componentFolderRequests: ComponentFolder[] = [];
362
-
363
- // there's a lot of complex logic here, and it's tempting to want to
364
- // simplify it. however, it's difficult to get rid of the complexity
365
- // without sacrificing specificity and expressiveness.
366
- //
367
- // if folders specified..
368
- if (specifiedComponentFolders) {
369
- switch (componentRoot) {
370
- // .. and no root specified, you only get components in the specified folders
371
- case undefined:
372
- case false:
373
- componentFolderRequests.push(...specifiedComponentFolders);
374
- break;
375
- // .. and root specified, you get components in folders and the root
376
- default:
377
- componentFolderRequests.push(...specifiedComponentFolders);
378
- componentFolderRequests.push(rootRequest);
379
- break;
380
- }
381
- }
382
- // if no folders specified..
383
- else {
384
- switch (componentRoot) {
385
- // .. and no root specified, you get all components including those in folders
386
- case undefined:
387
- componentFolderRequests.push(...allComponentFolders);
388
- componentFolderRequests.push(rootRequest);
389
- break;
390
- // .. and root specified as false, you only get components in folders
391
- case false:
392
- componentFolderRequests.push(...allComponentFolders);
393
- break;
394
- // .. and root specified as true or config object, you only get components in the root
395
- default:
396
- componentFolderRequests.push(rootRequest);
397
- break;
398
- }
399
- }
400
-
401
- // this array is populated while fetching from the component library and is used when
402
- // generating the index.js driver file
403
- const componentSources: ComponentSource[] = [];
404
-
405
- async function fetchComponentLibrary(format: SupportedFormat) {
406
- // Always include a variant with an apiID of undefined to ensure that we
407
- // fetch the base text for the component library.
408
- const componentVariants = [{ apiID: undefined }, ...(variants || [])];
409
-
410
- const params = new URLSearchParams();
411
- if (options?.meta)
412
- Object.entries(options.meta).forEach(([k, v]) => params.append(k, v));
413
- if (format) params.append("format", format);
414
- if (richText) params.append("includeRichText", richText.toString());
415
-
416
- // Root-level status gets set as the default if specified
417
- if (status) params.append("status", status);
418
-
419
- const messagePromises: Promise<string>[] = [];
420
-
421
- componentVariants.forEach(({ apiID: variantApiId }) => {
422
- messagePromises.push(
423
- ...componentFolderRequests.map(async (componentFolder) => {
424
- const componentFolderParams = new URLSearchParams(params);
425
-
426
- if (variantApiId)
427
- componentFolderParams.append("variant", variantApiId);
428
-
429
- // If folder-level status is specified, overrides root-level status
430
- if (componentFolder.status)
431
- componentFolderParams.append("status", componentFolder.status);
432
-
433
- const url =
434
- componentFolder.id === "__root__"
435
- ? "/v1/components?root_only=true"
436
- : `/v1/component-folders/${componentFolder.id}/components`;
437
-
438
- const { data } = await api.get(url, {
439
- params: componentFolderParams,
440
- });
441
-
442
- const nameExt = getFormatExtension(format);
443
- const nameBase = "components";
444
-
445
- // we need to clean the folder name by itself first, otherwise we can
446
- // end up with "empty" words and weird hyphenation.
447
- const nameFolder = `__${cleanFileName(componentFolder.name)}`;
448
- const namePostfix = `__${variantApiId || "base"}`;
449
-
450
- const fileName = cleanFileName(
451
- `${nameBase}${nameFolder}${namePostfix}${nameExt}`
452
- );
453
- const filePath = path.join(consts.TEXT_DIR, fileName);
454
-
455
- let dataString = data;
456
- if (nameExt === ".json") {
457
- dataString = JSON.stringify(data, null, 2);
458
- }
459
-
460
- const dataIsValid = getFormatDataIsValid[format];
461
- if (!dataIsValid(dataString)) {
462
- return "";
463
- }
464
-
465
- await writeFile(filePath, dataString);
466
-
467
- componentSources.push({
468
- type: "components",
469
- id: "ditto_component_library",
470
- name: "ditto_component_library",
471
- fileName,
472
- variant: variantApiId || "base",
473
- });
474
-
475
- return getSavedMessage(fileName);
476
- })
477
- );
478
- });
479
-
480
- const messages = await Promise.all(messagePromises);
481
- if (shouldLogOutputFiles) {
482
- msg += messages.join("");
483
- }
484
- }
485
-
486
- if (shouldFetchComponentLibrary) {
487
- for (const format of formats) {
488
- await fetchComponentLibrary(format);
489
- }
490
- }
491
-
492
- async function fetchProjects(format: SupportedFormat) {
493
- let result = "";
494
- if (variants) {
495
- result = await downloadAndSaveVariants({
496
- variants,
497
- projects: validProjects,
498
- format,
499
- status,
500
- richText,
501
- token,
502
- });
503
- } else {
504
- result = await downloadAndSaveBase({
505
- projects: validProjects,
506
- format,
507
- status,
508
- richText,
509
- token,
510
- options: {
511
- meta,
512
- },
513
- });
514
- }
515
-
516
- if (shouldLogOutputFiles) {
517
- msg += result;
518
- }
519
- }
520
-
521
- if (validProjects.length) {
522
- for (const format of formats) {
523
- await fetchProjects(format);
524
- }
525
- }
526
-
527
- const sources: Source[] = [...validProjects, ...componentSources];
528
-
529
- if (hasJSONFormat && !disableJsDriver)
530
- msg += generateJsDriver(sources, getJsonFormat(formats));
531
-
532
- if (shouldGenerateIOSBundles) {
533
- msg += "iOS locale information detected, generating bundles..\n\n";
534
- msg += await generateIOSBundles(localeByVariantApiId);
535
- msg += await generateSwiftDriver(source);
536
- }
537
-
538
- msg += `\n\n${output.success("Done")}!`;
539
-
540
- spinner.stop();
541
- return console.log(msg);
542
- } catch (e: any) {
543
- console.error(e);
544
-
545
- spinner.stop();
546
- let error = e.message;
547
- if (e.response && e.response.status === 404) {
548
- await askForAnotherToken();
549
- pull();
550
- return;
551
- }
552
- if (e.response && e.response.status === 401) {
553
- error = "You don't have access to the selected projects";
554
- msg = `${output.errorText(error)}.\nChoose others using the ${output.info(
555
- "project"
556
- )} command, or update your API key.`;
557
- return console.log(msg);
558
- }
559
- if (e.response && e.response.status === 403) {
560
- error =
561
- "One or more of the requested projects don't have Developer Mode enabled";
562
- msg = `${output.errorText(
563
- error
564
- )}.\nPlease choose different projects using the ${output.info(
565
- "project"
566
- )} command, or turn on Developer Mode for all selected projects. Learn more here: ${output.subtle(
567
- "https://www.dittowords.com/docs/ditto-developer-mode"
568
- )}.`;
569
- return console.log(msg);
570
- }
571
- if (e.response && e.response.status === 400) {
572
- error = "projects not found";
573
- }
574
- msg = `We hit an error fetching text from the projects: ${output.errorText(
575
- error
576
- )}.\nChoose others using the ${output.info("project")} command.`;
577
- return console.log(msg);
578
- }
579
- }
580
-
581
- export interface PullOptions {
582
- meta?: Record<string, string>;
583
- includeSampleData?: boolean;
584
- }
585
-
586
- export const pull = async (options?: PullOptions) => {
587
- const meta = options ? options.meta : {};
588
- const includeSampleData = options?.includeSampleData || false;
589
- const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
590
- const sourceInformation = config.parseSourceInformation();
591
- try {
592
- return await downloadAndSave(sourceInformation, token, {
593
- meta,
594
- includeSampleData,
595
- });
596
- } catch (e) {
597
- const eventId = Sentry.captureException(e);
598
- const eventStr = `\n\nError ID: ${output.info(eventId)}`;
599
- if (e instanceof AxiosError) {
600
- return await quit(
601
- output.errorText(
602
- "Something went wrong connecting to Ditto servers. Please contact support or try again later."
603
- ) + eventStr
604
- );
605
- }
606
-
607
- return await quit(
608
- output.errorText(
609
- "Something went wrong. Please contact support or try again later."
610
- ) + eventStr
611
- );
612
- }
613
- };
614
-
615
- export default {
616
- pull,
617
- _testing: {
618
- cleanOutputFiles,
619
- downloadAndSaveVariant,
620
- downloadAndSaveVariants,
621
- downloadAndSaveBase,
622
- },
623
- };
624
-
625
- export const _test = {
626
- getJsonFormat,
627
- getJsonFormatIsValid,
628
- ensureEndsWithNewLine,
629
- };
@@ -1,44 +0,0 @@
1
- import config from "./config";
2
- import consts from "./consts";
3
- import output from "./output";
4
- import {
5
- getSelectedProjects,
6
- getIsUsingComponents,
7
- } from "./utils/getSelectedProjects";
8
- import promptForProject from "./utils/promptForProject";
9
-
10
- async function removeProject() {
11
- const projects = getSelectedProjects();
12
- const isUsingComponents = getIsUsingComponents();
13
- if (!projects.length && !isUsingComponents) {
14
- console.log(
15
- "\n" +
16
- "No projects found in your current configuration.\n" +
17
- `Try adding one with: ${output.info("ditto-cli project add")}\n`
18
- );
19
- return;
20
- }
21
-
22
- const projectToRemove = await promptForProject({
23
- projects,
24
- message: "Select a project to remove",
25
- });
26
- if (!projectToRemove) return;
27
-
28
- config.writeProjectConfigData(consts.PROJECT_CONFIG_FILE, {
29
- components: isUsingComponents && projectToRemove.id !== "components",
30
- projects: projects.filter(({ id }) => id !== projectToRemove.id),
31
- });
32
-
33
- console.log(
34
- `\n${output.info(
35
- projectToRemove.name
36
- )} has been removed from your selected projects. ` +
37
- `\nWe saved your updated configuration to: ${output.info(
38
- consts.PROJECT_CONFIG_FILE
39
- )}` +
40
- "\n"
41
- );
42
- }
43
-
44
- export default removeProject;