@dittowords/cli 4.3.0 → 4.4.1
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/.sentryclirc +3 -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.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/ditto.js +12 -4
- package/bin/ditto.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 +6 -3
- 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 +15 -4
- package/bin/pull.js.map +1 -1
- package/bin/pull.test.js +73 -290
- 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/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/consts.ts +19 -17
- package/lib/ditto.ts +9 -1
- 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 +15 -4
- 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 +63 -316
- package/lib/pull.ts +13 -2
- package/lib/replace.test.ts +46 -10
- package/lib/replace.ts +20 -3
- 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,77 @@
|
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
|
|
39
|
-
} = allPull;
|
|
40
|
-
const variant = "english";
|
|
41
|
-
|
|
42
|
-
const cleanOutputDir = () => {
|
|
43
|
-
if (fs.existsSync(consts.TEXT_DIR))
|
|
44
|
-
fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
|
|
45
|
-
|
|
46
|
-
fs.mkdirSync(consts.TEXT_DIR);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
afterAll(() => {
|
|
50
|
-
fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("cleanOutputFiles", () => {
|
|
54
|
-
it("removes .js, .json, .xml, .strings, .stringsdict files", () => {
|
|
55
|
-
cleanOutputDir();
|
|
4
|
+
import { jest } from "@jest/globals";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
const axiosMock = jest.mocked(axios);
|
|
7
|
+
import fs from "fs";
|
|
56
8
|
|
|
57
|
-
|
|
58
|
-
|
|
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");
|
|
9
|
+
jest.mock("fs");
|
|
10
|
+
jest.mock("./api");
|
|
64
11
|
|
|
65
|
-
|
|
12
|
+
jest.mock("./http/fetchComponentFolders");
|
|
13
|
+
jest.mock("./http/fetchComponents");
|
|
14
|
+
jest.mock("./http/fetchVariants");
|
|
66
15
|
|
|
67
|
-
|
|
16
|
+
const defaultEnv = { ...process.env };
|
|
68
17
|
|
|
69
|
-
|
|
70
|
-
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vol.reset();
|
|
20
|
+
process.env = { ...defaultEnv };
|
|
71
21
|
});
|
|
72
22
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
};
|
|
142
|
-
|
|
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"
|
|
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" })
|
|
159
47
|
);
|
|
160
48
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
};
|
|
49
|
+
vol.fromJSON({
|
|
50
|
+
[consts.CONFIG_FILE]: mockGlobalConfigFile,
|
|
51
|
+
[consts.PROJECT_CONFIG_FILE]: mockProjectConfigFile,
|
|
52
|
+
});
|
|
180
53
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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);
|
|
185
73
|
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
74
|
|
|
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);
|
|
75
|
+
expect(filesOnDiskExpected.size).toBe(0);
|
|
329
76
|
});
|
|
330
77
|
});
|
package/lib/pull.ts
CHANGED
|
@@ -327,7 +327,7 @@ async function downloadAndSave(
|
|
|
327
327
|
spinner.start();
|
|
328
328
|
|
|
329
329
|
const [variants, allComponentFoldersResponse] = await Promise.all([
|
|
330
|
-
fetchVariants(source),
|
|
330
|
+
fetchVariants(source, options),
|
|
331
331
|
fetchComponentFolders({}),
|
|
332
332
|
]);
|
|
333
333
|
|
|
@@ -578,15 +578,20 @@ async function downloadAndSave(
|
|
|
578
578
|
|
|
579
579
|
export interface PullOptions {
|
|
580
580
|
meta?: Record<string, string>;
|
|
581
|
+
includeSampleData?: boolean;
|
|
581
582
|
}
|
|
582
583
|
|
|
583
584
|
export const pull = async (options?: PullOptions) => {
|
|
584
585
|
const meta = options ? options.meta : {};
|
|
586
|
+
const includeSampleData = options?.includeSampleData || false;
|
|
585
587
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
586
588
|
const sourceInformation = config.parseSourceInformation();
|
|
587
589
|
|
|
588
590
|
try {
|
|
589
|
-
return await downloadAndSave(sourceInformation, token, {
|
|
591
|
+
return await downloadAndSave(sourceInformation, token, {
|
|
592
|
+
meta,
|
|
593
|
+
includeSampleData,
|
|
594
|
+
});
|
|
590
595
|
} catch (e) {
|
|
591
596
|
const eventId = Sentry.captureException(e);
|
|
592
597
|
const eventStr = `\n\nError ID: ${output.info(eventId)}`;
|
|
@@ -615,3 +620,9 @@ export default {
|
|
|
615
620
|
downloadAndSaveBase,
|
|
616
621
|
},
|
|
617
622
|
};
|
|
623
|
+
|
|
624
|
+
export const _test = {
|
|
625
|
+
getJsonFormat,
|
|
626
|
+
getJsonFormatIsValid,
|
|
627
|
+
ensureEndsWithNewLine,
|
|
628
|
+
};
|
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) {
|
|
@@ -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
|
+
});
|
|
@@ -9,18 +9,16 @@ export type ModuleType = "commonjs" | "module";
|
|
|
9
9
|
* @returns "commonjs" or "module", defaulting to "module" if no `package.json` is found or if the found
|
|
10
10
|
* file does not include a `type` property.
|
|
11
11
|
*/
|
|
12
|
-
export function determineModuleType() {
|
|
13
|
-
const value = getRawTypeFromPackageJson();
|
|
12
|
+
export function determineModuleType(currentDir: string | null = process.cwd()) {
|
|
13
|
+
const value = getRawTypeFromPackageJson(currentDir);
|
|
14
14
|
return getTypeOrDefault(value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function getRawTypeFromPackageJson() {
|
|
17
|
+
function getRawTypeFromPackageJson(currentDir: string | null) {
|
|
18
18
|
if (process.env.DITTO_MODULE_TYPE) {
|
|
19
19
|
return process.env.DITTO_MODULE_TYPE;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let currentDir: string | null = process.cwd(); // Get the current working directory
|
|
23
|
-
|
|
24
22
|
while (currentDir) {
|
|
25
23
|
const packageJsonPath = path.join(currentDir, "package.json");
|
|
26
24
|
if (fs.existsSync(packageJsonPath)) {
|
|
@@ -36,7 +34,7 @@ function getRawTypeFromPackageJson() {
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
if (currentDir === "/") {
|
|
39
|
-
|
|
37
|
+
break;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// Move up a directory and continue the search
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import yaml, { YAMLException } from "js-yaml";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import consts from "../consts";
|
|
5
5
|
import { ConfigYAML, Project } from "../types";
|
|
6
6
|
import Config, { DEFAULT_CONFIG_JSON } from "../config";
|
|
7
7
|
|
|
@@ -29,8 +29,8 @@ function yamlToJson(_yaml: string): ConfigYAML | null {
|
|
|
29
29
|
* Returns an array containing all valid projects ({ id, name })
|
|
30
30
|
* currently contained in the project config file.
|
|
31
31
|
*/
|
|
32
|
-
export const getSelectedProjects = (configFile = PROJECT_CONFIG_FILE) =>
|
|
32
|
+
export const getSelectedProjects = (configFile = consts.PROJECT_CONFIG_FILE) =>
|
|
33
33
|
Config.parseSourceInformation(configFile).validProjects;
|
|
34
34
|
|
|
35
|
-
export const getIsUsingComponents = (configFile = PROJECT_CONFIG_FILE) =>
|
|
35
|
+
export const getIsUsingComponents = (configFile = consts.PROJECT_CONFIG_FILE) =>
|
|
36
36
|
Config.parseSourceInformation(configFile).shouldFetchComponentLibrary;
|
package/lib/utils/quit.ts
CHANGED