@dittowords/cli 4.0.1-alpha.0 → 4.1.0-alpha
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 +27 -371
- package/bin/api.js +3 -15
- package/bin/api.js.map +1 -1
- package/bin/config.js +5 -3
- package/bin/config.js.map +1 -1
- package/bin/generate-swift-struct.js +6 -0
- package/bin/generate-swift-struct.js.map +1 -0
- package/bin/http/fetchComponentFolders.js +3 -3
- package/bin/http/fetchComponentFolders.js.map +1 -1
- package/bin/http/fetchComponents.js +13 -5
- package/bin/http/fetchComponents.js.map +1 -1
- package/bin/http/fetchVariants.js +3 -3
- package/bin/http/fetchVariants.js.map +1 -1
- package/bin/init/project.js +3 -3
- package/bin/init/project.js.map +1 -1
- package/bin/pull.js +82 -49
- package/bin/pull.js.map +1 -1
- package/bin/pull.test.js +26 -24
- package/bin/pull.test.js.map +1 -1
- package/bin/sentry-test.js.map +1 -0
- package/bin/types.js +2 -2
- package/bin/types.js.map +1 -1
- package/bin/utils/determineModuleType.js +80 -0
- package/bin/utils/determineModuleType.js.map +1 -0
- package/bin/utils/generateIOSBundles.js +147 -0
- package/bin/utils/generateIOSBundles.js.map +1 -0
- package/bin/utils/generateJsDriver.js +117 -58
- package/bin/utils/generateJsDriver.js.map +1 -1
- package/bin/utils/generateJsDriverTypeFile.js +105 -0
- package/bin/utils/generateJsDriverTypeFile.js.map +1 -0
- package/bin/utils/generateSwiftDriver.js +93 -0
- package/bin/utils/generateSwiftDriver.js.map +1 -0
- package/lib/api.ts +1 -17
- package/lib/config.ts +4 -0
- package/lib/http/fetchComponentFolders.ts +1 -1
- package/lib/http/fetchComponents.ts +14 -9
- package/lib/http/fetchVariants.ts +1 -1
- package/lib/init/project.ts +1 -1
- package/lib/pull.test.ts +24 -22
- package/lib/pull.ts +127 -90
- package/lib/types.ts +4 -0
- package/lib/utils/determineModuleType.ts +57 -0
- package/lib/utils/generateIOSBundles.ts +122 -0
- package/lib/utils/generateJsDriver.ts +156 -51
- package/lib/utils/generateJsDriverTypeFile.ts +75 -0
- package/lib/utils/generateSwiftDriver.ts +48 -0
- package/package.json +1 -1
package/lib/pull.ts
CHANGED
|
@@ -25,11 +25,26 @@ import { fetchVariants } from "./http/fetchVariants";
|
|
|
25
25
|
import { quit } from "./utils/quit";
|
|
26
26
|
import { AxiosError } from "axios";
|
|
27
27
|
import { fetchComponentFolders } from "./http/fetchComponentFolders";
|
|
28
|
+
import { generateSwiftDriver } from "./utils/generateSwiftDriver";
|
|
29
|
+
import { generateIOSBundles } from "./utils/generateIOSBundles";
|
|
30
|
+
|
|
31
|
+
interface IRequestOptions {
|
|
32
|
+
projects: Project[];
|
|
33
|
+
format: SupportedFormat;
|
|
34
|
+
status: string | undefined;
|
|
35
|
+
richText?: boolean | undefined;
|
|
36
|
+
token?: Token;
|
|
37
|
+
options?: PullOptions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface IRequestOptionsWithVariants extends IRequestOptions {
|
|
41
|
+
variants: { apiID: string }[];
|
|
42
|
+
}
|
|
28
43
|
|
|
29
44
|
const ensureEndsWithNewLine = (str: string) =>
|
|
30
45
|
str + (/[\r\n]$/.test(str) ? "" : "\n");
|
|
31
46
|
|
|
32
|
-
const writeFile = (path: string, data: string) =>
|
|
47
|
+
export const writeFile = (path: string, data: string) =>
|
|
33
48
|
new Promise((r) => fs.writeFile(path, ensureEndsWithNewLine(data), r));
|
|
34
49
|
|
|
35
50
|
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
@@ -41,7 +56,20 @@ const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
|
41
56
|
"icu",
|
|
42
57
|
];
|
|
43
58
|
|
|
44
|
-
|
|
59
|
+
export type JSONFormat = "flat" | "nested" | "structured" | "icu";
|
|
60
|
+
|
|
61
|
+
const IOS_FORMATS: SupportedFormat[] = ["ios-strings", "ios-stringsdict"];
|
|
62
|
+
const JSON_FORMATS: JSONFormat[] = ["flat", "structured", "icu"];
|
|
63
|
+
|
|
64
|
+
const getJsonFormat = (formats: string[]): JSONFormat => {
|
|
65
|
+
// edge case: multiple json formats specified
|
|
66
|
+
// we should grab the last one
|
|
67
|
+
const jsonFormats = formats.filter((f) =>
|
|
68
|
+
JSON_FORMATS.includes(f as JSONFormat)
|
|
69
|
+
) as JSONFormat[];
|
|
70
|
+
|
|
71
|
+
return jsonFormats[jsonFormats.length - 1] || "flat";
|
|
72
|
+
};
|
|
45
73
|
|
|
46
74
|
const FORMAT_EXTENSIONS = {
|
|
47
75
|
flat: ".json",
|
|
@@ -55,7 +83,7 @@ const FORMAT_EXTENSIONS = {
|
|
|
55
83
|
const getJsonFormatIsValid = (data: string) => {
|
|
56
84
|
try {
|
|
57
85
|
return Object.keys(JSON.parse(data)).some(
|
|
58
|
-
(k) => !k.startsWith("__variant")
|
|
86
|
+
(k) => !k.startsWith("__variant")
|
|
59
87
|
);
|
|
60
88
|
} catch {
|
|
61
89
|
return false;
|
|
@@ -73,12 +101,12 @@ export const getFormatDataIsValid = {
|
|
|
73
101
|
};
|
|
74
102
|
|
|
75
103
|
const getFormat = (
|
|
76
|
-
formatFromSource: string | string[] | undefined
|
|
104
|
+
formatFromSource: string | string[] | undefined
|
|
77
105
|
): SupportedFormat[] => {
|
|
78
106
|
const formats = (
|
|
79
107
|
Array.isArray(formatFromSource) ? formatFromSource : [formatFromSource]
|
|
80
108
|
).filter((format) =>
|
|
81
|
-
SUPPORTED_FORMATS.includes(format as SupportedFormat)
|
|
109
|
+
SUPPORTED_FORMATS.includes(format as SupportedFormat)
|
|
82
110
|
) as SupportedFormat[];
|
|
83
111
|
|
|
84
112
|
if (formats.length) {
|
|
@@ -119,12 +147,9 @@ async function askForAnotherToken() {
|
|
|
119
147
|
*/
|
|
120
148
|
async function downloadAndSaveVariant(
|
|
121
149
|
variantApiId: string | null,
|
|
122
|
-
|
|
123
|
-
format: SupportedFormat,
|
|
124
|
-
status: string | undefined,
|
|
125
|
-
richText: boolean | undefined,
|
|
126
|
-
token?: Token,
|
|
150
|
+
requestOptions: IRequestOptions
|
|
127
151
|
) {
|
|
152
|
+
const { projects, format, status, richText, token } = requestOptions;
|
|
128
153
|
const api = createApiClient();
|
|
129
154
|
const params: Record<string, string | null> = { variant: variantApiId };
|
|
130
155
|
if (format) params.format = format;
|
|
@@ -141,7 +166,7 @@ async function downloadAndSaveVariant(
|
|
|
141
166
|
if (project.exclude_components)
|
|
142
167
|
projectParams.exclude_components = String(project.exclude_components);
|
|
143
168
|
|
|
144
|
-
const { data } = await api.get(`/projects/${project.id}`, {
|
|
169
|
+
const { data } = await api.get(`/v1/projects/${project.id}`, {
|
|
145
170
|
params: projectParams,
|
|
146
171
|
headers: { Authorization: `token ${token}` },
|
|
147
172
|
});
|
|
@@ -153,7 +178,7 @@ async function downloadAndSaveVariant(
|
|
|
153
178
|
const extension = getFormatExtension(format);
|
|
154
179
|
|
|
155
180
|
const filename = cleanFileName(
|
|
156
|
-
project.fileName + ("__" + (variantApiId || "base")) + extension
|
|
181
|
+
project.fileName + ("__" + (variantApiId || "base")) + extension
|
|
157
182
|
);
|
|
158
183
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
159
184
|
|
|
@@ -169,38 +194,28 @@ async function downloadAndSaveVariant(
|
|
|
169
194
|
|
|
170
195
|
await writeFile(filepath, dataString);
|
|
171
196
|
return getSavedMessage(filename);
|
|
172
|
-
})
|
|
197
|
+
})
|
|
173
198
|
);
|
|
174
199
|
|
|
175
200
|
return savedMessages.join("");
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
async function downloadAndSaveVariants(
|
|
179
|
-
|
|
180
|
-
projects: Project[],
|
|
181
|
-
format: SupportedFormat,
|
|
182
|
-
status: string | undefined,
|
|
183
|
-
richText: boolean | undefined,
|
|
184
|
-
token?: Token,
|
|
204
|
+
requestOptions: IRequestOptionsWithVariants
|
|
185
205
|
) {
|
|
186
206
|
const messages = await Promise.all([
|
|
187
|
-
downloadAndSaveVariant(null,
|
|
188
|
-
...variants.map(({ apiID }: { apiID: string }) =>
|
|
189
|
-
downloadAndSaveVariant(apiID,
|
|
207
|
+
downloadAndSaveVariant(null, requestOptions),
|
|
208
|
+
...requestOptions.variants.map(({ apiID }: { apiID: string }) =>
|
|
209
|
+
downloadAndSaveVariant(apiID, requestOptions)
|
|
190
210
|
),
|
|
191
211
|
]);
|
|
192
212
|
|
|
193
213
|
return messages.join("");
|
|
194
214
|
}
|
|
195
215
|
|
|
196
|
-
async function downloadAndSaveBase(
|
|
197
|
-
projects
|
|
198
|
-
|
|
199
|
-
status: string | undefined,
|
|
200
|
-
richText?: boolean | undefined,
|
|
201
|
-
token?: Token,
|
|
202
|
-
options?: PullOptions,
|
|
203
|
-
) {
|
|
216
|
+
async function downloadAndSaveBase(requestOptions: IRequestOptions) {
|
|
217
|
+
const { projects, format, status, richText, token, options } = requestOptions;
|
|
218
|
+
|
|
204
219
|
const api = createApiClient();
|
|
205
220
|
const params = { ...options?.meta };
|
|
206
221
|
if (format) params.format = format;
|
|
@@ -217,7 +232,7 @@ async function downloadAndSaveBase(
|
|
|
217
232
|
if (project.exclude_components)
|
|
218
233
|
projectParams.exclude_components = String(project.exclude_components);
|
|
219
234
|
|
|
220
|
-
const { data } = await api.get(`/projects/${project.id}`, {
|
|
235
|
+
const { data } = await api.get(`/v1/projects/${project.id}`, {
|
|
221
236
|
params: projectParams,
|
|
222
237
|
headers: { Authorization: `token ${token}` },
|
|
223
238
|
});
|
|
@@ -238,7 +253,7 @@ async function downloadAndSaveBase(
|
|
|
238
253
|
|
|
239
254
|
await writeFile(filepath, dataString);
|
|
240
255
|
return getSavedMessage(filename);
|
|
241
|
-
})
|
|
256
|
+
})
|
|
242
257
|
);
|
|
243
258
|
|
|
244
259
|
return savedMessages.join("");
|
|
@@ -253,10 +268,23 @@ function cleanOutputFiles() {
|
|
|
253
268
|
fs.mkdirSync(consts.TEXT_DIR);
|
|
254
269
|
}
|
|
255
270
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
const directoryContents = fs.readdirSync(consts.TEXT_DIR, {
|
|
272
|
+
withFileTypes: true,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
directoryContents.forEach((item) => {
|
|
276
|
+
if (item.isDirectory() && /\.lproj$/.test(item.name)) {
|
|
277
|
+
return fs.rmSync(path.resolve(consts.TEXT_DIR, item.name), {
|
|
278
|
+
recursive: true,
|
|
279
|
+
force: true,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
item.isFile() &&
|
|
285
|
+
/\.js(on)?|\.xml|\.strings(dict)?$|\.swift$/.test(item.name)
|
|
286
|
+
) {
|
|
287
|
+
return fs.unlinkSync(path.resolve(consts.TEXT_DIR, item.name));
|
|
260
288
|
}
|
|
261
289
|
});
|
|
262
290
|
|
|
@@ -266,19 +294,9 @@ function cleanOutputFiles() {
|
|
|
266
294
|
async function downloadAndSave(
|
|
267
295
|
source: SourceInformation,
|
|
268
296
|
token?: Token,
|
|
269
|
-
options?: PullOptions
|
|
297
|
+
options?: PullOptions
|
|
270
298
|
) {
|
|
271
299
|
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
|
-
|
|
282
300
|
const {
|
|
283
301
|
validProjects,
|
|
284
302
|
format: formatFromSource,
|
|
@@ -287,10 +305,19 @@ async function downloadAndSave(
|
|
|
287
305
|
richText,
|
|
288
306
|
componentFolders: specifiedComponentFolders,
|
|
289
307
|
componentRoot,
|
|
308
|
+
localeByVariantApiId,
|
|
290
309
|
} = source;
|
|
291
310
|
|
|
292
311
|
const formats = getFormat(formatFromSource);
|
|
293
312
|
|
|
313
|
+
const hasJSONFormat = formats.some((f) =>
|
|
314
|
+
JSON_FORMATS.includes(f as JSONFormat)
|
|
315
|
+
);
|
|
316
|
+
const hasIOSFormat = formats.some((f) => IOS_FORMATS.includes(f));
|
|
317
|
+
const shouldGenerateIOSBundles = hasIOSFormat && localeByVariantApiId;
|
|
318
|
+
|
|
319
|
+
const shouldLogOutputFiles = !shouldGenerateIOSBundles;
|
|
320
|
+
|
|
294
321
|
let msg = "";
|
|
295
322
|
const spinner = ora(msg);
|
|
296
323
|
spinner.start();
|
|
@@ -301,17 +328,17 @@ async function downloadAndSave(
|
|
|
301
328
|
]);
|
|
302
329
|
|
|
303
330
|
const allComponentFolders = Object.entries(
|
|
304
|
-
allComponentFoldersResponse
|
|
331
|
+
allComponentFoldersResponse
|
|
305
332
|
).reduce(
|
|
306
333
|
(acc, [id, name]) => acc.concat([{ id, name }]),
|
|
307
|
-
[] as ComponentFolder[]
|
|
334
|
+
[] as ComponentFolder[]
|
|
308
335
|
);
|
|
309
336
|
|
|
310
337
|
try {
|
|
311
338
|
msg += cleanOutputFiles();
|
|
312
339
|
msg += `\nFetching the latest text from ${sourcesToText(
|
|
313
340
|
validProjects,
|
|
314
|
-
shouldFetchComponentLibrary
|
|
341
|
+
shouldFetchComponentLibrary
|
|
315
342
|
)}\n`;
|
|
316
343
|
|
|
317
344
|
const meta = options ? options.meta : {};
|
|
@@ -400,8 +427,8 @@ async function downloadAndSave(
|
|
|
400
427
|
|
|
401
428
|
const url =
|
|
402
429
|
componentFolder.id === "__root__"
|
|
403
|
-
? "/components?root_only=true"
|
|
404
|
-
: `/component-folders/${componentFolder.id}/components`;
|
|
430
|
+
? "/v1/components?root_only=true"
|
|
431
|
+
: `/v1/component-folders/${componentFolder.id}/components`;
|
|
405
432
|
|
|
406
433
|
const { data } = await api.get(url, {
|
|
407
434
|
params: componentFolderParams,
|
|
@@ -413,7 +440,7 @@ async function downloadAndSave(
|
|
|
413
440
|
const namePostfix = `__${variantApiId || "base"}`;
|
|
414
441
|
|
|
415
442
|
const fileName = cleanFileName(
|
|
416
|
-
`${nameBase}${nameFolder}${namePostfix}${nameExt}
|
|
443
|
+
`${nameBase}${nameFolder}${namePostfix}${nameExt}`
|
|
417
444
|
);
|
|
418
445
|
const filePath = path.join(consts.TEXT_DIR, fileName);
|
|
419
446
|
|
|
@@ -438,12 +465,14 @@ async function downloadAndSave(
|
|
|
438
465
|
});
|
|
439
466
|
|
|
440
467
|
return getSavedMessage(fileName);
|
|
441
|
-
})
|
|
468
|
+
})
|
|
442
469
|
);
|
|
443
470
|
});
|
|
444
471
|
|
|
445
472
|
const messages = await Promise.all(messagePromises);
|
|
446
|
-
|
|
473
|
+
if (shouldLogOutputFiles) {
|
|
474
|
+
msg += messages.join("");
|
|
475
|
+
}
|
|
447
476
|
}
|
|
448
477
|
|
|
449
478
|
if (shouldFetchComponentLibrary) {
|
|
@@ -453,25 +482,32 @@ async function downloadAndSave(
|
|
|
453
482
|
}
|
|
454
483
|
|
|
455
484
|
async function fetchProjects(format: SupportedFormat) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
485
|
+
let result = "";
|
|
486
|
+
if (variants) {
|
|
487
|
+
result = await downloadAndSaveVariants({
|
|
488
|
+
variants,
|
|
489
|
+
projects: validProjects,
|
|
490
|
+
format,
|
|
491
|
+
status,
|
|
492
|
+
richText,
|
|
493
|
+
token,
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
result = await downloadAndSaveBase({
|
|
497
|
+
projects: validProjects,
|
|
498
|
+
format,
|
|
499
|
+
status,
|
|
500
|
+
richText,
|
|
501
|
+
token,
|
|
502
|
+
options: {
|
|
503
|
+
meta,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (shouldLogOutputFiles) {
|
|
509
|
+
msg += result;
|
|
510
|
+
}
|
|
475
511
|
}
|
|
476
512
|
|
|
477
513
|
if (validProjects.length) {
|
|
@@ -482,10 +518,15 @@ async function downloadAndSave(
|
|
|
482
518
|
|
|
483
519
|
const sources: Source[] = [...validProjects, ...componentSources];
|
|
484
520
|
|
|
485
|
-
if (
|
|
486
|
-
msg += generateJsDriver(sources);
|
|
521
|
+
if (hasJSONFormat) msg += generateJsDriver(sources, getJsonFormat(formats));
|
|
487
522
|
|
|
488
|
-
|
|
523
|
+
if (shouldGenerateIOSBundles) {
|
|
524
|
+
msg += "iOS locale information detected, generating bundles..\n\n";
|
|
525
|
+
msg += await generateIOSBundles(localeByVariantApiId);
|
|
526
|
+
msg += await generateSwiftDriver(source);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
msg += `\n\n${output.success("Done")}!`;
|
|
489
530
|
|
|
490
531
|
spinner.stop();
|
|
491
532
|
return console.log(msg);
|
|
@@ -502,7 +543,7 @@ async function downloadAndSave(
|
|
|
502
543
|
if (e.response && e.response.status === 401) {
|
|
503
544
|
error = "You don't have access to the selected projects";
|
|
504
545
|
msg = `${output.errorText(error)}.\nChoose others using the ${output.info(
|
|
505
|
-
"project"
|
|
546
|
+
"project"
|
|
506
547
|
)} command, or update your API key.`;
|
|
507
548
|
return console.log(msg);
|
|
508
549
|
}
|
|
@@ -510,11 +551,11 @@ async function downloadAndSave(
|
|
|
510
551
|
error =
|
|
511
552
|
"One or more of the requested projects don't have Developer Mode enabled";
|
|
512
553
|
msg = `${output.errorText(
|
|
513
|
-
error
|
|
554
|
+
error
|
|
514
555
|
)}.\nPlease choose different projects using the ${output.info(
|
|
515
|
-
"project"
|
|
556
|
+
"project"
|
|
516
557
|
)} command, or turn on Developer Mode for all selected projects. Learn more here: ${output.subtle(
|
|
517
|
-
"https://www.dittowords.com/docs/ditto-developer-mode"
|
|
558
|
+
"https://www.dittowords.com/docs/ditto-developer-mode"
|
|
518
559
|
)}.`;
|
|
519
560
|
return console.log(msg);
|
|
520
561
|
}
|
|
@@ -522,7 +563,7 @@ async function downloadAndSave(
|
|
|
522
563
|
error = "projects not found";
|
|
523
564
|
}
|
|
524
565
|
msg = `We hit an error fetching text from the projects: ${output.errorText(
|
|
525
|
-
error
|
|
566
|
+
error
|
|
526
567
|
)}.\nChoose others using the ${output.info("project")} command.`;
|
|
527
568
|
return console.log(msg);
|
|
528
569
|
}
|
|
@@ -537,10 +578,6 @@ export const pull = async (options?: PullOptions) => {
|
|
|
537
578
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
538
579
|
const sourceInformation = config.parseSourceInformation();
|
|
539
580
|
|
|
540
|
-
if (process.env.DEBUG_CLI === "true") {
|
|
541
|
-
console.debug(`Token: ${token}`);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
581
|
try {
|
|
545
582
|
return await downloadAndSave(sourceInformation, token, { meta });
|
|
546
583
|
} catch (e) {
|
|
@@ -549,15 +586,15 @@ export const pull = async (options?: PullOptions) => {
|
|
|
549
586
|
if (e instanceof AxiosError) {
|
|
550
587
|
return quit(
|
|
551
588
|
output.errorText(
|
|
552
|
-
"Something went wrong connecting to Ditto servers. Please contact support or try again later."
|
|
553
|
-
) + eventStr
|
|
589
|
+
"Something went wrong connecting to Ditto servers. Please contact support or try again later."
|
|
590
|
+
) + eventStr
|
|
554
591
|
);
|
|
555
592
|
}
|
|
556
593
|
|
|
557
594
|
return quit(
|
|
558
595
|
output.errorText(
|
|
559
|
-
"Something went wrong. Please contact support or try again later."
|
|
560
|
-
) + eventStr
|
|
596
|
+
"Something went wrong. Please contact support or try again later."
|
|
597
|
+
) + eventStr
|
|
561
598
|
);
|
|
562
599
|
}
|
|
563
600
|
};
|
package/lib/types.ts
CHANGED
|
@@ -46,6 +46,9 @@ export interface ConfigYAML {
|
|
|
46
46
|
variants?: boolean;
|
|
47
47
|
richText?: boolean;
|
|
48
48
|
|
|
49
|
+
// TODO: might want to rename this at some point
|
|
50
|
+
iosLocales?: Record<string, string>[];
|
|
51
|
+
|
|
49
52
|
// these are legacy fields - if they exist, we should output
|
|
50
53
|
// a deprecation error, and suggest that they nest them under
|
|
51
54
|
// a top-level `sources` property
|
|
@@ -66,6 +69,7 @@ export interface SourceInformation {
|
|
|
66
69
|
richText: boolean | undefined;
|
|
67
70
|
componentRoot: boolean | { status: string } | undefined;
|
|
68
71
|
componentFolders: ComponentFolder[] | undefined;
|
|
72
|
+
localeByVariantApiId: Record<string, string> | undefined;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export type Token = string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export type ModuleType = "commonjs" | "module";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Looks for a `package.json` file starting in the current working directory and traversing upwards
|
|
8
|
+
* until it finds one or reaches root.
|
|
9
|
+
* @returns "commonjs" or "module", defaulting to "module" if no `package.json` is found or if the found
|
|
10
|
+
* file does not include a `type` property.
|
|
11
|
+
*/
|
|
12
|
+
export function determineModuleType() {
|
|
13
|
+
const value = getRawTypeFromPackageJson();
|
|
14
|
+
return getTypeOrDefault(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getRawTypeFromPackageJson() {
|
|
18
|
+
if (process.env.DITTO_MODULE_TYPE) {
|
|
19
|
+
return process.env.DITTO_MODULE_TYPE;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let currentDir: string | null = process.cwd(); // Get the current working directory
|
|
23
|
+
|
|
24
|
+
while (currentDir) {
|
|
25
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
26
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
27
|
+
const packageJsonContents = fs.readFileSync(packageJsonPath, "utf8");
|
|
28
|
+
try {
|
|
29
|
+
const packageData: { type?: string } = JSON.parse(packageJsonContents);
|
|
30
|
+
if (packageData?.type) {
|
|
31
|
+
return packageData.type;
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (currentDir === "/") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Move up a directory and continue the search
|
|
43
|
+
currentDir = path.dirname(currentDir);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// No package.json
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getTypeOrDefault(value: string | null): ModuleType {
|
|
51
|
+
const valueLower = value?.toLowerCase() || "";
|
|
52
|
+
if (valueLower === "commonjs" || valueLower === "module") {
|
|
53
|
+
return valueLower;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "commonjs";
|
|
57
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import consts from "../consts";
|
|
4
|
+
import output from "../output";
|
|
5
|
+
|
|
6
|
+
const IOS_FILE_EXTENSION_PATTERN = /\.(strings|stringsdict)$/;
|
|
7
|
+
|
|
8
|
+
export async function generateIOSBundles(
|
|
9
|
+
localeByVariantApiId: Record<string, string> | undefined
|
|
10
|
+
) {
|
|
11
|
+
const files = fs.readdirSync(consts.TEXT_DIR);
|
|
12
|
+
|
|
13
|
+
const bundlesGenerated: {
|
|
14
|
+
[bundleName: string]: {
|
|
15
|
+
mappedVariant?: string;
|
|
16
|
+
};
|
|
17
|
+
} = {};
|
|
18
|
+
|
|
19
|
+
for (const fileName of files) {
|
|
20
|
+
if (!IOS_FILE_EXTENSION_PATTERN.test(fileName)) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [name, fileExtension] = fileName.split(".");
|
|
25
|
+
if (!name.length) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const parts = name.split("__");
|
|
30
|
+
const source = parts[0];
|
|
31
|
+
const variant = parts[parts.length - 1];
|
|
32
|
+
if (!(source && variant)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const bundleName =
|
|
37
|
+
localeByVariantApiId && localeByVariantApiId[variant]
|
|
38
|
+
? localeByVariantApiId[variant]
|
|
39
|
+
: variant;
|
|
40
|
+
const bundleFileName = `${bundleName}.lproj`;
|
|
41
|
+
const bundleFolder = path.join(consts.TEXT_DIR, bundleFileName);
|
|
42
|
+
if (!fs.existsSync(bundleFolder)) {
|
|
43
|
+
fs.mkdirSync(bundleFolder);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const filePathCurrent = path.join(consts.TEXT_DIR, fileName);
|
|
47
|
+
const filePathNew = path.join(bundleFolder, `${source}.${fileExtension}`);
|
|
48
|
+
|
|
49
|
+
handleBundleGeneration(source, fileExtension, filePathCurrent, filePathNew);
|
|
50
|
+
|
|
51
|
+
bundlesGenerated[bundleFileName] = {
|
|
52
|
+
mappedVariant: variant === bundleName ? undefined : variant,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
Object.keys(bundlesGenerated)
|
|
58
|
+
.map((bundleName) => {
|
|
59
|
+
let msg = `Successfully generated iOS bundle ${output.info(
|
|
60
|
+
bundleName
|
|
61
|
+
)}`;
|
|
62
|
+
const mappedVariant = bundlesGenerated[bundleName].mappedVariant;
|
|
63
|
+
if (mappedVariant) {
|
|
64
|
+
msg += ` ${output.subtle(`(mapped to variant '${mappedVariant}')`)}`;
|
|
65
|
+
}
|
|
66
|
+
return msg;
|
|
67
|
+
})
|
|
68
|
+
.join("\n") + "\n"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleBundleGeneration(
|
|
73
|
+
sourceId: string,
|
|
74
|
+
extension: string,
|
|
75
|
+
sourcePath: string,
|
|
76
|
+
newFilePath: string
|
|
77
|
+
) {
|
|
78
|
+
if (!fs.existsSync(newFilePath)) {
|
|
79
|
+
return fs.renameSync(sourcePath, newFilePath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (sourceId !== "components") {
|
|
83
|
+
throw new Error("Bundle path for " + sourceId + " already exists");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (extension === "strings") {
|
|
87
|
+
return appendStringsFile(sourcePath, newFilePath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (extension === "stringsdict") {
|
|
91
|
+
return appendStringsDictFile(sourcePath, newFilePath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new Error("Unsupported extension " + extension);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function appendStringsFile(sourcePath: string, destPath: string) {
|
|
98
|
+
const sourceContents = fs.readFileSync(sourcePath, "utf-8");
|
|
99
|
+
const newFileContents = fs.readFileSync(destPath, "utf-8");
|
|
100
|
+
const newContents = newFileContents + "\n" + sourceContents;
|
|
101
|
+
fs.writeFileSync(destPath, newContents);
|
|
102
|
+
fs.unlinkSync(sourcePath);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function appendStringsDictFile(sourcePath: string, destPath: string) {
|
|
106
|
+
const sourceContentsFull = fs.readFileSync(sourcePath, "utf-8");
|
|
107
|
+
const sourceContentsContent = sourceContentsFull.split("\n").slice(3, -4);
|
|
108
|
+
|
|
109
|
+
const newFileContentsFull = fs.readFileSync(destPath, "utf-8");
|
|
110
|
+
const newFileContentsContent = newFileContentsFull.split("\n").slice(3, -4);
|
|
111
|
+
|
|
112
|
+
const newContents = `<?xml version="1.0" encoding="utf-8"?>
|
|
113
|
+
<plist version="1.0">
|
|
114
|
+
<dict>
|
|
115
|
+
${[newFileContentsContent, sourceContentsContent].join("\n")}
|
|
116
|
+
</dict>
|
|
117
|
+
</plist>
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
fs.writeFileSync(destPath, newContents);
|
|
121
|
+
fs.unlinkSync(sourcePath);
|
|
122
|
+
}
|