@dittowords/cli 4.4.0 → 4.5.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/.github/actions/install-node-dependencies/action.yml +24 -0
- package/.github/workflows/required-checks.yml +24 -0
- package/__mocks__/fs.js +2 -0
- package/bin/__mocks__/api.js +48 -0
- package/bin/__mocks__/api.js.map +1 -0
- package/bin/config.js +6 -4
- package/bin/config.js.map +1 -1
- package/bin/config.test.js +4 -3
- package/bin/config.test.js.map +1 -1
- package/bin/consts.js +19 -29
- package/bin/consts.js.map +1 -1
- package/bin/generate-suggestions.js +68 -58
- package/bin/generate-suggestions.js.map +1 -1
- package/bin/generate-suggestions.test.js +24 -13
- package/bin/generate-suggestions.test.js.map +1 -1
- package/bin/http/__mocks__/fetchComponentFolders.js +71 -0
- package/bin/http/__mocks__/fetchComponentFolders.js.map +1 -0
- package/bin/http/__mocks__/fetchComponents.js +73 -0
- package/bin/http/__mocks__/fetchComponents.js.map +1 -0
- package/bin/http/__mocks__/fetchVariants.js +71 -0
- package/bin/http/__mocks__/fetchVariants.js.map +1 -0
- package/bin/http/fetchComponentFolders.js +4 -4
- package/bin/http/fetchComponentFolders.js.map +1 -1
- package/bin/http/fetchComponents.js +4 -4
- package/bin/http/fetchComponents.js.map +1 -1
- package/bin/http/fetchVariants.js +2 -2
- package/bin/http/fetchVariants.js.map +1 -1
- package/bin/http/http.test.js +159 -0
- package/bin/http/http.test.js.map +1 -0
- package/bin/http/importComponents.js +9 -2
- package/bin/http/importComponents.js.map +1 -1
- package/bin/init/project.test.js +5 -28
- package/bin/init/project.test.js.map +1 -1
- package/bin/init/token.js +72 -27
- package/bin/init/token.js.map +1 -1
- package/bin/init/token.test.js +87 -9
- package/bin/init/token.test.js.map +1 -1
- package/bin/pull-lib.test.js +379 -0
- package/bin/pull-lib.test.js.map +1 -0
- package/bin/pull.js +13 -5
- package/bin/pull.js.map +1 -1
- package/bin/pull.test.js +100 -289
- package/bin/pull.test.js.map +1 -1
- package/bin/replace.js +22 -6
- package/bin/replace.js.map +1 -1
- package/bin/replace.test.js +53 -11
- package/bin/replace.test.js.map +1 -1
- package/bin/types.js +2 -2
- package/bin/types.js.map +1 -1
- package/bin/utils/determineModuleType.js +6 -7
- package/bin/utils/determineModuleType.js.map +1 -1
- package/bin/utils/determineModuleType.test.js +60 -0
- package/bin/utils/determineModuleType.test.js.map +1 -0
- package/bin/utils/getSelectedProjects.js +5 -5
- package/bin/utils/getSelectedProjects.js.map +1 -1
- package/bin/utils/quit.js +3 -3
- package/bin/utils/quit.js.map +1 -1
- package/jest.config.ts +16 -0
- package/lib/__mocks__/api.ts +12 -0
- package/lib/config.test.ts +3 -1
- package/lib/config.ts +4 -2
- package/lib/consts.ts +19 -17
- package/lib/generate-suggestions.test.ts +23 -11
- package/lib/generate-suggestions.ts +89 -79
- package/lib/http/__mocks__/fetchComponentFolders.ts +23 -0
- package/lib/http/__mocks__/fetchComponents.ts +24 -0
- package/lib/http/__mocks__/fetchVariants.ts +21 -0
- package/lib/http/fetchComponentFolders.ts +6 -4
- package/lib/http/fetchComponents.ts +5 -3
- package/lib/http/fetchVariants.ts +14 -3
- package/lib/http/http.test.ts +122 -0
- package/lib/http/importComponents.ts +8 -0
- package/lib/init/project.test.ts +4 -27
- package/lib/init/token.test.ts +55 -7
- package/lib/init/token.ts +76 -27
- package/lib/pull-lib.test.ts +367 -0
- package/lib/pull.test.ts +97 -310
- package/lib/pull.ts +11 -3
- package/lib/replace.test.ts +46 -10
- package/lib/replace.ts +20 -3
- package/lib/types.ts +5 -0
- package/lib/utils/determineModuleType.test.ts +48 -0
- package/lib/utils/determineModuleType.ts +4 -6
- package/lib/utils/getSelectedProjects.ts +3 -3
- package/lib/utils/quit.ts +1 -1
- package/package.json +4 -3
- package/jest.config.js +0 -6
package/lib/pull.test.ts
CHANGED
|
@@ -1,330 +1,117 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
jest.mock("./api", () => ({
|
|
5
|
-
createApiClient: jest.fn(), // this needs to be mocked in each test that requires it
|
|
6
|
-
}));
|
|
7
|
-
import { createApiClient } from "./api";
|
|
8
|
-
|
|
9
|
-
const testProjects: Project[] = [
|
|
10
|
-
{
|
|
11
|
-
id: "1",
|
|
12
|
-
name: "Project 1",
|
|
13
|
-
fileName: "Project 1",
|
|
14
|
-
},
|
|
15
|
-
{ id: "2", name: "Project 2", fileName: "Project 2" },
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
// TODO: all tests in this file currently failing because we're re-instantiating the api client
|
|
19
|
-
// everywhere and are unable to mock the return type separately for each instance of usage.
|
|
20
|
-
// We need to refactor to share one api client everywhere instead of always re-creating it.
|
|
21
|
-
const mockApi = createApiClient() as any as jest.Mocked<
|
|
22
|
-
ReturnType<typeof createApiClient>
|
|
23
|
-
>;
|
|
24
|
-
|
|
25
|
-
jest.mock("./consts", () => ({
|
|
26
|
-
TEXT_DIR: ".testing",
|
|
27
|
-
API_HOST: "https://api.dittowords.com",
|
|
28
|
-
CONFIG_FILE: ".testing/ditto",
|
|
29
|
-
PROJECT_CONFIG_FILE: ".testing/config.yml",
|
|
30
|
-
TEXT_FILE: ".testing/text.json",
|
|
31
|
-
}));
|
|
32
|
-
|
|
1
|
+
import { pull } from "./pull";
|
|
2
|
+
import { vol } from "memfs";
|
|
33
3
|
import consts from "./consts";
|
|
34
|
-
import
|
|
35
|
-
import
|
|
4
|
+
import { jest } from "@jest/globals";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
const axiosMock = jest.mocked(axios);
|
|
7
|
+
import fs from "fs";
|
|
36
8
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} = allPull;
|
|
40
|
-
const variant = "english";
|
|
9
|
+
jest.mock("fs");
|
|
10
|
+
jest.mock("./api");
|
|
41
11
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
12
|
+
jest.mock("./http/fetchComponentFolders");
|
|
13
|
+
jest.mock("./http/fetchComponents");
|
|
14
|
+
jest.mock("./http/fetchVariants");
|
|
45
15
|
|
|
46
|
-
|
|
47
|
-
};
|
|
16
|
+
const defaultEnv = { ...process.env };
|
|
48
17
|
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vol.reset();
|
|
20
|
+
process.env = { ...defaultEnv };
|
|
51
21
|
});
|
|
52
22
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
const mockGlobalConfigFile = `
|
|
24
|
+
api.dittowords.com:
|
|
25
|
+
- token: xxx-xxx-xxx
|
|
26
|
+
`;
|
|
27
|
+
const mockProjectConfigFile = `
|
|
28
|
+
sources:
|
|
29
|
+
components: true
|
|
30
|
+
projects:
|
|
31
|
+
- id: project-id-1
|
|
32
|
+
name: Test Project
|
|
33
|
+
variants: true
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
describe("pull", () => {
|
|
37
|
+
it("correctly writes files to disk per source for basic config", async () => {
|
|
38
|
+
process.env.DITTO_TEXT_DIR = "/ditto";
|
|
39
|
+
process.env.DITTO_PROJECT_CONFIG_FILE = "/ditto/config.yml";
|
|
40
|
+
|
|
41
|
+
// we need to manually mock responses for the http calls that happen
|
|
42
|
+
// directly within the pull function; we don't need to mock the http
|
|
43
|
+
// calls that happen by way of http/* function calls since those have
|
|
44
|
+
// their own mocks already.
|
|
45
|
+
axiosMock.get.mockImplementation(
|
|
46
|
+
(): Promise<any> => Promise.resolve({ data: "data" })
|
|
47
|
+
);
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
vol.fromJSON({
|
|
50
|
+
[consts.CONFIG_FILE]: mockGlobalConfigFile,
|
|
51
|
+
[consts.PROJECT_CONFIG_FILE]: mockProjectConfigFile,
|
|
52
|
+
});
|
|
72
53
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
await pull();
|
|
55
|
+
|
|
56
|
+
const filesOnDiskExpected = new Set([
|
|
57
|
+
"components__example-folder__base.json",
|
|
58
|
+
"components__example-folder__example-variant-1.json",
|
|
59
|
+
"components__example-folder__example-variant-2.json",
|
|
60
|
+
"components__root__base.json",
|
|
61
|
+
"components__root__example-variant-1.json",
|
|
62
|
+
"components__root__example-variant-2.json",
|
|
63
|
+
"test-project__base.json",
|
|
64
|
+
"test-project__example-variant-1.json",
|
|
65
|
+
"test-project__example-variant-2.json",
|
|
66
|
+
"index.d.ts",
|
|
67
|
+
"index.js",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const filesOnDisk = fs.readdirSync("/ditto");
|
|
71
|
+
filesOnDisk.forEach((file) => {
|
|
72
|
+
filesOnDiskExpected.delete(file);
|
|
73
|
+
});
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
cleanOutputDir();
|
|
75
|
+
expect(filesOnDiskExpected.size).toBe(0);
|
|
82
76
|
});
|
|
83
77
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const mockDataIcu = { hello: "world" };
|
|
88
|
-
const mockDataAndroid = `
|
|
89
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
90
|
-
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
|
91
|
-
<string name="hello-world" ditto_api_id="hello-world">Hello World</string>
|
|
92
|
-
</resources>
|
|
93
|
-
`;
|
|
94
|
-
const mockDataIosStrings = `
|
|
95
|
-
"hello" = "world";
|
|
96
|
-
`;
|
|
97
|
-
const mockDataIosStringsDict = `
|
|
98
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
99
|
-
<plist version="1.0">
|
|
100
|
-
<dict>
|
|
101
|
-
<key>hello-world</key>
|
|
102
|
-
<dict>
|
|
103
|
-
<key>NSStringLocalizedFormatKey</key>
|
|
104
|
-
<string>%1$#@count@</string>
|
|
105
|
-
<key>count</key>
|
|
106
|
-
<dict>
|
|
107
|
-
<key>NSStringFormatSpecTypeKey</key>
|
|
108
|
-
<string>NSStringPluralRuleType</string>
|
|
109
|
-
<key>NSStringFormatValueTypeKey</key>
|
|
110
|
-
<string>d</string>
|
|
111
|
-
<key>other</key>
|
|
112
|
-
<string>espanol</string>
|
|
113
|
-
</dict>
|
|
114
|
-
</dict>
|
|
115
|
-
</dict>
|
|
116
|
-
</plist>
|
|
117
|
-
`;
|
|
118
|
-
|
|
119
|
-
const formats: Array<{
|
|
120
|
-
format: SupportedFormat;
|
|
121
|
-
data: Object;
|
|
122
|
-
ext: SupportedExtension;
|
|
123
|
-
}> = [
|
|
124
|
-
{ format: "flat", data: mockDataFlat, ext: ".json" },
|
|
125
|
-
{ format: "nested", data: mockDataNested, ext: ".json" },
|
|
126
|
-
{ format: "structured", data: mockDataStructured, ext: ".json" },
|
|
127
|
-
{ format: "icu", data: mockDataIcu, ext: ".json" },
|
|
128
|
-
{ format: "android", data: mockDataAndroid, ext: ".xml" },
|
|
129
|
-
{ format: "ios-strings", data: mockDataIosStrings, ext: ".strings" },
|
|
130
|
-
{
|
|
131
|
-
format: "ios-stringsdict",
|
|
132
|
-
data: mockDataIosStringsDict,
|
|
133
|
-
ext: ".stringsdict",
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
const mockApiCall = (data: unknown) => {
|
|
138
|
-
(createApiClient as any).mockImplementation(() => ({
|
|
139
|
-
get: () => ({ data }),
|
|
140
|
-
}));
|
|
141
|
-
};
|
|
78
|
+
it("correctly does not write index.js or index.d.ts when `disableJsDriver: true` is specified", async () => {
|
|
79
|
+
process.env.DITTO_TEXT_DIR = "/ditto";
|
|
80
|
+
process.env.DITTO_PROJECT_CONFIG_FILE = "/ditto/config.yml";
|
|
142
81
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
projects: testProjects,
|
|
150
|
-
format: format,
|
|
151
|
-
} as any);
|
|
152
|
-
expect(/successfully saved/i.test(output)).toEqual(true);
|
|
153
|
-
const directoryContents = fs.readdirSync(consts.TEXT_DIR);
|
|
154
|
-
expect(directoryContents.length).toEqual(testProjects.length);
|
|
155
|
-
expect(directoryContents.every((f) => f.endsWith(ext))).toBe(true);
|
|
156
|
-
const fileDataString = fs.readFileSync(
|
|
157
|
-
path.resolve(consts.TEXT_DIR, directoryContents[0]),
|
|
158
|
-
"utf8"
|
|
82
|
+
// we need to manually mock responses for the http calls that happen
|
|
83
|
+
// directly within the pull function; we don't need to mock the http
|
|
84
|
+
// calls that happen by way of http/* function calls since those have
|
|
85
|
+
// their own mocks already.
|
|
86
|
+
axiosMock.get.mockImplementation(
|
|
87
|
+
(): Promise<any> => Promise.resolve({ data: "data" })
|
|
159
88
|
);
|
|
160
89
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
expect(fileDataString.replace(/\s/g, "")).toEqual(
|
|
167
|
-
(data as string).replace(/\s/g, "")
|
|
168
|
-
);
|
|
169
|
-
break;
|
|
170
|
-
|
|
171
|
-
case "flat":
|
|
172
|
-
case "nested":
|
|
173
|
-
case "structured":
|
|
174
|
-
case "icu":
|
|
175
|
-
default:
|
|
176
|
-
expect(JSON.parse(fileDataString)).toEqual(data);
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
90
|
+
vol.fromJSON({
|
|
91
|
+
[consts.CONFIG_FILE]: mockGlobalConfigFile,
|
|
92
|
+
[consts.PROJECT_CONFIG_FILE]:
|
|
93
|
+
mockProjectConfigFile + "\n" + "disableJsDriver: true",
|
|
94
|
+
});
|
|
180
95
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
96
|
+
await pull();
|
|
97
|
+
|
|
98
|
+
const filesOnDiskExpected = new Set([
|
|
99
|
+
"components__example-folder__base.json",
|
|
100
|
+
"components__example-folder__example-variant-1.json",
|
|
101
|
+
"components__example-folder__example-variant-2.json",
|
|
102
|
+
"components__root__base.json",
|
|
103
|
+
"components__root__example-variant-1.json",
|
|
104
|
+
"components__root__example-variant-2.json",
|
|
105
|
+
"test-project__base.json",
|
|
106
|
+
"test-project__example-variant-1.json",
|
|
107
|
+
"test-project__example-variant-2.json",
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const filesOnDisk = fs.readdirSync("/ditto");
|
|
111
|
+
filesOnDisk.forEach((file) => {
|
|
112
|
+
filesOnDiskExpected.delete(file);
|
|
185
113
|
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
114
|
|
|
189
|
-
|
|
190
|
-
it("handles flat format appropriately", () => {
|
|
191
|
-
expect(getFormatDataIsValid.flat("{}")).toBe(false);
|
|
192
|
-
expect(getFormatDataIsValid.flat(`{ "hello": "world" }`)).toBe(true);
|
|
193
|
-
expect(
|
|
194
|
-
getFormatDataIsValid.flat(`{
|
|
195
|
-
"__variant-name": "English",
|
|
196
|
-
"__variant-description": ""
|
|
197
|
-
}`)
|
|
198
|
-
).toBe(false);
|
|
199
|
-
expect(
|
|
200
|
-
getFormatDataIsValid.flat(`{
|
|
201
|
-
"__variant-name": "English",
|
|
202
|
-
"__variant-description": "",
|
|
203
|
-
"hello": "world"
|
|
204
|
-
}`)
|
|
205
|
-
).toBe(true);
|
|
206
|
-
});
|
|
207
|
-
it("handles structured format appropriately", () => {
|
|
208
|
-
expect(getFormatDataIsValid.structured("{}")).toBe(false);
|
|
209
|
-
expect(
|
|
210
|
-
getFormatDataIsValid.structured(`{ "hello": { "text": "world" } }`)
|
|
211
|
-
).toBe(true);
|
|
212
|
-
expect(
|
|
213
|
-
getFormatDataIsValid.structured(`{
|
|
214
|
-
"__variant-name": "English",
|
|
215
|
-
"__variant-description": ""
|
|
216
|
-
}`)
|
|
217
|
-
).toBe(false);
|
|
218
|
-
expect(
|
|
219
|
-
getFormatDataIsValid.structured(`{
|
|
220
|
-
"__variant-name": "English",
|
|
221
|
-
"__variant-description": "",
|
|
222
|
-
"hello": { "text": "world" }
|
|
223
|
-
}`)
|
|
224
|
-
).toBe(true);
|
|
225
|
-
});
|
|
226
|
-
it("handles icu format appropriately", () => {
|
|
227
|
-
expect(getFormatDataIsValid.icu("{}")).toBe(false);
|
|
228
|
-
expect(getFormatDataIsValid.icu(`{ "hello": "world" }`)).toBe(true);
|
|
229
|
-
expect(
|
|
230
|
-
getFormatDataIsValid.icu(`{
|
|
231
|
-
"__variant-name": "English",
|
|
232
|
-
"__variant-description": ""
|
|
233
|
-
}`)
|
|
234
|
-
).toBe(false);
|
|
235
|
-
expect(
|
|
236
|
-
getFormatDataIsValid.icu(`{
|
|
237
|
-
"__variant-name": "English",
|
|
238
|
-
"__variant-description": "",
|
|
239
|
-
"hello": "world"
|
|
240
|
-
}`)
|
|
241
|
-
).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
it("handles android format appropriately", () => {
|
|
244
|
-
expect(
|
|
245
|
-
getFormatDataIsValid.android(`
|
|
246
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
247
|
-
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>
|
|
248
|
-
`)
|
|
249
|
-
).toBe(false);
|
|
250
|
-
expect(
|
|
251
|
-
getFormatDataIsValid.android(`
|
|
252
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
253
|
-
<!--Variant Name: English-->
|
|
254
|
-
<!--Variant Description: -->
|
|
255
|
-
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"/>
|
|
256
|
-
`)
|
|
257
|
-
).toBe(false);
|
|
258
|
-
expect(
|
|
259
|
-
getFormatDataIsValid.android(`
|
|
260
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
261
|
-
<!--Variant Name: English-->
|
|
262
|
-
<!--Variant Description: -->
|
|
263
|
-
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
|
264
|
-
<string name="hello-world" ditto_api_id="hello-world">Hello World</string>
|
|
265
|
-
</resources>
|
|
266
|
-
`)
|
|
267
|
-
).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
it("handles ios-strings format appropriately", () => {
|
|
270
|
-
expect(getFormatDataIsValid["ios-strings"]("")).toBe(false);
|
|
271
|
-
expect(
|
|
272
|
-
getFormatDataIsValid["ios-strings"](`
|
|
273
|
-
/* Variant Name: English */
|
|
274
|
-
/* Variant Description: */
|
|
275
|
-
`)
|
|
276
|
-
).toBe(false);
|
|
277
|
-
expect(
|
|
278
|
-
getFormatDataIsValid["ios-strings"](`
|
|
279
|
-
/* Variant Name: English */
|
|
280
|
-
/* Variant Description: */
|
|
281
|
-
"Hello" = "World";
|
|
282
|
-
`)
|
|
283
|
-
).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
it("handles ios-stringsdict format appropriately", () => {
|
|
286
|
-
expect(
|
|
287
|
-
getFormatDataIsValid["ios-stringsdict"](`
|
|
288
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
289
|
-
<plist version="1.0">
|
|
290
|
-
<dict/>
|
|
291
|
-
</plist>
|
|
292
|
-
`)
|
|
293
|
-
).toBe(false);
|
|
294
|
-
expect(
|
|
295
|
-
getFormatDataIsValid["ios-stringsdict"](`
|
|
296
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
297
|
-
<!--Variant Name: English-->
|
|
298
|
-
<!--Variant Description: -->
|
|
299
|
-
<plist version="1.0">
|
|
300
|
-
<dict/>
|
|
301
|
-
</plist>
|
|
302
|
-
`)
|
|
303
|
-
).toBe(false);
|
|
304
|
-
expect(
|
|
305
|
-
getFormatDataIsValid["ios-stringsdict"](`
|
|
306
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
307
|
-
<!--Variant Name: English-->
|
|
308
|
-
<!--Variant Description: -->
|
|
309
|
-
<plist version="1.0">
|
|
310
|
-
<dict>
|
|
311
|
-
<key>Hello World</key>
|
|
312
|
-
<dict>
|
|
313
|
-
<key>NSStringLocalizedFormatKey</key>
|
|
314
|
-
<string>%1$#@count@</string>
|
|
315
|
-
<key>count</key>
|
|
316
|
-
<dict>
|
|
317
|
-
<key>NSStringFormatSpecTypeKey</key>
|
|
318
|
-
<string>NSStringPluralRuleType</string>
|
|
319
|
-
<key>NSStringFormatValueTypeKey</key>
|
|
320
|
-
<string>d</string>
|
|
321
|
-
<key>other</key>
|
|
322
|
-
<string>espanol</string>
|
|
323
|
-
</dict>
|
|
324
|
-
</dict>
|
|
325
|
-
</dict>
|
|
326
|
-
</plist>
|
|
327
|
-
`)
|
|
328
|
-
).toBe(true);
|
|
115
|
+
expect(filesOnDiskExpected.size).toBe(0);
|
|
329
116
|
});
|
|
330
117
|
});
|
package/lib/pull.ts
CHANGED
|
@@ -286,7 +286,7 @@ function cleanOutputFiles() {
|
|
|
286
286
|
|
|
287
287
|
if (
|
|
288
288
|
item.isFile() &&
|
|
289
|
-
/\.js(on)?|\.xml|\.strings(dict)?$|\.swift$/.test(item.name)
|
|
289
|
+
/\.js(on)?|\.ts|\.xml|\.strings(dict)?$|\.swift$/.test(item.name)
|
|
290
290
|
) {
|
|
291
291
|
return fs.unlinkSync(path.resolve(consts.TEXT_DIR, item.name));
|
|
292
292
|
}
|
|
@@ -310,6 +310,7 @@ async function downloadAndSave(
|
|
|
310
310
|
componentFolders: specifiedComponentFolders,
|
|
311
311
|
componentRoot,
|
|
312
312
|
localeByVariantApiId,
|
|
313
|
+
disableJsDriver,
|
|
313
314
|
} = source;
|
|
314
315
|
|
|
315
316
|
const formats = getFormat(formatFromSource);
|
|
@@ -525,7 +526,8 @@ async function downloadAndSave(
|
|
|
525
526
|
|
|
526
527
|
const sources: Source[] = [...validProjects, ...componentSources];
|
|
527
528
|
|
|
528
|
-
if (hasJSONFormat
|
|
529
|
+
if (hasJSONFormat && !disableJsDriver)
|
|
530
|
+
msg += generateJsDriver(sources, getJsonFormat(formats));
|
|
529
531
|
|
|
530
532
|
if (shouldGenerateIOSBundles) {
|
|
531
533
|
msg += "iOS locale information detected, generating bundles..\n\n";
|
|
@@ -583,7 +585,7 @@ export interface PullOptions {
|
|
|
583
585
|
|
|
584
586
|
export const pull = async (options?: PullOptions) => {
|
|
585
587
|
const meta = options ? options.meta : {};
|
|
586
|
-
const includeSampleData = options?.includeSampleData || false
|
|
588
|
+
const includeSampleData = options?.includeSampleData || false;
|
|
587
589
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
588
590
|
const sourceInformation = config.parseSourceInformation();
|
|
589
591
|
|
|
@@ -620,3 +622,9 @@ export default {
|
|
|
620
622
|
downloadAndSaveBase,
|
|
621
623
|
},
|
|
622
624
|
};
|
|
625
|
+
|
|
626
|
+
export const _test = {
|
|
627
|
+
getJsonFormat,
|
|
628
|
+
getJsonFormatIsValid,
|
|
629
|
+
ensureEndsWithNewLine,
|
|
630
|
+
};
|
package/lib/replace.test.ts
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import fs from "fs
|
|
1
|
+
import fs from "fs";
|
|
2
2
|
import { parseOptions, replaceJSXTextInFile } from "./replace"; // Assuming the function is exported in a separate file
|
|
3
3
|
|
|
4
|
+
jest.mock("fs");
|
|
5
|
+
|
|
4
6
|
// Helper function to create a temporary file
|
|
5
7
|
async function createTempJSXFile(content: string): Promise<string> {
|
|
6
|
-
const tempFile = "tempFile.jsx";
|
|
7
|
-
await
|
|
8
|
+
const tempFile = "/tempFile.jsx";
|
|
9
|
+
await new Promise((resolve, reject) => {
|
|
10
|
+
try {
|
|
11
|
+
fs.writeFile(tempFile, content, resolve);
|
|
12
|
+
} catch (e) {
|
|
13
|
+
reject(e);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
8
16
|
return tempFile;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
// Helper function to delete the temporary file
|
|
12
20
|
async function deleteTempFile(filePath: string): Promise<void> {
|
|
13
|
-
await
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
try {
|
|
23
|
+
fs.unlink(filePath, resolve);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
reject(e);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
describe("parseOptions", () => {
|
|
@@ -57,10 +71,9 @@ describe("parseOptions", () => {
|
|
|
57
71
|
});
|
|
58
72
|
});
|
|
59
73
|
|
|
60
|
-
// TODO: this is flaky
|
|
61
74
|
describe("replaceJSXTextInFile", () => {
|
|
62
75
|
afterEach(async () => {
|
|
63
|
-
await deleteTempFile("tempFile.jsx");
|
|
76
|
+
await deleteTempFile("/tempFile.jsx");
|
|
64
77
|
});
|
|
65
78
|
|
|
66
79
|
test("should replace JSX text with a DittoComponent", async () => {
|
|
@@ -70,7 +83,15 @@ describe("replaceJSXTextInFile", () => {
|
|
|
70
83
|
|
|
71
84
|
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
72
85
|
|
|
73
|
-
const transformedCode = await
|
|
86
|
+
const transformedCode = await new Promise((resolve, reject) => {
|
|
87
|
+
fs.readFile(tempFile, "utf-8", (error, data) => {
|
|
88
|
+
if (error) {
|
|
89
|
+
reject(error);
|
|
90
|
+
} else {
|
|
91
|
+
resolve(data);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
74
95
|
expect(transformedCode).toContain(
|
|
75
96
|
`<div>Hello, <DittoComponent componentId="${replaceWith}" /></div>`
|
|
76
97
|
);
|
|
@@ -89,7 +110,12 @@ describe("replaceJSXTextInFile", () => {
|
|
|
89
110
|
{ lineNumbers: [3] }
|
|
90
111
|
);
|
|
91
112
|
|
|
92
|
-
const transformedCode = await
|
|
113
|
+
const transformedCode = await new Promise((resolve, reject) =>
|
|
114
|
+
fs.readFile(tempFile, "utf-8", (error, data) => {
|
|
115
|
+
if (error) reject(error);
|
|
116
|
+
else resolve(data);
|
|
117
|
+
})
|
|
118
|
+
);
|
|
93
119
|
expect(transformedCode).toContain(
|
|
94
120
|
`<>\n <div>Hello, world</div>\n <div>Hello, <DittoComponent componentId=\"some-id\" /></div>\n</>;`
|
|
95
121
|
);
|
|
@@ -102,7 +128,12 @@ describe("replaceJSXTextInFile", () => {
|
|
|
102
128
|
|
|
103
129
|
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
104
130
|
|
|
105
|
-
const transformedCode = await
|
|
131
|
+
const transformedCode = await new Promise((resolve, reject) =>
|
|
132
|
+
fs.readFile(tempFile, "utf-8", (error, data) => {
|
|
133
|
+
if (error) reject(error);
|
|
134
|
+
else resolve(data);
|
|
135
|
+
})
|
|
136
|
+
);
|
|
106
137
|
expect(transformedCode).toContain(
|
|
107
138
|
`<div>HeLLo, <DittoComponent componentId="${replaceWith}" /></div>`
|
|
108
139
|
);
|
|
@@ -115,7 +146,12 @@ describe("replaceJSXTextInFile", () => {
|
|
|
115
146
|
|
|
116
147
|
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
117
148
|
|
|
118
|
-
const transformedCode = await
|
|
149
|
+
const transformedCode = await new Promise((resolve, reject) =>
|
|
150
|
+
fs.readFile(tempFile, "utf-8", (error, data) => {
|
|
151
|
+
if (error) reject(error);
|
|
152
|
+
else resolve(data);
|
|
153
|
+
})
|
|
154
|
+
);
|
|
119
155
|
expect(transformedCode).toContain("<div>Hello, world!</div>");
|
|
120
156
|
});
|
|
121
157
|
});
|
package/lib/replace.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fs from "fs
|
|
1
|
+
import fs from "fs";
|
|
2
2
|
import { parse } from "@babel/parser";
|
|
3
3
|
import traverse from "@babel/traverse";
|
|
4
4
|
import * as t from "@babel/types";
|
|
@@ -11,7 +11,15 @@ async function replaceJSXTextInFile(
|
|
|
11
11
|
lineNumbers?: number[];
|
|
12
12
|
}
|
|
13
13
|
) {
|
|
14
|
-
const code = await
|
|
14
|
+
const code = await new Promise<string>((resolve, reject) =>
|
|
15
|
+
fs.readFile(filePath, "utf-8", (err, data) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
reject(err);
|
|
18
|
+
} else {
|
|
19
|
+
resolve(data);
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
);
|
|
15
23
|
const ast = parse(code, {
|
|
16
24
|
sourceType: "module",
|
|
17
25
|
plugins: ["jsx", "typescript"],
|
|
@@ -69,7 +77,16 @@ async function replaceJSXTextInFile(
|
|
|
69
77
|
/* @ts-ignore */
|
|
70
78
|
configFile: false,
|
|
71
79
|
});
|
|
72
|
-
|
|
80
|
+
|
|
81
|
+
await new Promise((resolve, reject) =>
|
|
82
|
+
fs.writeFile(filePath, transformedCode, (err) => {
|
|
83
|
+
if (err) {
|
|
84
|
+
reject(err);
|
|
85
|
+
} else {
|
|
86
|
+
resolve(null);
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
);
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
function splitByCaseInsensitive(str: string, delimiter: string) {
|
package/lib/types.ts
CHANGED
|
@@ -52,6 +52,10 @@ export interface ConfigYAML {
|
|
|
52
52
|
// TODO: might want to rename this at some point
|
|
53
53
|
iosLocales?: Record<string, string>[];
|
|
54
54
|
|
|
55
|
+
// prevents the generation of index.js and index.d.ts files
|
|
56
|
+
// when working with JSON formats
|
|
57
|
+
disableJsDriver?: boolean;
|
|
58
|
+
|
|
55
59
|
// these are legacy fields - if they exist, we should output
|
|
56
60
|
// a deprecation error, and suggest that they nest them under
|
|
57
61
|
// a top-level `sources` property
|
|
@@ -73,6 +77,7 @@ export interface SourceInformation {
|
|
|
73
77
|
componentRoot: boolean | { status: string } | undefined;
|
|
74
78
|
componentFolders: ComponentFolder[] | undefined;
|
|
75
79
|
localeByVariantApiId: Record<string, string> | undefined;
|
|
80
|
+
disableJsDriver?: boolean;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
export type Token = string | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { determineModuleType } from "./determineModuleType";
|
|
2
|
+
import { vol } from "memfs";
|
|
3
|
+
|
|
4
|
+
const defaultEnv = process.env;
|
|
5
|
+
|
|
6
|
+
jest.mock("fs");
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vol.reset();
|
|
10
|
+
process.env = { ...defaultEnv };
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("'commonjs' if no package.json found", () => {
|
|
14
|
+
expect(determineModuleType()).toBe("commonjs");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("'commonjs' if package.json found but no `type` property", () => {
|
|
18
|
+
vol.fromJSON({ "package.json": JSON.stringify({}) }, process.cwd());
|
|
19
|
+
expect(determineModuleType()).toBe("commonjs");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("'commonjs' if package.json found and `type` property is 'commonjs'", () => {
|
|
23
|
+
vol.fromJSON({ "package.json": JSON.stringify({ type: "commonjs" }) });
|
|
24
|
+
expect(determineModuleType()).toBe("commonjs");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("'commonjs' if package.json found and `type` property is invalid", () => {
|
|
28
|
+
vol.fromJSON({ "package.json": JSON.stringify({ type: "invalid-type" }) });
|
|
29
|
+
expect(determineModuleType()).toBe("commonjs");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("'module' if package.json found and `type` property is 'module'", () => {
|
|
33
|
+
vol.fromJSON({ "package.json": JSON.stringify({ type: "module" }) });
|
|
34
|
+
expect(determineModuleType()).toBe("module");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("finds package.json in parent directories", () => {
|
|
38
|
+
vol.fromJSON({
|
|
39
|
+
"/some/nested/dir/test.txt": "",
|
|
40
|
+
"/package.json": JSON.stringify({ type: "module" }),
|
|
41
|
+
});
|
|
42
|
+
expect(determineModuleType("/some/nested/dir")).toBe("module");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("supports explicit specification of module type via environment variable", () => {
|
|
46
|
+
process.env.DITTO_MODULE_TYPE = "module";
|
|
47
|
+
expect(determineModuleType()).toBe("module");
|
|
48
|
+
});
|