@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.
Files changed (87) hide show
  1. package/.github/actions/install-node-dependencies/action.yml +24 -0
  2. package/.github/workflows/required-checks.yml +24 -0
  3. package/__mocks__/fs.js +2 -0
  4. package/bin/__mocks__/api.js +48 -0
  5. package/bin/__mocks__/api.js.map +1 -0
  6. package/bin/config.js +6 -4
  7. package/bin/config.js.map +1 -1
  8. package/bin/config.test.js +4 -3
  9. package/bin/config.test.js.map +1 -1
  10. package/bin/consts.js +19 -29
  11. package/bin/consts.js.map +1 -1
  12. package/bin/generate-suggestions.js +68 -58
  13. package/bin/generate-suggestions.js.map +1 -1
  14. package/bin/generate-suggestions.test.js +24 -13
  15. package/bin/generate-suggestions.test.js.map +1 -1
  16. package/bin/http/__mocks__/fetchComponentFolders.js +71 -0
  17. package/bin/http/__mocks__/fetchComponentFolders.js.map +1 -0
  18. package/bin/http/__mocks__/fetchComponents.js +73 -0
  19. package/bin/http/__mocks__/fetchComponents.js.map +1 -0
  20. package/bin/http/__mocks__/fetchVariants.js +71 -0
  21. package/bin/http/__mocks__/fetchVariants.js.map +1 -0
  22. package/bin/http/fetchComponentFolders.js +4 -4
  23. package/bin/http/fetchComponentFolders.js.map +1 -1
  24. package/bin/http/fetchComponents.js +4 -4
  25. package/bin/http/fetchComponents.js.map +1 -1
  26. package/bin/http/fetchVariants.js +2 -2
  27. package/bin/http/fetchVariants.js.map +1 -1
  28. package/bin/http/http.test.js +159 -0
  29. package/bin/http/http.test.js.map +1 -0
  30. package/bin/http/importComponents.js +9 -2
  31. package/bin/http/importComponents.js.map +1 -1
  32. package/bin/init/project.test.js +5 -28
  33. package/bin/init/project.test.js.map +1 -1
  34. package/bin/init/token.js +72 -27
  35. package/bin/init/token.js.map +1 -1
  36. package/bin/init/token.test.js +87 -9
  37. package/bin/init/token.test.js.map +1 -1
  38. package/bin/pull-lib.test.js +379 -0
  39. package/bin/pull-lib.test.js.map +1 -0
  40. package/bin/pull.js +13 -5
  41. package/bin/pull.js.map +1 -1
  42. package/bin/pull.test.js +100 -289
  43. package/bin/pull.test.js.map +1 -1
  44. package/bin/replace.js +22 -6
  45. package/bin/replace.js.map +1 -1
  46. package/bin/replace.test.js +53 -11
  47. package/bin/replace.test.js.map +1 -1
  48. package/bin/types.js +2 -2
  49. package/bin/types.js.map +1 -1
  50. package/bin/utils/determineModuleType.js +6 -7
  51. package/bin/utils/determineModuleType.js.map +1 -1
  52. package/bin/utils/determineModuleType.test.js +60 -0
  53. package/bin/utils/determineModuleType.test.js.map +1 -0
  54. package/bin/utils/getSelectedProjects.js +5 -5
  55. package/bin/utils/getSelectedProjects.js.map +1 -1
  56. package/bin/utils/quit.js +3 -3
  57. package/bin/utils/quit.js.map +1 -1
  58. package/jest.config.ts +16 -0
  59. package/lib/__mocks__/api.ts +12 -0
  60. package/lib/config.test.ts +3 -1
  61. package/lib/config.ts +4 -2
  62. package/lib/consts.ts +19 -17
  63. package/lib/generate-suggestions.test.ts +23 -11
  64. package/lib/generate-suggestions.ts +89 -79
  65. package/lib/http/__mocks__/fetchComponentFolders.ts +23 -0
  66. package/lib/http/__mocks__/fetchComponents.ts +24 -0
  67. package/lib/http/__mocks__/fetchVariants.ts +21 -0
  68. package/lib/http/fetchComponentFolders.ts +6 -4
  69. package/lib/http/fetchComponents.ts +5 -3
  70. package/lib/http/fetchVariants.ts +14 -3
  71. package/lib/http/http.test.ts +122 -0
  72. package/lib/http/importComponents.ts +8 -0
  73. package/lib/init/project.test.ts +4 -27
  74. package/lib/init/token.test.ts +55 -7
  75. package/lib/init/token.ts +76 -27
  76. package/lib/pull-lib.test.ts +367 -0
  77. package/lib/pull.test.ts +97 -310
  78. package/lib/pull.ts +11 -3
  79. package/lib/replace.test.ts +46 -10
  80. package/lib/replace.ts +20 -3
  81. package/lib/types.ts +5 -0
  82. package/lib/utils/determineModuleType.test.ts +48 -0
  83. package/lib/utils/determineModuleType.ts +4 -6
  84. package/lib/utils/getSelectedProjects.ts +3 -3
  85. package/lib/utils/quit.ts +1 -1
  86. package/package.json +4 -3
  87. package/jest.config.js +0 -6
