@dittowords/cli 4.5.2 → 5.0.0-beta.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/README.md +6 -5
- package/bin/ditto.js +110 -285
- package/package.json +16 -10
- package/.github/actions/install-node-dependencies/action.yml +0 -24
- package/.github/workflows/required-checks.yml +0 -24
- package/.husky/pre-commit +0 -4
- package/.prettierignore +0 -0
- package/.prettierrc.json +0 -1
- package/__mocks__/fs.js +0 -2
- package/babel.config.js +0 -6
- package/bin/__mocks__/api.js +0 -48
- package/bin/__mocks__/api.js.map +0 -1
- package/bin/add-project.js +0 -104
- package/bin/add-project.js.map +0 -1
- package/bin/api.js +0 -54
- package/bin/api.js.map +0 -1
- package/bin/component-folders.js +0 -59
- package/bin/component-folders.js.map +0 -1
- package/bin/config.js +0 -242
- package/bin/config.js.map +0 -1
- package/bin/config.test.js +0 -93
- package/bin/config.test.js.map +0 -1
- package/bin/consts.js +0 -57
- package/bin/consts.js.map +0 -1
- package/bin/ditto.js.map +0 -1
- package/bin/generate-suggestions.js +0 -183
- package/bin/generate-suggestions.js.map +0 -1
- package/bin/generate-suggestions.test.js +0 -200
- package/bin/generate-suggestions.test.js.map +0 -1
- package/bin/http/__mocks__/fetchComponentFolders.js +0 -71
- package/bin/http/__mocks__/fetchComponentFolders.js.map +0 -1
- package/bin/http/__mocks__/fetchComponents.js +0 -73
- package/bin/http/__mocks__/fetchComponents.js.map +0 -1
- package/bin/http/__mocks__/fetchVariants.js +0 -71
- package/bin/http/__mocks__/fetchVariants.js.map +0 -1
- package/bin/http/fetchComponentFolders.js +0 -64
- package/bin/http/fetchComponentFolders.js.map +0 -1
- package/bin/http/fetchComponents.js +0 -78
- package/bin/http/fetchComponents.js.map +0 -1
- package/bin/http/fetchVariants.js +0 -87
- package/bin/http/fetchVariants.js.map +0 -1
- package/bin/http/http.test.js +0 -159
- package/bin/http/http.test.js.map +0 -1
- package/bin/http/importComponents.js +0 -114
- package/bin/http/importComponents.js.map +0 -1
- package/bin/importComponents.js +0 -65
- package/bin/importComponents.js.map +0 -1
- package/bin/init/init.js +0 -126
- package/bin/init/init.js.map +0 -1
- package/bin/init/project.js +0 -182
- package/bin/init/project.js.map +0 -1
- package/bin/init/project.test.js +0 -26
- package/bin/init/project.test.js.map +0 -1
- package/bin/init/token.js +0 -196
- package/bin/init/token.js.map +0 -1
- package/bin/init/token.test.js +0 -147
- package/bin/init/token.test.js.map +0 -1
- package/bin/output.js +0 -76
- package/bin/output.js.map +0 -1
- package/bin/pull-lib.test.js +0 -379
- package/bin/pull-lib.test.js.map +0 -1
- package/bin/pull.js +0 -562
- package/bin/pull.js.map +0 -1
- package/bin/pull.test.js +0 -151
- package/bin/pull.test.js.map +0 -1
- package/bin/remove-project.js +0 -99
- package/bin/remove-project.js.map +0 -1
- package/bin/replace.js +0 -171
- package/bin/replace.js.map +0 -1
- package/bin/replace.test.js +0 -197
- package/bin/replace.test.js.map +0 -1
- package/bin/types.js +0 -21
- package/bin/types.js.map +0 -1
- package/bin/utils/cleanFileName.js +0 -40
- package/bin/utils/cleanFileName.js.map +0 -1
- package/bin/utils/cleanFileName.test.js +0 -15
- package/bin/utils/cleanFileName.test.js.map +0 -1
- package/bin/utils/createSentryContext.js +0 -43
- package/bin/utils/createSentryContext.js.map +0 -1
- package/bin/utils/determineModuleType.js +0 -79
- package/bin/utils/determineModuleType.js.map +0 -1
- package/bin/utils/determineModuleType.test.js +0 -60
- package/bin/utils/determineModuleType.test.js.map +0 -1
- package/bin/utils/generateIOSBundles.js +0 -147
- package/bin/utils/generateIOSBundles.js.map +0 -1
- package/bin/utils/generateJsDriver.js +0 -178
- package/bin/utils/generateJsDriver.js.map +0 -1
- package/bin/utils/generateJsDriverTypeFile.js +0 -105
- package/bin/utils/generateJsDriverTypeFile.js.map +0 -1
- package/bin/utils/generateSwiftDriver.js +0 -93
- package/bin/utils/generateSwiftDriver.js.map +0 -1
- package/bin/utils/getSelectedProjects.js +0 -67
- package/bin/utils/getSelectedProjects.js.map +0 -1
- package/bin/utils/processMetaOption.js +0 -40
- package/bin/utils/processMetaOption.js.map +0 -1
- package/bin/utils/processMetaOption.test.js +0 -45
- package/bin/utils/processMetaOption.test.js.map +0 -1
- package/bin/utils/projectsToText.js +0 -58
- package/bin/utils/projectsToText.js.map +0 -1
- package/bin/utils/promptForProject.js +0 -96
- package/bin/utils/promptForProject.js.map +0 -1
- package/bin/utils/quit.js +0 -73
- package/bin/utils/quit.js.map +0 -1
- package/bin/utils/sourcesToText.js +0 -57
- package/bin/utils/sourcesToText.js.map +0 -1
- package/etsc.config.js +0 -13
- package/jest.config.ts +0 -16
- package/jsconfig.json +0 -5
- package/lib/__mocks__/api.ts +0 -12
- package/lib/add-project.ts +0 -48
- package/lib/api.ts +0 -16
- package/lib/component-folders.ts +0 -9
- package/lib/config.test.ts +0 -79
- package/lib/config.ts +0 -279
- package/lib/consts.ts +0 -22
- package/lib/ditto.ts +0 -285
- package/lib/generate-suggestions.test.ts +0 -169
- package/lib/generate-suggestions.ts +0 -166
- package/lib/http/__mocks__/fetchComponentFolders.ts +0 -23
- package/lib/http/__mocks__/fetchComponents.ts +0 -24
- package/lib/http/__mocks__/fetchVariants.ts +0 -21
- package/lib/http/fetchComponentFolders.ts +0 -23
- package/lib/http/fetchComponents.ts +0 -43
- package/lib/http/fetchVariants.ts +0 -42
- package/lib/http/http.test.ts +0 -122
- package/lib/http/importComponents.ts +0 -79
- package/lib/importComponents.ts +0 -24
- package/lib/init/init.ts +0 -79
- package/lib/init/project.test.ts +0 -26
- package/lib/init/project.ts +0 -136
- package/lib/init/token.test.ts +0 -99
- package/lib/init/token.ts +0 -156
- package/lib/output.ts +0 -21
- package/lib/pull-lib.test.ts +0 -367
- package/lib/pull.test.ts +0 -117
- package/lib/pull.ts +0 -629
- package/lib/remove-project.ts +0 -44
- package/lib/replace.test.ts +0 -157
- package/lib/replace.ts +0 -140
- package/lib/types.ts +0 -83
- package/lib/utils/cleanFileName.test.ts +0 -11
- package/lib/utils/cleanFileName.ts +0 -8
- package/lib/utils/createSentryContext.ts +0 -20
- package/lib/utils/determineModuleType.test.ts +0 -48
- package/lib/utils/determineModuleType.ts +0 -55
- package/lib/utils/generateIOSBundles.ts +0 -122
- package/lib/utils/generateJsDriver.ts +0 -207
- package/lib/utils/generateJsDriverTypeFile.ts +0 -75
- package/lib/utils/generateSwiftDriver.ts +0 -48
- package/lib/utils/getSelectedProjects.ts +0 -36
- package/lib/utils/processMetaOption.test.ts +0 -18
- package/lib/utils/processMetaOption.ts +0 -16
- package/lib/utils/projectsToText.ts +0 -29
- package/lib/utils/promptForProject.ts +0 -61
- package/lib/utils/quit.ts +0 -7
- package/lib/utils/sourcesToText.ts +0 -25
- package/pull_request_template.md +0 -20
- package/testfiles/en.json +0 -5
- package/testfiles/es.json +0 -5
- package/testfiles/fr.json +0 -5
- package/testfiles/test1.jsx +0 -18
- package/testfiles/test2.jsx +0 -9
- package/testing/.gitkeep +0 -0
- package/testing/fixtures/bad-yaml.yml +0 -6
- package/testing/fixtures/ditto-config-no-token +0 -2
- package/testing/fixtures/project-config-empty-projects.yml +0 -1
- package/testing/fixtures/project-config-no-id.yml +0 -2
- package/testing/fixtures/project-config-no-name.yml +0 -2
- package/testing/fixtures/project-config-pull.yml +0 -0
- package/testing/fixtures/project-config-working.yml +0 -3
- package/tsconfig.json +0 -16
package/lib/replace.test.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import { parseOptions, replaceJSXTextInFile } from "./replace"; // Assuming the function is exported in a separate file
|
|
3
|
-
|
|
4
|
-
jest.mock("fs");
|
|
5
|
-
|
|
6
|
-
// Helper function to create a temporary file
|
|
7
|
-
async function createTempJSXFile(content: string): Promise<string> {
|
|
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
|
-
});
|
|
16
|
-
return tempFile;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Helper function to delete the temporary file
|
|
20
|
-
async function deleteTempFile(filePath: string): Promise<void> {
|
|
21
|
-
await new Promise((resolve, reject) => {
|
|
22
|
-
try {
|
|
23
|
-
fs.unlink(filePath, resolve);
|
|
24
|
-
} catch (e) {
|
|
25
|
-
reject(e);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("parseOptions", () => {
|
|
31
|
-
test("should pass with valid input", async () => {
|
|
32
|
-
const tempFile = await createTempJSXFile("<div>Hello, world!</div>");
|
|
33
|
-
expect(() =>
|
|
34
|
-
parseOptions([tempFile, "secondString", "thirdString"])
|
|
35
|
-
).not.toThrow();
|
|
36
|
-
|
|
37
|
-
const result = parseOptions([tempFile, "secondString", "thirdString"]);
|
|
38
|
-
expect(result).toEqual({
|
|
39
|
-
filePath: tempFile,
|
|
40
|
-
searchString: "secondString",
|
|
41
|
-
replaceWith: "thirdString",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
deleteTempFile(tempFile);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("should throw error when options array does not have exactly three strings", () => {
|
|
48
|
-
expect(() => parseOptions(["oneString"])).toThrow(
|
|
49
|
-
"The options array must contain <file path> <search string> <replace with>."
|
|
50
|
-
);
|
|
51
|
-
expect(() => parseOptions(["one", "two"])).toThrow(
|
|
52
|
-
"The options array must contain <file path> <search string> <replace with>."
|
|
53
|
-
);
|
|
54
|
-
expect(() => parseOptions(["one", "two", "three", "four"])).toThrow(
|
|
55
|
-
"The options array must contain <file path> <search string> <replace with>."
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("should throw error when the first string is not a valid file path", () => {
|
|
60
|
-
const invalidFilePath = "/path/to/invalid/file.txt";
|
|
61
|
-
expect(() =>
|
|
62
|
-
parseOptions([invalidFilePath, "secondString", "thirdString"])
|
|
63
|
-
).toThrow(`${invalidFilePath} is not a valid file path.`);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("should throw error when the first string is a directory", () => {
|
|
67
|
-
const directoryPath = ".";
|
|
68
|
-
expect(() =>
|
|
69
|
-
parseOptions([directoryPath, "secondString", "thirdString"])
|
|
70
|
-
).toThrow(`${directoryPath} is not a valid file path.`);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("replaceJSXTextInFile", () => {
|
|
75
|
-
afterEach(async () => {
|
|
76
|
-
await deleteTempFile("/tempFile.jsx");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("should replace JSX text with a DittoComponent", async () => {
|
|
80
|
-
const tempFile = await createTempJSXFile("<div>Hello, world</div>");
|
|
81
|
-
const searchString = "world";
|
|
82
|
-
const replaceWith = "some-id";
|
|
83
|
-
|
|
84
|
-
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
85
|
-
|
|
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
|
-
});
|
|
95
|
-
expect(transformedCode).toContain(
|
|
96
|
-
`<div>Hello, <DittoComponent componentId="${replaceWith}" /></div>`
|
|
97
|
-
);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("should replace JSX text with a DittoComponent with a flag", async () => {
|
|
101
|
-
const tempFile = await createTempJSXFile(
|
|
102
|
-
`<>\n<div>Hello, world</div>\n<div>Hello, world</div>\n</>`
|
|
103
|
-
);
|
|
104
|
-
const searchString = "world";
|
|
105
|
-
const replaceWith = "some-id";
|
|
106
|
-
|
|
107
|
-
await replaceJSXTextInFile(
|
|
108
|
-
tempFile,
|
|
109
|
-
{ searchString, replaceWith },
|
|
110
|
-
{ lineNumbers: [3] }
|
|
111
|
-
);
|
|
112
|
-
|
|
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
|
-
);
|
|
119
|
-
expect(transformedCode).toContain(
|
|
120
|
-
`<>\n <div>Hello, world</div>\n <div>Hello, <DittoComponent componentId=\"some-id\" /></div>\n</>;`
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("should handle case-insensitive search", async () => {
|
|
125
|
-
const tempFile = await createTempJSXFile("<div>HeLLo, WoRlD</div>");
|
|
126
|
-
const searchString = "world";
|
|
127
|
-
const replaceWith = "some-id";
|
|
128
|
-
|
|
129
|
-
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
130
|
-
|
|
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
|
-
);
|
|
137
|
-
expect(transformedCode).toContain(
|
|
138
|
-
`<div>HeLLo, <DittoComponent componentId="${replaceWith}" /></div>`
|
|
139
|
-
);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("should not replace JSX text if searchString is not found", async () => {
|
|
143
|
-
const tempFile = await createTempJSXFile("<div>Hello, world!</div>");
|
|
144
|
-
const searchString = "foobar";
|
|
145
|
-
const replaceWith = "some-id";
|
|
146
|
-
|
|
147
|
-
await replaceJSXTextInFile(tempFile, { searchString, replaceWith }, {});
|
|
148
|
-
|
|
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
|
-
);
|
|
155
|
-
expect(transformedCode).toContain("<div>Hello, world!</div>");
|
|
156
|
-
});
|
|
157
|
-
});
|
package/lib/replace.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import { parse } from "@babel/parser";
|
|
3
|
-
import traverse from "@babel/traverse";
|
|
4
|
-
import * as t from "@babel/types";
|
|
5
|
-
import { transformFromAst } from "@babel/core";
|
|
6
|
-
|
|
7
|
-
async function replaceJSXTextInFile(
|
|
8
|
-
filePath: string,
|
|
9
|
-
replacement: { searchString: string; replaceWith: string },
|
|
10
|
-
flags: {
|
|
11
|
-
lineNumbers?: number[];
|
|
12
|
-
}
|
|
13
|
-
) {
|
|
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
|
-
);
|
|
23
|
-
const ast = parse(code, {
|
|
24
|
-
sourceType: "module",
|
|
25
|
-
plugins: ["jsx", "typescript"],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
traverse(ast, {
|
|
29
|
-
JSXText(path) {
|
|
30
|
-
const { searchString, replaceWith } = replacement;
|
|
31
|
-
|
|
32
|
-
const searchStringEscaped = searchString.replace(
|
|
33
|
-
/[.*+?^${}()|[\]\\]/g,
|
|
34
|
-
"\\$&"
|
|
35
|
-
);
|
|
36
|
-
const regex = new RegExp(searchStringEscaped, "gi");
|
|
37
|
-
if (regex.test(path.node.value)) {
|
|
38
|
-
// Ignore if not on a line number that we want to replace.
|
|
39
|
-
if (
|
|
40
|
-
flags.lineNumbers &&
|
|
41
|
-
path.node.loc &&
|
|
42
|
-
!flags.lineNumbers.includes(path.node.loc.start.line)
|
|
43
|
-
) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const splitValues = splitByCaseInsensitive(
|
|
48
|
-
path.node.value,
|
|
49
|
-
searchStringEscaped
|
|
50
|
-
);
|
|
51
|
-
const nodes: (t.JSXElement | t.JSXText)[] = [];
|
|
52
|
-
|
|
53
|
-
splitValues.forEach((splitValue) => {
|
|
54
|
-
if (splitValue.toLowerCase() === searchString.toLowerCase()) {
|
|
55
|
-
const identifier = t.jsxIdentifier("DittoComponent");
|
|
56
|
-
const componentId = t.jsxAttribute(
|
|
57
|
-
t.jsxIdentifier("componentId"),
|
|
58
|
-
t.stringLiteral(replaceWith)
|
|
59
|
-
);
|
|
60
|
-
const o = t.jsxOpeningElement(identifier, [componentId], true);
|
|
61
|
-
const jsxElement = t.jsxElement(o, undefined, [], true);
|
|
62
|
-
nodes.push(jsxElement);
|
|
63
|
-
} else {
|
|
64
|
-
nodes.push(t.jsxText(splitValue));
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
path.replaceWithMultiple(nodes);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// transfromFromAst types are wrong?
|
|
74
|
-
/* @ts-ignore */
|
|
75
|
-
const { code: transformedCode } = transformFromAst(ast, code, {
|
|
76
|
-
// Don't let this codebase's Babel config affect the code we're transforming.
|
|
77
|
-
/* @ts-ignore */
|
|
78
|
-
configFile: false,
|
|
79
|
-
});
|
|
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
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function splitByCaseInsensitive(str: string, delimiter: string) {
|
|
93
|
-
return str.split(new RegExp(`(${delimiter})`, "gi")).filter((s) => s !== "");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function replace(options: string[], flags: { lineNumbers?: number[] }) {
|
|
97
|
-
let filePath: string;
|
|
98
|
-
let searchString: string;
|
|
99
|
-
let replaceWith: string;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const parsedOptions = parseOptions(options);
|
|
103
|
-
filePath = parsedOptions.filePath;
|
|
104
|
-
searchString = parsedOptions.searchString;
|
|
105
|
-
replaceWith = parsedOptions.replaceWith;
|
|
106
|
-
} catch (e) {
|
|
107
|
-
console.error(e);
|
|
108
|
-
console.error(
|
|
109
|
-
"Usage for replace: ditto-cli replace <file path> <search string> <replace with>"
|
|
110
|
-
);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
replaceJSXTextInFile(filePath, { searchString, replaceWith }, flags);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function parseOptions(options: string[]): {
|
|
118
|
-
filePath: string;
|
|
119
|
-
searchString: string;
|
|
120
|
-
replaceWith: string;
|
|
121
|
-
} {
|
|
122
|
-
if (options.length !== 3) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
"The options array must contain <file path> <search string> <replace with>."
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const filePath = options[0];
|
|
129
|
-
// Check if the file path exists and points to a regular file (not a directory or other file system object).
|
|
130
|
-
const isFilePathValid =
|
|
131
|
-
fs.existsSync(filePath) && fs.lstatSync(filePath).isFile();
|
|
132
|
-
|
|
133
|
-
if (!isFilePathValid) {
|
|
134
|
-
throw new Error(`${filePath} is not a valid file path.`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return { filePath, searchString: options[1], replaceWith: options[2] };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export { replace, parseOptions, replaceJSXTextInFile };
|
package/lib/types.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
export interface Project {
|
|
2
|
-
name: string;
|
|
3
|
-
id: string;
|
|
4
|
-
url?: string;
|
|
5
|
-
fileName?: string;
|
|
6
|
-
status?: string;
|
|
7
|
-
exclude_components?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type ComponentSource = ComponentFolder & {
|
|
11
|
-
type: "components";
|
|
12
|
-
fileName: string;
|
|
13
|
-
variant: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type Source = (Project & { type?: undefined }) | ComponentSource;
|
|
17
|
-
|
|
18
|
-
export interface ComponentFolder {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
status?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type SupportedFormat =
|
|
25
|
-
| "flat"
|
|
26
|
-
| "nested"
|
|
27
|
-
| "structured"
|
|
28
|
-
| "android"
|
|
29
|
-
| "ios-strings"
|
|
30
|
-
| "ios-stringsdict"
|
|
31
|
-
| "icu";
|
|
32
|
-
|
|
33
|
-
export type SupportedExtension = ".json" | ".xml" | ".strings" | ".stringsdict";
|
|
34
|
-
|
|
35
|
-
type ComponentsSourceBool = boolean;
|
|
36
|
-
type ComponentsSourceConfig = {
|
|
37
|
-
root?: boolean | { status: string };
|
|
38
|
-
folders?: ComponentFolder[];
|
|
39
|
-
};
|
|
40
|
-
type ComponentsSource = ComponentsSourceBool | ComponentsSourceConfig;
|
|
41
|
-
|
|
42
|
-
export interface ConfigYAML {
|
|
43
|
-
sources?: {
|
|
44
|
-
components?: ComponentsSource;
|
|
45
|
-
projects?: Project[];
|
|
46
|
-
};
|
|
47
|
-
format?: SupportedFormat;
|
|
48
|
-
status?: string;
|
|
49
|
-
variants?: boolean;
|
|
50
|
-
richText?: boolean;
|
|
51
|
-
|
|
52
|
-
// TODO: might want to rename this at some point
|
|
53
|
-
iosLocales?: Record<string, string>[];
|
|
54
|
-
|
|
55
|
-
// prevents the generation of index.js and index.d.ts files
|
|
56
|
-
// when working with JSON formats
|
|
57
|
-
disableJsDriver?: boolean;
|
|
58
|
-
|
|
59
|
-
// these are legacy fields - if they exist, we should output
|
|
60
|
-
// a deprecation error, and suggest that they nest them under
|
|
61
|
-
// a top-level `sources` property
|
|
62
|
-
components?: boolean;
|
|
63
|
-
projects?: Project[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface SourceInformation {
|
|
67
|
-
hasSourceData: boolean;
|
|
68
|
-
hasTopLevelProjectsField: boolean;
|
|
69
|
-
hasTopLevelComponentsField: boolean;
|
|
70
|
-
hasComponentLibraryInProjects: boolean;
|
|
71
|
-
validProjects: Project[];
|
|
72
|
-
shouldFetchComponentLibrary: boolean;
|
|
73
|
-
variants: boolean;
|
|
74
|
-
format: string | string[] | undefined;
|
|
75
|
-
status: string | undefined;
|
|
76
|
-
richText: boolean | undefined;
|
|
77
|
-
componentRoot: boolean | { status: string } | undefined;
|
|
78
|
-
componentFolders: ComponentFolder[] | undefined;
|
|
79
|
-
localeByVariantApiId: Record<string, string> | undefined;
|
|
80
|
-
disableJsDriver?: boolean;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export type Token = string | undefined;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { cleanFileName } from "./cleanFileName";
|
|
2
|
-
|
|
3
|
-
describe("cleanFileName tests", () => {
|
|
4
|
-
it("correctly cleans emojis", () => {
|
|
5
|
-
const folderName = "👍Good Folder";
|
|
6
|
-
expect(cleanFileName(folderName)).toEqual("good-folder");
|
|
7
|
-
|
|
8
|
-
const fileName = "👍 Good Folder";
|
|
9
|
-
expect(cleanFileName(fileName)).toEqual("good-folder");
|
|
10
|
-
});
|
|
11
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
type SentryContext = Record<
|
|
2
|
-
string,
|
|
3
|
-
string | number | boolean | null | undefined
|
|
4
|
-
>;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Sentry context only supports properties one layer deep
|
|
8
|
-
*/
|
|
9
|
-
export function createSentryContext(obj: unknown) {
|
|
10
|
-
if (typeof obj !== "object") return {};
|
|
11
|
-
|
|
12
|
-
const ctx: SentryContext = {};
|
|
13
|
-
for (const key in obj) {
|
|
14
|
-
const k = key as keyof typeof obj;
|
|
15
|
-
const r = obj[k];
|
|
16
|
-
ctx[k] = typeof r === "object" || Array.isArray(r) ? JSON.stringify(r) : r;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return ctx;
|
|
20
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
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(currentDir: string | null = process.cwd()) {
|
|
13
|
-
const value = getRawTypeFromPackageJson(currentDir);
|
|
14
|
-
return getTypeOrDefault(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getRawTypeFromPackageJson(currentDir: string | null) {
|
|
18
|
-
if (process.env.DITTO_MODULE_TYPE) {
|
|
19
|
-
return process.env.DITTO_MODULE_TYPE;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
while (currentDir) {
|
|
23
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
24
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
25
|
-
const packageJsonContents = fs.readFileSync(packageJsonPath, "utf8");
|
|
26
|
-
try {
|
|
27
|
-
const packageData: { type?: string } = JSON.parse(packageJsonContents);
|
|
28
|
-
if (packageData?.type) {
|
|
29
|
-
return packageData.type;
|
|
30
|
-
}
|
|
31
|
-
} catch {}
|
|
32
|
-
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (currentDir === "/") {
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Move up a directory and continue the search
|
|
41
|
-
currentDir = path.dirname(currentDir);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// No package.json
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getTypeOrDefault(value: string | null): ModuleType {
|
|
49
|
-
const valueLower = value?.toLowerCase() || "";
|
|
50
|
-
if (valueLower === "commonjs" || valueLower === "module") {
|
|
51
|
-
return valueLower;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return "commonjs";
|
|
55
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
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
|
-
}
|