@dittowords/cli 4.0.0-alpha.0 → 4.0.1-alpha.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 +137 -39
- package/bin/api.js +15 -3
- package/bin/api.js.map +1 -1
- package/bin/config.js +8 -6
- package/bin/config.js.map +1 -1
- package/bin/ditto.js +1 -1
- package/bin/init/project.js +3 -3
- package/bin/init/project.js.map +1 -1
- package/bin/pull.js +138 -57
- package/bin/pull.js.map +1 -1
- package/bin/types.js +2 -2
- package/bin/types.js.map +1 -1
- package/bin/utils/createSentryContext.js +2 -2
- package/bin/utils/createSentryContext.js.map +1 -1
- package/bin/utils/generateJsDriver.js +29 -5
- package/bin/utils/generateJsDriver.js.map +1 -1
- package/lib/api.ts +17 -1
- package/lib/config.ts +19 -6
- package/lib/init/project.ts +1 -1
- package/lib/pull.ts +205 -73
- package/lib/types.ts +24 -7
- package/lib/utils/generateJsDriver.ts +40 -6
- package/package.json +2 -2
- package/bin/sentry-test.js.map +0 -1
package/lib/config.ts
CHANGED
|
@@ -5,12 +5,12 @@ import yaml from "js-yaml";
|
|
|
5
5
|
import * as Sentry from "@sentry/node";
|
|
6
6
|
|
|
7
7
|
import consts from "./consts";
|
|
8
|
-
import { Project, ConfigYAML } from "./types";
|
|
9
8
|
import { createSentryContext } from "./utils/createSentryContext";
|
|
9
|
+
import { Project, ConfigYAML, SourceInformation } from "./types";
|
|
10
10
|
|
|
11
11
|
export const DEFAULT_CONFIG_JSON: ConfigYAML = {
|
|
12
12
|
sources: {
|
|
13
|
-
components:
|
|
13
|
+
components: true,
|
|
14
14
|
},
|
|
15
15
|
variants: true,
|
|
16
16
|
format: "flat",
|
|
@@ -188,7 +188,7 @@ function dedupeProjectName(projectNames: Set<string>, projectName: string) {
|
|
|
188
188
|
* - an array of valid, deduped projects
|
|
189
189
|
* - the `variants` and `format` config options
|
|
190
190
|
*/
|
|
191
|
-
function parseSourceInformation(file?: string) {
|
|
191
|
+
function parseSourceInformation(file?: string): SourceInformation {
|
|
192
192
|
const {
|
|
193
193
|
sources,
|
|
194
194
|
variants,
|
|
@@ -222,8 +222,20 @@ function parseSourceInformation(file?: string) {
|
|
|
222
222
|
validProjects.push(project);
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
-
const shouldFetchComponentLibrary = Boolean(sources?.components
|
|
226
|
-
|
|
225
|
+
const shouldFetchComponentLibrary = Boolean(sources?.components);
|
|
226
|
+
const componentRoot =
|
|
227
|
+
typeof sources?.components === "object"
|
|
228
|
+
? sources.components.root
|
|
229
|
+
: undefined;
|
|
230
|
+
const componentFolders =
|
|
231
|
+
typeof sources?.components === "object"
|
|
232
|
+
? sources.components.folders
|
|
233
|
+
: undefined;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* If it's not specified to fetch projects or the component library, then there
|
|
237
|
+
* is no source data to pull.
|
|
238
|
+
*/
|
|
227
239
|
const hasSourceData = !!validProjects.length || shouldFetchComponentLibrary;
|
|
228
240
|
|
|
229
241
|
const result = {
|
|
@@ -237,7 +249,8 @@ function parseSourceInformation(file?: string) {
|
|
|
237
249
|
hasTopLevelProjectsField: !!projectsRoot,
|
|
238
250
|
hasTopLevelComponentsField: !!componentsRoot,
|
|
239
251
|
hasComponentLibraryInProjects,
|
|
240
|
-
|
|
252
|
+
componentRoot,
|
|
253
|
+
componentFolders,
|
|
241
254
|
};
|
|
242
255
|
|
|
243
256
|
Sentry.setContext("config", createSentryContext(result));
|
package/lib/init/project.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { quit } from "../utils/quit";
|
|
|
17
17
|
function saveProject(file: string, name: string, id: string) {
|
|
18
18
|
if (id === "components") {
|
|
19
19
|
config.writeProjectConfigData(file, {
|
|
20
|
-
sources: { components:
|
|
20
|
+
sources: { components: true },
|
|
21
21
|
});
|
|
22
22
|
return;
|
|
23
23
|
}
|
package/lib/pull.ts
CHANGED
|
@@ -12,11 +12,25 @@ import { collectAndSaveToken } from "./init/token";
|
|
|
12
12
|
import sourcesToText from "./utils/sourcesToText";
|
|
13
13
|
import { generateJsDriver } from "./utils/generateJsDriver";
|
|
14
14
|
import { cleanFileName } from "./utils/cleanFileName";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
SourceInformation,
|
|
17
|
+
Token,
|
|
18
|
+
Project,
|
|
19
|
+
SupportedFormat,
|
|
20
|
+
ComponentFolder,
|
|
21
|
+
ComponentSource,
|
|
22
|
+
Source,
|
|
23
|
+
} from "./types";
|
|
16
24
|
import { fetchVariants } from "./http/fetchVariants";
|
|
17
|
-
import { kMaxLength } from "buffer";
|
|
18
25
|
import { quit } from "./utils/quit";
|
|
19
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));
|
|
20
34
|
|
|
21
35
|
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
22
36
|
"flat",
|
|
@@ -41,7 +55,7 @@ const FORMAT_EXTENSIONS = {
|
|
|
41
55
|
const getJsonFormatIsValid = (data: string) => {
|
|
42
56
|
try {
|
|
43
57
|
return Object.keys(JSON.parse(data)).some(
|
|
44
|
-
(k) => !k.startsWith("__variant")
|
|
58
|
+
(k) => !k.startsWith("__variant"),
|
|
45
59
|
);
|
|
46
60
|
} catch {
|
|
47
61
|
return false;
|
|
@@ -59,12 +73,12 @@ export const getFormatDataIsValid = {
|
|
|
59
73
|
};
|
|
60
74
|
|
|
61
75
|
const getFormat = (
|
|
62
|
-
formatFromSource: string | string[] | undefined
|
|
76
|
+
formatFromSource: string | string[] | undefined,
|
|
63
77
|
): SupportedFormat[] => {
|
|
64
78
|
const formats = (
|
|
65
79
|
Array.isArray(formatFromSource) ? formatFromSource : [formatFromSource]
|
|
66
80
|
).filter((format) =>
|
|
67
|
-
SUPPORTED_FORMATS.includes(format as SupportedFormat)
|
|
81
|
+
SUPPORTED_FORMATS.includes(format as SupportedFormat),
|
|
68
82
|
) as SupportedFormat[];
|
|
69
83
|
|
|
70
84
|
if (formats.length) {
|
|
@@ -109,18 +123,26 @@ async function downloadAndSaveVariant(
|
|
|
109
123
|
format: SupportedFormat,
|
|
110
124
|
status: string | undefined,
|
|
111
125
|
richText: boolean | undefined,
|
|
112
|
-
token?: Token
|
|
126
|
+
token?: Token,
|
|
113
127
|
) {
|
|
114
128
|
const api = createApiClient();
|
|
115
129
|
const params: Record<string, string | null> = { variant: variantApiId };
|
|
116
130
|
if (format) params.format = format;
|
|
117
|
-
if (status) params.status = status;
|
|
118
131
|
if (richText) params.includeRichText = richText.toString();
|
|
119
132
|
|
|
133
|
+
// Root-level status gets set as the default if specified
|
|
134
|
+
if (status) params.status = status;
|
|
135
|
+
|
|
120
136
|
const savedMessages = await Promise.all(
|
|
121
|
-
projects.map(async (
|
|
122
|
-
const
|
|
123
|
-
|
|
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,
|
|
124
146
|
headers: { Authorization: `token ${token}` },
|
|
125
147
|
});
|
|
126
148
|
|
|
@@ -131,7 +153,7 @@ async function downloadAndSaveVariant(
|
|
|
131
153
|
const extension = getFormatExtension(format);
|
|
132
154
|
|
|
133
155
|
const filename = cleanFileName(
|
|
134
|
-
fileName + ("__" + (variantApiId || "base")) + extension
|
|
156
|
+
project.fileName + ("__" + (variantApiId || "base")) + extension,
|
|
135
157
|
);
|
|
136
158
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
137
159
|
|
|
@@ -145,9 +167,9 @@ async function downloadAndSaveVariant(
|
|
|
145
167
|
return "";
|
|
146
168
|
}
|
|
147
169
|
|
|
148
|
-
|
|
170
|
+
await writeFile(filepath, dataString);
|
|
149
171
|
return getSavedMessage(filename);
|
|
150
|
-
})
|
|
172
|
+
}),
|
|
151
173
|
);
|
|
152
174
|
|
|
153
175
|
return savedMessages.join("");
|
|
@@ -159,12 +181,12 @@ async function downloadAndSaveVariants(
|
|
|
159
181
|
format: SupportedFormat,
|
|
160
182
|
status: string | undefined,
|
|
161
183
|
richText: boolean | undefined,
|
|
162
|
-
token?: Token
|
|
184
|
+
token?: Token,
|
|
163
185
|
) {
|
|
164
186
|
const messages = await Promise.all([
|
|
165
187
|
downloadAndSaveVariant(null, projects, format, status, richText, token),
|
|
166
188
|
...variants.map(({ apiID }: { apiID: string }) =>
|
|
167
|
-
downloadAndSaveVariant(apiID, projects, format, status, richText, token)
|
|
189
|
+
downloadAndSaveVariant(apiID, projects, format, status, richText, token),
|
|
168
190
|
),
|
|
169
191
|
]);
|
|
170
192
|
|
|
@@ -177,23 +199,31 @@ async function downloadAndSaveBase(
|
|
|
177
199
|
status: string | undefined,
|
|
178
200
|
richText?: boolean | undefined,
|
|
179
201
|
token?: Token,
|
|
180
|
-
options?: PullOptions
|
|
202
|
+
options?: PullOptions,
|
|
181
203
|
) {
|
|
182
204
|
const api = createApiClient();
|
|
183
205
|
const params = { ...options?.meta };
|
|
184
206
|
if (format) params.format = format;
|
|
185
|
-
if (status) params.status = status;
|
|
186
207
|
if (richText) params.includeRichText = richText.toString();
|
|
187
208
|
|
|
209
|
+
// Root-level status gets set as the default if specified
|
|
210
|
+
if (status) params.status = status;
|
|
211
|
+
|
|
188
212
|
const savedMessages = await Promise.all(
|
|
189
|
-
projects.map(async (
|
|
190
|
-
const
|
|
191
|
-
|
|
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,
|
|
192
222
|
headers: { Authorization: `token ${token}` },
|
|
193
223
|
});
|
|
194
224
|
|
|
195
225
|
const extension = getFormatExtension(format);
|
|
196
|
-
const filename = cleanFileName(`${fileName}__base${extension}`);
|
|
226
|
+
const filename = cleanFileName(`${project.fileName}__base${extension}`);
|
|
197
227
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
198
228
|
|
|
199
229
|
let dataString = data;
|
|
@@ -206,9 +236,9 @@ async function downloadAndSaveBase(
|
|
|
206
236
|
return "";
|
|
207
237
|
}
|
|
208
238
|
|
|
209
|
-
|
|
239
|
+
await writeFile(filepath, dataString);
|
|
210
240
|
return getSavedMessage(filename);
|
|
211
|
-
})
|
|
241
|
+
}),
|
|
212
242
|
);
|
|
213
243
|
|
|
214
244
|
return savedMessages.join("");
|
|
@@ -236,16 +266,27 @@ function cleanOutputFiles() {
|
|
|
236
266
|
async function downloadAndSave(
|
|
237
267
|
source: SourceInformation,
|
|
238
268
|
token?: Token,
|
|
239
|
-
options?: PullOptions
|
|
269
|
+
options?: PullOptions,
|
|
240
270
|
) {
|
|
241
271
|
const api = createApiClient();
|
|
272
|
+
|
|
273
|
+
if (process.env.DEBUG_CLI) {
|
|
274
|
+
try {
|
|
275
|
+
await api.get("/health");
|
|
276
|
+
console.debug("Can connect to api.dittowords.com");
|
|
277
|
+
} catch {
|
|
278
|
+
console.debug("CANNOT connect to api.dittowords.com");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
242
282
|
const {
|
|
243
283
|
validProjects,
|
|
244
284
|
format: formatFromSource,
|
|
245
285
|
shouldFetchComponentLibrary,
|
|
246
286
|
status,
|
|
247
287
|
richText,
|
|
248
|
-
componentFolders,
|
|
288
|
+
componentFolders: specifiedComponentFolders,
|
|
289
|
+
componentRoot,
|
|
249
290
|
} = source;
|
|
250
291
|
|
|
251
292
|
const formats = getFormat(formatFromSource);
|
|
@@ -254,17 +295,81 @@ async function downloadAndSave(
|
|
|
254
295
|
const spinner = ora(msg);
|
|
255
296
|
spinner.start();
|
|
256
297
|
|
|
257
|
-
const variants = await
|
|
298
|
+
const [variants, allComponentFoldersResponse] = await Promise.all([
|
|
299
|
+
fetchVariants(source),
|
|
300
|
+
fetchComponentFolders(),
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const allComponentFolders = Object.entries(
|
|
304
|
+
allComponentFoldersResponse,
|
|
305
|
+
).reduce(
|
|
306
|
+
(acc, [id, name]) => acc.concat([{ id, name }]),
|
|
307
|
+
[] as ComponentFolder[],
|
|
308
|
+
);
|
|
258
309
|
|
|
259
310
|
try {
|
|
260
311
|
msg += cleanOutputFiles();
|
|
261
312
|
msg += `\nFetching the latest text from ${sourcesToText(
|
|
262
313
|
validProjects,
|
|
263
|
-
shouldFetchComponentLibrary
|
|
314
|
+
shouldFetchComponentLibrary,
|
|
264
315
|
)}\n`;
|
|
265
316
|
|
|
266
317
|
const meta = options ? options.meta : {};
|
|
267
318
|
|
|
319
|
+
const rootRequest = {
|
|
320
|
+
id: "__root__",
|
|
321
|
+
name: "Root",
|
|
322
|
+
// componentRoot can be a boolean or an object
|
|
323
|
+
status:
|
|
324
|
+
typeof source.componentRoot === "object"
|
|
325
|
+
? source.componentRoot.status
|
|
326
|
+
: undefined,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
let componentFolderRequests: ComponentFolder[] = [];
|
|
330
|
+
|
|
331
|
+
// there's a lot of complex logic here, and it's tempting to want to
|
|
332
|
+
// simplify it. however, it's difficult to get rid of the complexity
|
|
333
|
+
// without sacrificing specificity and expressiveness.
|
|
334
|
+
//
|
|
335
|
+
// if folders specified..
|
|
336
|
+
if (specifiedComponentFolders) {
|
|
337
|
+
switch (componentRoot) {
|
|
338
|
+
// .. and no root specified, you only get components in the specified folders
|
|
339
|
+
case undefined:
|
|
340
|
+
case false:
|
|
341
|
+
componentFolderRequests.push(...specifiedComponentFolders);
|
|
342
|
+
break;
|
|
343
|
+
// .. and root specified, you get components in folders and the root
|
|
344
|
+
default:
|
|
345
|
+
componentFolderRequests.push(...specifiedComponentFolders);
|
|
346
|
+
componentFolderRequests.push(rootRequest);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// if no folders specified..
|
|
351
|
+
else {
|
|
352
|
+
switch (componentRoot) {
|
|
353
|
+
// .. and no root specified, you get all components including those in folders
|
|
354
|
+
case undefined:
|
|
355
|
+
componentFolderRequests.push(...allComponentFolders);
|
|
356
|
+
componentFolderRequests.push(rootRequest);
|
|
357
|
+
break;
|
|
358
|
+
// .. and root specified as false, you only get components in folders
|
|
359
|
+
case false:
|
|
360
|
+
componentFolderRequests.push(...allComponentFolders);
|
|
361
|
+
break;
|
|
362
|
+
// .. and root specified as true or config object, you only get components in the root
|
|
363
|
+
default:
|
|
364
|
+
componentFolderRequests.push(rootRequest);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// this array is populated while fetching from the component library and is used when
|
|
370
|
+
// generating the index.js driver file
|
|
371
|
+
const componentSources: ComponentSource[] = [];
|
|
372
|
+
|
|
268
373
|
async function fetchComponentLibrary(format: SupportedFormat) {
|
|
269
374
|
// Always include a variant with an apiID of undefined to ensure that we
|
|
270
375
|
// fetch the base text for the component library.
|
|
@@ -274,41 +379,70 @@ async function downloadAndSave(
|
|
|
274
379
|
if (options?.meta)
|
|
275
380
|
Object.entries(options.meta).forEach(([k, v]) => params.append(k, v));
|
|
276
381
|
if (format) params.append("format", format);
|
|
277
|
-
if (status) params.append("status", status);
|
|
278
382
|
if (richText) params.append("includeRichText", richText.toString());
|
|
279
|
-
if (componentFolders) {
|
|
280
|
-
componentFolders.forEach(({ id }) => params.append("folder_id[]", id));
|
|
281
|
-
}
|
|
282
383
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const p = new URLSearchParams(params);
|
|
286
|
-
if (variantApiId) p.append("variant", variantApiId);
|
|
384
|
+
// Root-level status gets set as the default if specified
|
|
385
|
+
if (status) params.append("status", status);
|
|
287
386
|
|
|
288
|
-
|
|
387
|
+
const messagePromises: Promise<string>[] = [];
|
|
289
388
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
389
|
+
componentVariants.forEach(({ apiID: variantApiId }) => {
|
|
390
|
+
messagePromises.push(
|
|
391
|
+
...componentFolderRequests.map(async (componentFolder) => {
|
|
392
|
+
const componentFolderParams = new URLSearchParams(params);
|
|
293
393
|
|
|
294
|
-
|
|
295
|
-
|
|
394
|
+
if (variantApiId)
|
|
395
|
+
componentFolderParams.append("variant", variantApiId);
|
|
296
396
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
397
|
+
// If folder-level status is specified, overrides root-level status
|
|
398
|
+
if (componentFolder.status)
|
|
399
|
+
componentFolderParams.append("status", componentFolder.status);
|
|
301
400
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
401
|
+
const url =
|
|
402
|
+
componentFolder.id === "__root__"
|
|
403
|
+
? "/components?root_only=true"
|
|
404
|
+
: `/component-folders/${componentFolder.id}/components`;
|
|
306
405
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
406
|
+
const { data } = await api.get(url, {
|
|
407
|
+
params: componentFolderParams,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const nameExt = getFormatExtension(format);
|
|
411
|
+
const nameBase = "components";
|
|
412
|
+
const nameFolder = `__${componentFolder.name}`;
|
|
413
|
+
const namePostfix = `__${variantApiId || "base"}`;
|
|
414
|
+
|
|
415
|
+
const fileName = cleanFileName(
|
|
416
|
+
`${nameBase}${nameFolder}${namePostfix}${nameExt}`,
|
|
417
|
+
);
|
|
418
|
+
const filePath = path.join(consts.TEXT_DIR, fileName);
|
|
419
|
+
|
|
420
|
+
let dataString = data;
|
|
421
|
+
if (nameExt === ".json") {
|
|
422
|
+
dataString = JSON.stringify(data, null, 2);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
426
|
+
if (!dataIsValid(dataString)) {
|
|
427
|
+
return "";
|
|
428
|
+
}
|
|
311
429
|
|
|
430
|
+
await writeFile(filePath, dataString);
|
|
431
|
+
|
|
432
|
+
componentSources.push({
|
|
433
|
+
type: "components",
|
|
434
|
+
id: "ditto_component_library",
|
|
435
|
+
name: "ditto_component_library",
|
|
436
|
+
fileName,
|
|
437
|
+
variant: variantApiId || "base",
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
return getSavedMessage(fileName);
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const messages = await Promise.all(messagePromises);
|
|
312
446
|
msg += messages.join("");
|
|
313
447
|
}
|
|
314
448
|
|
|
@@ -326,7 +460,7 @@ async function downloadAndSave(
|
|
|
326
460
|
format,
|
|
327
461
|
status,
|
|
328
462
|
richText,
|
|
329
|
-
token
|
|
463
|
+
token,
|
|
330
464
|
)
|
|
331
465
|
: await downloadAndSaveBase(
|
|
332
466
|
validProjects,
|
|
@@ -336,7 +470,7 @@ async function downloadAndSave(
|
|
|
336
470
|
token,
|
|
337
471
|
{
|
|
338
472
|
meta,
|
|
339
|
-
}
|
|
473
|
+
},
|
|
340
474
|
);
|
|
341
475
|
}
|
|
342
476
|
|
|
@@ -346,14 +480,7 @@ async function downloadAndSave(
|
|
|
346
480
|
}
|
|
347
481
|
}
|
|
348
482
|
|
|
349
|
-
const sources = [...validProjects];
|
|
350
|
-
if (shouldFetchComponentLibrary) {
|
|
351
|
-
sources.push({
|
|
352
|
-
id: "ditto_component_library",
|
|
353
|
-
name: "Ditto Component Library",
|
|
354
|
-
fileName: "ditto-component-library",
|
|
355
|
-
});
|
|
356
|
-
}
|
|
483
|
+
const sources: Source[] = [...validProjects, ...componentSources];
|
|
357
484
|
|
|
358
485
|
if (formats.some((f) => JSON_FORMATS.includes(f)))
|
|
359
486
|
msg += generateJsDriver(sources);
|
|
@@ -375,7 +502,7 @@ async function downloadAndSave(
|
|
|
375
502
|
if (e.response && e.response.status === 401) {
|
|
376
503
|
error = "You don't have access to the selected projects";
|
|
377
504
|
msg = `${output.errorText(error)}.\nChoose others using the ${output.info(
|
|
378
|
-
"project"
|
|
505
|
+
"project",
|
|
379
506
|
)} command, or update your API key.`;
|
|
380
507
|
return console.log(msg);
|
|
381
508
|
}
|
|
@@ -383,11 +510,11 @@ async function downloadAndSave(
|
|
|
383
510
|
error =
|
|
384
511
|
"One or more of the requested projects don't have Developer Mode enabled";
|
|
385
512
|
msg = `${output.errorText(
|
|
386
|
-
error
|
|
513
|
+
error,
|
|
387
514
|
)}.\nPlease choose different projects using the ${output.info(
|
|
388
|
-
"project"
|
|
515
|
+
"project",
|
|
389
516
|
)} command, or turn on Developer Mode for all selected projects. Learn more here: ${output.subtle(
|
|
390
|
-
"https://www.dittowords.com/docs/ditto-developer-mode"
|
|
517
|
+
"https://www.dittowords.com/docs/ditto-developer-mode",
|
|
391
518
|
)}.`;
|
|
392
519
|
return console.log(msg);
|
|
393
520
|
}
|
|
@@ -395,7 +522,7 @@ async function downloadAndSave(
|
|
|
395
522
|
error = "projects not found";
|
|
396
523
|
}
|
|
397
524
|
msg = `We hit an error fetching text from the projects: ${output.errorText(
|
|
398
|
-
error
|
|
525
|
+
error,
|
|
399
526
|
)}.\nChoose others using the ${output.info("project")} command.`;
|
|
400
527
|
return console.log(msg);
|
|
401
528
|
}
|
|
@@ -410,22 +537,27 @@ export const pull = async (options?: PullOptions) => {
|
|
|
410
537
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
411
538
|
const sourceInformation = config.parseSourceInformation();
|
|
412
539
|
|
|
540
|
+
if (process.env.DEBUG_CLI === "true") {
|
|
541
|
+
console.debug(`Token: ${token}`);
|
|
542
|
+
}
|
|
543
|
+
|
|
413
544
|
try {
|
|
414
545
|
return await downloadAndSave(sourceInformation, token, { meta });
|
|
415
546
|
} catch (e) {
|
|
416
|
-
Sentry.captureException(e);
|
|
547
|
+
const eventId = Sentry.captureException(e);
|
|
548
|
+
const eventStr = `\n\nError ID: ${output.info(eventId)}`;
|
|
417
549
|
if (e instanceof AxiosError) {
|
|
418
550
|
return quit(
|
|
419
551
|
output.errorText(
|
|
420
|
-
"Something went wrong connecting to Ditto servers. Please contact support or try again later."
|
|
421
|
-
)
|
|
552
|
+
"Something went wrong connecting to Ditto servers. Please contact support or try again later.",
|
|
553
|
+
) + eventStr,
|
|
422
554
|
);
|
|
423
555
|
}
|
|
424
556
|
|
|
425
557
|
return quit(
|
|
426
558
|
output.errorText(
|
|
427
|
-
"Something went wrong. Please contact support or try again later."
|
|
428
|
-
)
|
|
559
|
+
"Something went wrong. Please contact support or try again later.",
|
|
560
|
+
) + eventStr,
|
|
429
561
|
);
|
|
430
562
|
}
|
|
431
563
|
};
|
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;
|
|
@@ -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/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dittowords/cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1-alpha.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
10
|
"start": "etsc && node bin/ditto.js",
|
|
11
11
|
"sync": "etsc && node bin/ditto.js pull",
|