package/lib/pull.test.ts CHANGED
@@ -1,330 +1,117 @@
1
- import fs from "fs";
2
- import path from "path";
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 allPull, { getFormatDataIsValid } from "./pull";
35
- import { Project, SupportedExtension, SupportedFormat } from "./types";
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
- const {
38
- _testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
39
- } = allPull;
40
- const variant = "english";
9
+ jest.mock("fs");
10
+ jest.mock("./api");
41
11
 
42
- const cleanOutputDir = () => {
43
- if (fs.existsSync(consts.TEXT_DIR))
44
- fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
12
+ jest.mock("./http/fetchComponentFolders");
13
+ jest.mock("./http/fetchComponents");
14
+ jest.mock("./http/fetchVariants");
45
15
 
46
- fs.mkdirSync(consts.TEXT_DIR);
47
- };
16
+ const defaultEnv = { ...process.env };
48
17
 
49
- afterAll(() => {
50
- fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
18
+ beforeEach(() => {
19
+ vol.reset();
20
+ process.env = { ...defaultEnv };
51
21
  });
52
22
 
53
- describe("cleanOutputFiles", () => {
54
- it("removes .js, .json, .xml, .strings, .stringsdict files", () => {
55
- cleanOutputDir();
56
-
57
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.json"), "test");
58
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.js"), "test");
59
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.xml"), "test");
60
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.strings"), "test");
61
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.stringsdict"), "test");
62
- // this file shouldn't be deleted
63
- fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.txt"), "test");
64
-
65
- expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(6);
66
-
67
- cleanOutputFiles();
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
- expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
70
- });
71
- });
49
+ vol.fromJSON({
50
+ [consts.CONFIG_FILE]: mockGlobalConfigFile,
51
+ [consts.PROJECT_CONFIG_FILE]: mockProjectConfigFile,
52
+ });
72
53
 
73
- describe("downloadAndSaveBase", () => {
74
- beforeAll(() => {
75
- if (!fs.existsSync(consts.TEXT_DIR)) {
76
- fs.mkdirSync(consts.TEXT_DIR);
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
- beforeEach(() => {
81
- cleanOutputDir();
75
+ expect(filesOnDiskExpected.size).toBe(0);
82
76
  });
83
77
 
84
- const mockDataFlat = { hello: "world" };
85
- const mockDataNested = { hello: { text: "world" } };
86
- const mockDataStructured = { hello: { text: "world" } };
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
- const verifySavedData = async (
144
- format: SupportedFormat,
145
- data: unknown,
146
- ext: SupportedExtension
147
- ) => {
148
- const output = await downloadAndSaveBase({
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
- switch (format) {
162
- case "android":
163
- case "ios-strings":
164
- case "ios-stringsdict":
165
- expect(typeof data).toBe("string");
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
- formats.forEach(({ format, data, ext }) => {
182
- it(`writes the ${format} format to disk`, async () => {
183
- mockApiCall(data);
184
- await verifySavedData(format, data, ext);
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
- describe("getFormatDataIsValid", () => {
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) msg += generateJsDriver(sources, getJsonFormat(formats));
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
+ };
@@ -1,16 +1,30 @@
1
- import fs from "fs/promises";
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 fs.writeFile(tempFile, content);
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 fs.unlink(filePath);
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 fs.readFile(tempFile, "utf-8");
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 fs.readFile(tempFile, "utf-8");
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 fs.readFile(tempFile, "utf-8");
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 fs.readFile(tempFile, "utf-8");
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-extra";
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 fs.readFile(filePath, "utf-8");
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
- fs.writeFile(filePath, transformedCode);
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
+ });