@dittowords/cli 4.0.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 +29 -364
- 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 -38
- package/bin/pull.js.map +1 -1
- package/bin/pull.test.js +26 -24
- package/bin/pull.test.js.map +1 -1
- 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/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 +106 -55
- 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",
|
|
@@ -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
|
});
|
|
@@ -176,31 +201,21 @@ async function downloadAndSaveVariant(
|
|
|
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
|
});
|
|
@@ -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
|
|
|
@@ -277,10 +305,19 @@ async function downloadAndSave(
|
|
|
277
305
|
richText,
|
|
278
306
|
componentFolders: specifiedComponentFolders,
|
|
279
307
|
componentRoot,
|
|
308
|
+
localeByVariantApiId,
|
|
280
309
|
} = source;
|
|
281
310
|
|
|
282
311
|
const formats = getFormat(formatFromSource);
|
|
283
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
|
+
|
|
284
321
|
let msg = "";
|
|
285
322
|
const spinner = ora(msg);
|
|
286
323
|
spinner.start();
|
|
@@ -390,8 +427,8 @@ async function downloadAndSave(
|
|
|
390
427
|
|
|
391
428
|
const url =
|
|
392
429
|
componentFolder.id === "__root__"
|
|
393
|
-
? "/components?root_only=true"
|
|
394
|
-
: `/component-folders/${componentFolder.id}/components`;
|
|
430
|
+
? "/v1/components?root_only=true"
|
|
431
|
+
: `/v1/component-folders/${componentFolder.id}/components`;
|
|
395
432
|
|
|
396
433
|
const { data } = await api.get(url, {
|
|
397
434
|
params: componentFolderParams,
|
|
@@ -433,7 +470,9 @@ async function downloadAndSave(
|
|
|
433
470
|
});
|
|
434
471
|
|
|
435
472
|
const messages = await Promise.all(messagePromises);
|
|
436
|
-
|
|
473
|
+
if (shouldLogOutputFiles) {
|
|
474
|
+
msg += messages.join("");
|
|
475
|
+
}
|
|
437
476
|
}
|
|
438
477
|
|
|
439
478
|
if (shouldFetchComponentLibrary) {
|
|
@@ -443,25 +482,32 @@ async function downloadAndSave(
|
|
|
443
482
|
}
|
|
444
483
|
|
|
445
484
|
async function fetchProjects(format: SupportedFormat) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
+
}
|
|
465
511
|
}
|
|
466
512
|
|
|
467
513
|
if (validProjects.length) {
|
|
@@ -472,10 +518,15 @@ async function downloadAndSave(
|
|
|
472
518
|
|
|
473
519
|
const sources: Source[] = [...validProjects, ...componentSources];
|
|
474
520
|
|
|
475
|
-
if (
|
|
476
|
-
|
|
521
|
+
if (hasJSONFormat) msg += generateJsDriver(sources, getJsonFormat(formats));
|
|
522
|
+
|
|
523
|
+
if (shouldGenerateIOSBundles) {
|
|
524
|
+
msg += "iOS locale information detected, generating bundles..\n\n";
|
|
525
|
+
msg += await generateIOSBundles(localeByVariantApiId);
|
|
526
|
+
msg += await generateSwiftDriver(source);
|
|
527
|
+
}
|
|
477
528
|
|
|
478
|
-
msg += `\n${output.success("Done")}!`;
|
|
529
|
+
msg += `\n\n${output.success("Done")}!`;
|
|
479
530
|
|
|
480
531
|
spinner.stop();
|
|
481
532
|
return console.log(msg);
|
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
|
+
}
|