@dittowords/cli 2.5.1 → 2.6.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/.idea/workspace.xml +124 -0
- package/README.md +23 -10
- package/babel.config.js +2 -8
- package/bin/add-project.js +38 -0
- package/bin/add-project.js.map +1 -0
- package/bin/api.js +20 -0
- package/bin/api.js.map +1 -0
- package/bin/config.js +174 -0
- package/bin/config.js.map +1 -0
- package/bin/consts.js +21 -0
- package/bin/consts.js.map +1 -0
- package/bin/ditto.js +71 -74
- package/bin/ditto.js.map +1 -0
- package/bin/init/init.js +39 -0
- package/bin/init/init.js.map +1 -0
- package/bin/init/project.js +115 -0
- package/bin/init/project.js.map +1 -0
- package/bin/init/token.js +89 -0
- package/bin/init/token.js.map +1 -0
- package/bin/output.js +34 -0
- package/bin/output.js.map +1 -0
- package/bin/pull.js +305 -0
- package/bin/pull.js.map +1 -0
- package/bin/remove-project.js +40 -0
- package/bin/remove-project.js.map +1 -0
- package/bin/types.js +3 -0
- package/bin/types.js.map +1 -0
- package/bin/utils/getSelectedProjects.js +76 -0
- package/bin/utils/getSelectedProjects.js.map +1 -0
- package/bin/utils/processMetaOption.js +15 -0
- package/bin/utils/processMetaOption.js.map +1 -0
- package/bin/utils/projectsToText.js +16 -0
- package/bin/utils/projectsToText.js.map +1 -0
- package/bin/utils/promptForProject.js +44 -0
- package/bin/utils/promptForProject.js.map +1 -0
- package/bin/utils/sourcesToText.js +25 -0
- package/bin/utils/sourcesToText.js.map +1 -0
- package/jest.config.js +1 -1
- package/lib/{add-project.js → add-project.ts} +8 -8
- package/lib/api.ts +15 -0
- package/lib/{config.test.js → config.test.ts} +14 -25
- package/lib/config.ts +214 -0
- package/lib/consts.ts +20 -0
- package/lib/ditto.ts +97 -0
- package/lib/init/{init.js → init.ts} +11 -12
- package/lib/init/project.test.ts +49 -0
- package/lib/init/{project.js → project.ts} +31 -29
- package/lib/init/{token.test.js → token.test.ts} +3 -3
- package/lib/init/{token.js → token.ts} +23 -19
- package/lib/output.ts +21 -0
- package/lib/pull.test.ts +172 -0
- package/lib/{pull.js → pull.ts} +145 -98
- package/lib/{remove-project.js → remove-project.ts} +11 -15
- package/lib/types.ts +23 -0
- package/lib/utils/getSelectedProjects.ts +55 -0
- package/lib/utils/processMetaOption.test.ts +18 -0
- package/lib/utils/processMetaOption.ts +16 -0
- package/lib/utils/{projectsToText.js → projectsToText.ts} +5 -4
- package/lib/utils/{promptForProject.js → promptForProject.ts} +18 -9
- package/lib/utils/{sourcesToText.js → sourcesToText.ts} +6 -5
- package/package.json +20 -15
- package/testing/fixtures/project-config-pull.yml +0 -0
- package/tsconfig.json +12 -0
- package/yarn-error.log +4466 -0
- package/lib/api.js +0 -18
- package/lib/config.js +0 -169
- package/lib/consts.js +0 -14
- package/lib/init/project.test.js +0 -99
- package/lib/output.js +0 -21
- package/lib/pull.test.js +0 -173
- package/lib/utils/getSelectedProjects.js +0 -44
- package/lib/utils/processMetaOption.js +0 -16
- package/lib/utils/processMetaOption.test.js +0 -16
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import chalk from "chalk";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { prompt } from "enquirer";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import { create } from "../api";
|
|
8
|
+
import consts from "../consts";
|
|
9
|
+
import output from "../output";
|
|
10
|
+
import config from "../config";
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
export const needsToken = (configFile?: string, host = consts.API_HOST) => {
|
|
13
13
|
if (config.getTokenFromEnv()) {
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const file = configFile || consts.CONFIG_FILE;
|
|
18
18
|
if (!fs.existsSync(file)) return true;
|
|
19
|
-
const configData = config.
|
|
19
|
+
const configData = config.readGlobalConfigData(file);
|
|
20
20
|
if (
|
|
21
21
|
!configData[config.justTheHost(host)] ||
|
|
22
22
|
configData[config.justTheHost(host)][0].token === ""
|
|
23
23
|
)
|
|
24
24
|
return true;
|
|
25
25
|
return false;
|
|
26
|
-
}
|
|
26
|
+
};
|
|
27
27
|
|
|
28
28
|
// Returns true if valid, otherwise an error message.
|
|
29
|
-
async function checkToken(token) {
|
|
30
|
-
const axios =
|
|
29
|
+
async function checkToken(token: string): Promise<any> {
|
|
30
|
+
const axios = create(token);
|
|
31
31
|
const endpoint = "/token-check";
|
|
32
32
|
|
|
33
33
|
const resOrError = await axios
|
|
34
34
|
.get(endpoint)
|
|
35
|
-
.catch((error) => {
|
|
35
|
+
.catch((error: any) => {
|
|
36
36
|
if (error.code === "ENOTFOUND") {
|
|
37
37
|
return output.errorText(
|
|
38
38
|
`Can't connect to API: ${output.url(error.hostname)}`
|
|
@@ -48,7 +48,6 @@ async function checkToken(token) {
|
|
|
48
48
|
.catch(() =>
|
|
49
49
|
output.errorText("Sorry! We're having trouble reaching the Ditto API.")
|
|
50
50
|
);
|
|
51
|
-
|
|
52
51
|
if (typeof resOrError === "string") return resOrError;
|
|
53
52
|
|
|
54
53
|
if (resOrError.status === 200) return true;
|
|
@@ -56,7 +55,7 @@ async function checkToken(token) {
|
|
|
56
55
|
return output.errorText("This API key isn't valid. Please try another.");
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
async function collectToken(message) {
|
|
58
|
+
async function collectToken(message: string | null) {
|
|
60
59
|
const blue = output.info;
|
|
61
60
|
const apiUrl = output.url("https://app.dittowords.com/account/user");
|
|
62
61
|
const breadcrumbs = `${blue("User")}`;
|
|
@@ -67,7 +66,7 @@ async function collectToken(message) {
|
|
|
67
66
|
)}".`;
|
|
68
67
|
console.log(tokenDescription);
|
|
69
68
|
|
|
70
|
-
const response = await prompt({
|
|
69
|
+
const response = await prompt<{ token: string }>({
|
|
71
70
|
type: "input",
|
|
72
71
|
name: "token",
|
|
73
72
|
message: "What is your API key?",
|
|
@@ -82,7 +81,12 @@ function quit(exitCode = 2) {
|
|
|
82
81
|
process.exit();
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @param {string | null} message
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
export const collectAndSaveToken = async (message: string | null = null) => {
|
|
86
90
|
try {
|
|
87
91
|
const token = await collectToken(message);
|
|
88
92
|
console.log(
|
|
@@ -97,6 +101,6 @@ async function collectAndSaveToken(message = null) {
|
|
|
97
101
|
} catch (error) {
|
|
98
102
|
quit();
|
|
99
103
|
}
|
|
100
|
-
}
|
|
104
|
+
};
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
export default { needsToken, collectAndSaveToken };
|
package/lib/output.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
export const errorText = (msg: string) => chalk.magenta(msg);
|
|
4
|
+
export const warnText = (msg: string) => chalk.yellow(msg);
|
|
5
|
+
export const info = (msg: string) => chalk.blueBright(msg);
|
|
6
|
+
export const success = (msg: string) => chalk.green(msg);
|
|
7
|
+
export const url = (msg: string) => chalk.blueBright.underline(msg);
|
|
8
|
+
export const subtle = (msg: string) => chalk.grey(msg);
|
|
9
|
+
export const write = (msg: string) => chalk.white(msg);
|
|
10
|
+
export const nl = () => console.log("\n");
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
errorText,
|
|
14
|
+
warnText,
|
|
15
|
+
info,
|
|
16
|
+
success,
|
|
17
|
+
url,
|
|
18
|
+
subtle,
|
|
19
|
+
write,
|
|
20
|
+
nl,
|
|
21
|
+
};
|
package/lib/pull.test.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import api from "./api";
|
|
4
|
+
import config from "./config";
|
|
5
|
+
|
|
6
|
+
const testProjects = [
|
|
7
|
+
{
|
|
8
|
+
id: "1",
|
|
9
|
+
name: "Project 1",
|
|
10
|
+
fileName: "Project 1",
|
|
11
|
+
},
|
|
12
|
+
{ id: "2", name: "Project 2", fileName: "Project 2" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
jest.mock("./api");
|
|
16
|
+
|
|
17
|
+
const mockApi = api as jest.Mocked<typeof api>;
|
|
18
|
+
|
|
19
|
+
jest.mock("./consts", () => ({
|
|
20
|
+
TEXT_DIR: ".testing",
|
|
21
|
+
API_HOST: "https://api.dittowords.com",
|
|
22
|
+
CONFIG_FILE: ".testing/ditto",
|
|
23
|
+
PROJECT_CONFIG_FILE: ".testing/config.yml",
|
|
24
|
+
TEXT_FILE: ".testing/text.json",
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import consts from "./consts";
|
|
28
|
+
import allPull from "./pull";
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
_testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
|
|
32
|
+
} = allPull;
|
|
33
|
+
const variant = "english";
|
|
34
|
+
|
|
35
|
+
const cleanOutputDir = () => {
|
|
36
|
+
if (fs.existsSync(consts.TEXT_DIR))
|
|
37
|
+
fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
|
|
38
|
+
|
|
39
|
+
fs.mkdirSync(consts.TEXT_DIR);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("cleanOutputFiles", () => {
|
|
47
|
+
it("removes .js, .json, .xml, and .strings files", () => {
|
|
48
|
+
cleanOutputDir();
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.json"), "test");
|
|
51
|
+
fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.js"), "test");
|
|
52
|
+
fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.xml"), "test");
|
|
53
|
+
fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.strings"), "test");
|
|
54
|
+
// this file shouldn't be deleted
|
|
55
|
+
fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.txt"), "test");
|
|
56
|
+
|
|
57
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(5);
|
|
58
|
+
|
|
59
|
+
cleanOutputFiles();
|
|
60
|
+
|
|
61
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// describe("downloadAndSaveVariant", () => {
|
|
66
|
+
// beforeAll(() => {
|
|
67
|
+
// if (!fs.existsSync(consts.TEXT_DIR)) {
|
|
68
|
+
// fs.mkdirSync(consts.TEXT_DIR);
|
|
69
|
+
// }
|
|
70
|
+
// });
|
|
71
|
+
|
|
72
|
+
// it("writes a single file for default format", async () => {
|
|
73
|
+
// cleanOutputDir();
|
|
74
|
+
|
|
75
|
+
// const output = await downloadAndSaveVariant(variant, testProjects, "");
|
|
76
|
+
// expect(/saved to.*english\.json/.test(output)).toEqual(true);
|
|
77
|
+
// expect(output.match(/saved to/g)?.length).toEqual(1);
|
|
78
|
+
// expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
|
|
79
|
+
// });
|
|
80
|
+
|
|
81
|
+
// it("writes multiple files for flat format", async () => {
|
|
82
|
+
// cleanOutputDir();
|
|
83
|
+
|
|
84
|
+
// const output = await downloadAndSaveVariant(variant, testProjects, "flat");
|
|
85
|
+
|
|
86
|
+
// expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
|
|
87
|
+
// expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
|
|
88
|
+
// expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
89
|
+
// expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
90
|
+
// });
|
|
91
|
+
|
|
92
|
+
// it("writes multiple files for structured format", async () => {
|
|
93
|
+
// cleanOutputDir();
|
|
94
|
+
|
|
95
|
+
// const output = await downloadAndSaveVariant(
|
|
96
|
+
// variant,
|
|
97
|
+
// testProjects,
|
|
98
|
+
// "structured"
|
|
99
|
+
// );
|
|
100
|
+
|
|
101
|
+
// expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
|
|
102
|
+
// expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
|
|
103
|
+
// expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
104
|
+
// expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
105
|
+
// });
|
|
106
|
+
// });
|
|
107
|
+
|
|
108
|
+
describe("downloadAndSaveBase", () => {
|
|
109
|
+
beforeAll(() => {
|
|
110
|
+
if (!fs.existsSync(consts.TEXT_DIR)) {
|
|
111
|
+
fs.mkdirSync(consts.TEXT_DIR);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("writes to text.json for default format", async () => {
|
|
116
|
+
cleanOutputDir();
|
|
117
|
+
|
|
118
|
+
mockApi.get.mockResolvedValueOnce({ data: [] });
|
|
119
|
+
const output = await downloadAndSaveBase(testProjects, "");
|
|
120
|
+
|
|
121
|
+
expect(/saved to.*text\.json/.test(output)).toEqual(true);
|
|
122
|
+
expect(output.match(/saved to/g)?.length).toEqual(1);
|
|
123
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("writes multiple files for flat format", async () => {
|
|
127
|
+
cleanOutputDir();
|
|
128
|
+
|
|
129
|
+
mockApi.get.mockResolvedValue({ data: [] });
|
|
130
|
+
const output = await downloadAndSaveBase(testProjects, "flat");
|
|
131
|
+
expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
|
|
132
|
+
expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
|
|
133
|
+
expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
134
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("writes multiple files for structured format", async () => {
|
|
138
|
+
cleanOutputDir();
|
|
139
|
+
|
|
140
|
+
mockApi.get.mockResolvedValue({ data: [] });
|
|
141
|
+
const output = await downloadAndSaveBase(testProjects, "structured");
|
|
142
|
+
|
|
143
|
+
expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
|
|
144
|
+
expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
|
|
145
|
+
expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
146
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("writes .xml files for android", async () => {
|
|
150
|
+
cleanOutputDir();
|
|
151
|
+
|
|
152
|
+
mockApi.get.mockResolvedValue({ data: "hello" });
|
|
153
|
+
const output = await downloadAndSaveBase(testProjects, "android");
|
|
154
|
+
|
|
155
|
+
expect(/saved to.*Project 1\.xml/.test(output)).toEqual(true);
|
|
156
|
+
expect(/saved to.*Project 2\.xml/.test(output)).toEqual(true);
|
|
157
|
+
expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
158
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("writes .strings files for ios-strings", async () => {
|
|
162
|
+
cleanOutputDir();
|
|
163
|
+
|
|
164
|
+
mockApi.get.mockResolvedValue({ data: "hello" });
|
|
165
|
+
const output = await downloadAndSaveBase(testProjects, "ios-strings");
|
|
166
|
+
|
|
167
|
+
expect(/saved to.*Project 1\.strings/.test(output)).toEqual(true);
|
|
168
|
+
expect(/saved to.*Project 2\.strings/.test(output)).toEqual(true);
|
|
169
|
+
expect(output.match(/saved to/g)?.length).toEqual(2);
|
|
170
|
+
expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
|
|
171
|
+
});
|
|
172
|
+
});
|
package/lib/{pull.js → pull.ts}
RENAMED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import ora from "ora";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
import api from "./api";
|
|
7
|
+
import config from "./config";
|
|
8
|
+
import consts from "./consts";
|
|
9
|
+
import output from "./output";
|
|
10
|
+
import { collectAndSaveToken } from "./init/token";
|
|
11
|
+
import sourcesToText from "./utils/sourcesToText";
|
|
12
|
+
import { SourceInformation, Token, Project } from "./types";
|
|
13
13
|
|
|
14
|
-
const NON_DEFAULT_FORMATS = ["flat", "structured"];
|
|
14
|
+
const NON_DEFAULT_FORMATS = ["flat", "structured", "android", "ios-strings"];
|
|
15
15
|
|
|
16
16
|
const DEFAULT_FORMAT_KEYS = ["projects", "exported_at"];
|
|
17
|
-
const hasVariantData = (data) => {
|
|
17
|
+
const hasVariantData = (data: any) => {
|
|
18
18
|
const hasTopLevelKeys =
|
|
19
19
|
Object.keys(data).filter((key) => !DEFAULT_FORMAT_KEYS.includes(key))
|
|
20
20
|
.length > 0;
|
|
@@ -31,6 +31,16 @@ async function askForAnotherToken() {
|
|
|
31
31
|
await collectAndSaveToken(message);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function getExtension(format: string) {
|
|
35
|
+
if (format === "android") {
|
|
36
|
+
return ".xml";
|
|
37
|
+
}
|
|
38
|
+
if (format === "ios-strings") {
|
|
39
|
+
return ".strings";
|
|
40
|
+
}
|
|
41
|
+
return ".json";
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
/**
|
|
35
45
|
* For a given variant:
|
|
36
46
|
* - if format is unspecified, fetch data for all projects from `/projects` and
|
|
@@ -38,15 +48,22 @@ async function askForAnotherToken() {
|
|
|
38
48
|
* - if format is `flat` or `structured`, fetch data for each project from `/project/:project_id` and
|
|
39
49
|
* save in `{projectName}-${variantApiId}.json`
|
|
40
50
|
*/
|
|
41
|
-
async function downloadAndSaveVariant(
|
|
42
|
-
|
|
51
|
+
async function downloadAndSaveVariant(
|
|
52
|
+
variantApiId: string | null,
|
|
53
|
+
projects: Project[],
|
|
54
|
+
format: string | undefined,
|
|
55
|
+
token?: Token
|
|
56
|
+
) {
|
|
57
|
+
const params: Record<string, string | null> = {
|
|
58
|
+
variant: variantApiId,
|
|
59
|
+
};
|
|
43
60
|
if (format) {
|
|
44
61
|
params.format = format;
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
if (NON_DEFAULT_FORMATS.includes(format)) {
|
|
64
|
+
if (format && NON_DEFAULT_FORMATS.includes(format)) {
|
|
48
65
|
const savedMessages = await Promise.all(
|
|
49
|
-
projects.map(async ({ id, fileName }) => {
|
|
66
|
+
projects.map(async ({ id, fileName }: Project) => {
|
|
50
67
|
const { data } = await api.get(`/projects/${id}`, {
|
|
51
68
|
params,
|
|
52
69
|
headers: { Authorization: `token ${token}` },
|
|
@@ -56,10 +73,16 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
|
|
|
56
73
|
return "";
|
|
57
74
|
}
|
|
58
75
|
|
|
59
|
-
const
|
|
76
|
+
const extension = getExtension(format);
|
|
77
|
+
|
|
78
|
+
const filename =
|
|
79
|
+
fileName + ("__" + (variantApiId || "base")) + extension;
|
|
60
80
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
let dataString = data;
|
|
83
|
+
if (extension === ".json") {
|
|
84
|
+
dataString = JSON.stringify(data, null, 2);
|
|
85
|
+
}
|
|
63
86
|
|
|
64
87
|
fs.writeFileSync(filepath, dataString);
|
|
65
88
|
|
|
@@ -89,10 +112,12 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
|
|
|
89
112
|
}
|
|
90
113
|
}
|
|
91
114
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
async function downloadAndSaveVariants(
|
|
116
|
+
projects: Project[],
|
|
117
|
+
format: string | undefined,
|
|
118
|
+
token?: Token,
|
|
119
|
+
options?: PullOptions
|
|
120
|
+
) {
|
|
96
121
|
const meta = options ? options.meta : {};
|
|
97
122
|
|
|
98
123
|
const { data: variants } = await api.get("/variants", {
|
|
@@ -105,7 +130,7 @@ async function downloadAndSaveVariants(projects, format, token, options) {
|
|
|
105
130
|
|
|
106
131
|
const messages = await Promise.all([
|
|
107
132
|
downloadAndSaveVariant(null, projects, format, token),
|
|
108
|
-
...variants.map(({ apiID }) =>
|
|
133
|
+
...variants.map(({ apiID }: { apiID: string }) =>
|
|
109
134
|
downloadAndSaveVariant(apiID, projects, format, token)
|
|
110
135
|
),
|
|
111
136
|
]);
|
|
@@ -113,10 +138,12 @@ async function downloadAndSaveVariants(projects, format, token, options) {
|
|
|
113
138
|
return messages.join("");
|
|
114
139
|
}
|
|
115
140
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
async function downloadAndSaveBase(
|
|
142
|
+
projects: Project[],
|
|
143
|
+
format: string | undefined,
|
|
144
|
+
token?: Token,
|
|
145
|
+
options?: PullOptions
|
|
146
|
+
) {
|
|
120
147
|
const meta = options ? options.meta : {};
|
|
121
148
|
|
|
122
149
|
const params = {
|
|
@@ -126,18 +153,22 @@ async function downloadAndSaveBase(projects, format, token, options) {
|
|
|
126
153
|
params.format = format;
|
|
127
154
|
}
|
|
128
155
|
|
|
129
|
-
if (NON_DEFAULT_FORMATS.includes(format)) {
|
|
156
|
+
if (format && NON_DEFAULT_FORMATS.includes(format)) {
|
|
130
157
|
const savedMessages = await Promise.all(
|
|
131
|
-
projects.map(async ({ id, fileName }) => {
|
|
158
|
+
projects.map(async ({ id, fileName }: Project) => {
|
|
132
159
|
const { data } = await api.get(`/projects/${id}`, {
|
|
133
160
|
params,
|
|
134
161
|
headers: { Authorization: `token ${token}` },
|
|
135
162
|
});
|
|
136
163
|
|
|
137
|
-
const
|
|
164
|
+
const extension = getExtension(format);
|
|
165
|
+
const filename = `${fileName}${extension}`;
|
|
138
166
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
139
167
|
|
|
140
|
-
|
|
168
|
+
let dataString = data;
|
|
169
|
+
if (extension === ".json") {
|
|
170
|
+
dataString = JSON.stringify(data, null, 2);
|
|
171
|
+
}
|
|
141
172
|
|
|
142
173
|
fs.writeFileSync(filepath, dataString);
|
|
143
174
|
|
|
@@ -160,7 +191,7 @@ async function downloadAndSaveBase(projects, format, token, options) {
|
|
|
160
191
|
}
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
function getSavedMessage(file) {
|
|
194
|
+
function getSavedMessage(file: string) {
|
|
164
195
|
return `Successfully saved to ${output.info(file)}\n`;
|
|
165
196
|
}
|
|
166
197
|
|
|
@@ -171,7 +202,7 @@ function cleanOutputFiles() {
|
|
|
171
202
|
|
|
172
203
|
const fileNames = fs.readdirSync(consts.TEXT_DIR);
|
|
173
204
|
fileNames.forEach((fileName) => {
|
|
174
|
-
if (/\.js(on)
|
|
205
|
+
if (/\.js(on)?|\.xml|\.strings$/.test(fileName)) {
|
|
175
206
|
fs.unlinkSync(path.resolve(consts.TEXT_DIR, fileName));
|
|
176
207
|
}
|
|
177
208
|
});
|
|
@@ -181,7 +212,7 @@ function cleanOutputFiles() {
|
|
|
181
212
|
|
|
182
213
|
// compatability with legacy method of specifying project ids
|
|
183
214
|
// that is still used by the default format
|
|
184
|
-
const stringifyProjectId = (projectId) =>
|
|
215
|
+
const stringifyProjectId = (projectId: string) =>
|
|
185
216
|
projectId === "ditto_component_library" ? projectId : `project_${projectId}`;
|
|
186
217
|
|
|
187
218
|
/**
|
|
@@ -195,74 +226,88 @@ const stringifyProjectId = (projectId) =>
|
|
|
195
226
|
* independent of the CLI configuration used to fetch
|
|
196
227
|
* data from Ditto.
|
|
197
228
|
*/
|
|
198
|
-
function generateJsDriver(
|
|
229
|
+
function generateJsDriver(
|
|
230
|
+
projects: Project[],
|
|
231
|
+
variants: boolean,
|
|
232
|
+
format: string | undefined
|
|
233
|
+
) {
|
|
199
234
|
const fileNames = fs
|
|
200
235
|
.readdirSync(consts.TEXT_DIR)
|
|
201
236
|
.filter((fileName) => /\.json$/.test(fileName));
|
|
202
237
|
|
|
203
|
-
const projectIdsByName = projects.reduce(
|
|
204
|
-
(agg, project) =>
|
|
238
|
+
const projectIdsByName: Record<string, string> = projects.reduce(
|
|
239
|
+
(agg, project) => {
|
|
240
|
+
if (project.fileName) {
|
|
241
|
+
return { ...agg, [project.fileName]: project.id };
|
|
242
|
+
}
|
|
243
|
+
return agg;
|
|
244
|
+
},
|
|
205
245
|
{}
|
|
206
246
|
);
|
|
207
247
|
|
|
208
|
-
const data = fileNames.reduce(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
248
|
+
const data = fileNames.reduce(
|
|
249
|
+
(obj: Record<string, Record<string, string>>, fileName) => {
|
|
250
|
+
// filename format: {project-name}__{variant-api-id}.json
|
|
251
|
+
// file format: flat or structured
|
|
252
|
+
if (variants && format) {
|
|
253
|
+
const [projectName, rest] = fileName.split("__");
|
|
254
|
+
const [variantApiId] = rest.split(".");
|
|
255
|
+
|
|
256
|
+
const projectId = projectIdsByName[projectName];
|
|
257
|
+
if (!projectId) {
|
|
258
|
+
throw new Error(`Couldn't find id for ${projectName}`);
|
|
259
|
+
}
|
|
214
260
|
|
|
215
|
-
|
|
216
|
-
if (!projectId) {
|
|
217
|
-
throw new Error(`Couldn't find id for ${projectName}`);
|
|
218
|
-
}
|
|
261
|
+
const projectIdStr = stringifyProjectId(projectId);
|
|
219
262
|
|
|
220
|
-
|
|
263
|
+
if (!obj[projectIdStr]) {
|
|
264
|
+
obj[projectIdStr] = {};
|
|
265
|
+
}
|
|
221
266
|
|
|
222
|
-
|
|
223
|
-
obj[projectIdStr] = {};
|
|
267
|
+
obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
|
|
224
268
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
269
|
+
// filename format: {variant-api-id}.json
|
|
270
|
+
// file format: default
|
|
271
|
+
else if (variants) {
|
|
272
|
+
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
273
|
+
const [variantApiId] = fileName.split(".");
|
|
274
|
+
|
|
275
|
+
Object.keys(file.projects).forEach((projectId) => {
|
|
276
|
+
if (!obj[projectId]) {
|
|
277
|
+
obj[projectId] = {};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const project = file.projects[projectId];
|
|
281
|
+
obj[projectId][variantApiId] = project.frames || project.components;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// filename format: {project-name}.json
|
|
285
|
+
// file format: flat or structured
|
|
286
|
+
else if (format) {
|
|
287
|
+
const [projectName] = fileName.split(".");
|
|
288
|
+
const projectId = projectIdsByName[projectName];
|
|
289
|
+
if (!projectId) {
|
|
290
|
+
throw new Error(`Couldn't find id for ${projectName}`);
|
|
237
291
|
}
|
|
238
292
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
293
|
+
obj[stringifyProjectId(projectId)] = {
|
|
294
|
+
base: `require('./${fileName}')`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// filename format: text.json (single file)
|
|
298
|
+
// file format: default
|
|
299
|
+
else {
|
|
300
|
+
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
301
|
+
Object.keys(file.projects).forEach((projectId) => {
|
|
302
|
+
const project = file.projects[projectId];
|
|
303
|
+
obj[projectId] = { base: project.frames || project.components };
|
|
304
|
+
});
|
|
250
305
|
}
|
|
251
306
|
|
|
252
|
-
obj
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
else {
|
|
257
|
-
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
258
|
-
Object.keys(file.projects).forEach((projectId) => {
|
|
259
|
-
const project = file.projects[projectId];
|
|
260
|
-
obj[projectId] = { base: project.frames || project.components };
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return obj;
|
|
265
|
-
}, {});
|
|
307
|
+
return obj;
|
|
308
|
+
},
|
|
309
|
+
{}
|
|
310
|
+
);
|
|
266
311
|
|
|
267
312
|
let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
|
|
268
313
|
// remove quotes around require statements
|
|
@@ -274,10 +319,11 @@ function generateJsDriver(projects, variants, format) {
|
|
|
274
319
|
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
275
320
|
}
|
|
276
321
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
322
|
+
async function downloadAndSave(
|
|
323
|
+
sourceInformation: SourceInformation,
|
|
324
|
+
token?: Token,
|
|
325
|
+
options?: PullOptions
|
|
326
|
+
) {
|
|
281
327
|
const { validProjects, variants, format, shouldFetchComponentLibrary } =
|
|
282
328
|
sourceInformation;
|
|
283
329
|
|
|
@@ -314,7 +360,7 @@ async function downloadAndSave(sourceInformation, token, options) {
|
|
|
314
360
|
|
|
315
361
|
spinner.stop();
|
|
316
362
|
return console.log(msg);
|
|
317
|
-
} catch (e) {
|
|
363
|
+
} catch (e: any) {
|
|
318
364
|
spinner.stop();
|
|
319
365
|
let error = e.message;
|
|
320
366
|
if (e.response && e.response.status === 404) {
|
|
@@ -351,18 +397,19 @@ async function downloadAndSave(sourceInformation, token, options) {
|
|
|
351
397
|
}
|
|
352
398
|
}
|
|
353
399
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
400
|
+
interface PullOptions {
|
|
401
|
+
meta?: Record<string, string>;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export const pull = (options?: PullOptions) => {
|
|
358
405
|
const meta = options ? options.meta : {};
|
|
359
406
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
360
407
|
const sourceInformation = config.parseSourceInformation();
|
|
361
408
|
|
|
362
409
|
return downloadAndSave(sourceInformation, token, { meta });
|
|
363
|
-
}
|
|
410
|
+
};
|
|
364
411
|
|
|
365
|
-
|
|
412
|
+
export default {
|
|
366
413
|
pull,
|
|
367
414
|
_testing: {
|
|
368
415
|
cleanOutputFiles,
|