@dittowords/cli 2.7.1 → 3.0.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 +150 -137
- package/bin/add-project.js +5 -7
- package/bin/add-project.js.map +1 -1
- package/bin/api.js +0 -5
- package/bin/api.js.map +1 -1
- package/bin/config.js +38 -11
- package/bin/config.js.map +1 -1
- package/bin/ditto.js +66 -57
- package/bin/ditto.js.map +1 -1
- package/bin/http/fetchVariants.js +26 -0
- package/bin/http/fetchVariants.js.map +1 -0
- package/bin/init/init.js +17 -6
- package/bin/init/init.js.map +1 -1
- package/bin/init/project.js +38 -45
- package/bin/init/project.js.map +1 -1
- package/bin/init/token.js +22 -20
- package/bin/init/token.js.map +1 -1
- package/bin/pull.js +142 -193
- package/bin/pull.js.map +1 -1
- package/bin/remove-project.js +2 -7
- package/bin/remove-project.js.map +1 -1
- package/bin/utils/cleanFileName.js +11 -0
- package/bin/utils/cleanFileName.js.map +1 -0
- package/bin/utils/generateJsDriver.js +56 -0
- package/bin/utils/generateJsDriver.js.map +1 -0
- package/bin/utils/getSelectedProjects.js +3 -18
- package/bin/utils/getSelectedProjects.js.map +1 -1
- package/bin/utils/projectsToText.js +10 -1
- package/bin/utils/projectsToText.js.map +1 -1
- package/bin/utils/promptForProject.js +2 -3
- package/bin/utils/promptForProject.js.map +1 -1
- package/bin/utils/quit.js +10 -0
- package/bin/utils/quit.js.map +1 -0
- package/lib/add-project.ts +6 -9
- package/lib/api.ts +0 -5
- package/lib/config.ts +57 -19
- package/lib/ditto.ts +74 -58
- package/lib/http/fetchVariants.ts +30 -0
- package/lib/init/init.ts +38 -6
- package/lib/init/project.test.ts +3 -3
- package/lib/init/project.ts +47 -58
- package/lib/init/token.ts +17 -16
- package/lib/pull.ts +205 -261
- package/lib/remove-project.ts +2 -8
- package/lib/types.ts +24 -3
- package/lib/utils/cleanFileName.ts +6 -0
- package/lib/utils/generateJsDriver.ts +68 -0
- package/lib/utils/getSelectedProjects.ts +5 -24
- package/lib/utils/projectsToText.ts +11 -1
- package/lib/utils/promptForProject.ts +2 -3
- package/lib/utils/quit.ts +5 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
package/lib/init/token.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { create } from "../api";
|
|
|
8
8
|
import consts from "../consts";
|
|
9
9
|
import output from "../output";
|
|
10
10
|
import config from "../config";
|
|
11
|
+
import { quit } from "../utils/quit";
|
|
11
12
|
|
|
12
13
|
export const needsToken = (configFile?: string, host = consts.API_HOST) => {
|
|
13
14
|
if (config.getTokenFromEnv()) {
|
|
@@ -30,9 +31,9 @@ async function checkToken(token: string): Promise<any> {
|
|
|
30
31
|
const axios = create(token);
|
|
31
32
|
const endpoint = "/token-check";
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.catch((error: any) => {
|
|
34
|
+
let resOrError;
|
|
35
|
+
try {
|
|
36
|
+
resOrError = await axios.get(endpoint).catch((error: any) => {
|
|
36
37
|
if (error.code === "ENOTFOUND") {
|
|
37
38
|
return output.errorText(
|
|
38
39
|
`Can't connect to API: ${output.url(error.hostname)}`
|
|
@@ -44,13 +45,19 @@ async function checkToken(token: string): Promise<any> {
|
|
|
44
45
|
);
|
|
45
46
|
}
|
|
46
47
|
return output.warnText("We're having trouble reaching the Ditto API.");
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
51
|
-
|
|
48
|
+
});
|
|
49
|
+
} catch (e: unknown) {
|
|
50
|
+
output.errorText(e as any);
|
|
51
|
+
output.errorText("Sorry! We're having trouble reaching the Ditto API.");
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
if (resOrError
|
|
54
|
+
if (typeof resOrError === "string") {
|
|
55
|
+
return resOrError;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (resOrError?.status === 200) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
54
61
|
|
|
55
62
|
return output.errorText("This API key isn't valid. Please try another.");
|
|
56
63
|
}
|
|
@@ -75,12 +82,6 @@ async function collectToken(message: string | null) {
|
|
|
75
82
|
return response.token;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
function quit(exitCode = 2) {
|
|
79
|
-
console.log("API key was not saved.");
|
|
80
|
-
process.exitCode = exitCode;
|
|
81
|
-
process.exit();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
85
|
/**
|
|
85
86
|
*
|
|
86
87
|
* @param {string | null} message
|
|
@@ -99,7 +100,7 @@ export const collectAndSaveToken = async (message: string | null = null) => {
|
|
|
99
100
|
config.saveToken(consts.CONFIG_FILE, consts.API_HOST, token);
|
|
100
101
|
return token;
|
|
101
102
|
} catch (error) {
|
|
102
|
-
quit();
|
|
103
|
+
quit("API token was not saved");
|
|
103
104
|
}
|
|
104
105
|
};
|
|
105
106
|
|
package/lib/pull.ts
CHANGED
|
@@ -9,9 +9,48 @@ import consts from "./consts";
|
|
|
9
9
|
import output from "./output";
|
|
10
10
|
import { collectAndSaveToken } from "./init/token";
|
|
11
11
|
import sourcesToText from "./utils/sourcesToText";
|
|
12
|
+
import { generateJsDriver } from "./utils/generateJsDriver";
|
|
13
|
+
import { cleanFileName } from "./utils/cleanFileName";
|
|
12
14
|
import { SourceInformation, Token, Project } from "./types";
|
|
15
|
+
import { fetchVariants } from "./http/fetchVariants";
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
type SupportedFormat = "flat" | "structured" | "android" | "ios-strings";
|
|
18
|
+
|
|
19
|
+
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
20
|
+
"flat",
|
|
21
|
+
"structured",
|
|
22
|
+
"android",
|
|
23
|
+
"ios-strings",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const JSON_FORMATS: SupportedFormat[] = ["flat", "structured"];
|
|
27
|
+
|
|
28
|
+
const FORMAT_EXTENSIONS = {
|
|
29
|
+
flat: ".json",
|
|
30
|
+
structured: ".json",
|
|
31
|
+
android: ".xml",
|
|
32
|
+
"ios-strings": ".strings",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getFormatDataIsValid = {
|
|
36
|
+
flat: (data: string) => data !== "{}",
|
|
37
|
+
structured: (data: string) => data !== "{}",
|
|
38
|
+
android: (data: string) => data.includes("<string"),
|
|
39
|
+
"ios-strings": (data: string) => !!data,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getFormat = (formatFromSource: string | undefined): SupportedFormat => {
|
|
43
|
+
const f = formatFromSource as SupportedFormat | undefined;
|
|
44
|
+
if (f && SUPPORTED_FORMATS.includes(f)) {
|
|
45
|
+
return f;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return "flat";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getFormatExtension = (format: SupportedFormat) => {
|
|
52
|
+
return FORMAT_EXTENSIONS[format];
|
|
53
|
+
};
|
|
15
54
|
|
|
16
55
|
const DEFAULT_FORMAT_KEYS = ["projects", "exported_at"];
|
|
17
56
|
const hasVariantData = (data: any) => {
|
|
@@ -31,16 +70,6 @@ async function askForAnotherToken() {
|
|
|
31
70
|
await collectAndSaveToken(message);
|
|
32
71
|
}
|
|
33
72
|
|
|
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
|
-
|
|
44
73
|
/**
|
|
45
74
|
* For a given variant:
|
|
46
75
|
* - if format is unspecified, fetch data for all projects from `/projects` and
|
|
@@ -51,92 +80,64 @@ function getExtension(format: string) {
|
|
|
51
80
|
async function downloadAndSaveVariant(
|
|
52
81
|
variantApiId: string | null,
|
|
53
82
|
projects: Project[],
|
|
54
|
-
format:
|
|
83
|
+
format: SupportedFormat,
|
|
55
84
|
status: string | undefined,
|
|
85
|
+
richText: boolean | undefined,
|
|
56
86
|
token?: Token
|
|
57
87
|
) {
|
|
58
|
-
const params: Record<string, string | null> = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
headers: { Authorization: `token ${token}` },
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (!hasVariantData(data)) {
|
|
77
|
-
return "";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const extension = getExtension(format);
|
|
81
|
-
|
|
82
|
-
const filename =
|
|
83
|
-
fileName + ("__" + (variantApiId || "base")) + extension;
|
|
84
|
-
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
85
|
-
|
|
86
|
-
let dataString = data;
|
|
87
|
-
if (extension === ".json") {
|
|
88
|
-
dataString = JSON.stringify(data, null, 2);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
fs.writeFileSync(filepath, dataString);
|
|
92
|
-
|
|
93
|
-
return getSavedMessage(filename);
|
|
94
|
-
})
|
|
95
|
-
);
|
|
88
|
+
const params: Record<string, string | null> = { variant: variantApiId };
|
|
89
|
+
if (format) params.format = format;
|
|
90
|
+
if (status) params.status = status;
|
|
91
|
+
if (richText) params.includeRichText = richText.toString();
|
|
92
|
+
|
|
93
|
+
const savedMessages = await Promise.all(
|
|
94
|
+
projects.map(async ({ id, fileName }: Project) => {
|
|
95
|
+
const { data } = await api.get(`/projects/${id}`, {
|
|
96
|
+
params,
|
|
97
|
+
headers: { Authorization: `token ${token}` },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!hasVariantData(data)) {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
const { data } = await api.get("/projects", {
|
|
100
|
-
params: { ...params, projectIds: projects.map(({ id }) => id) },
|
|
101
|
-
headers: { Authorization: `token ${token}` },
|
|
102
|
-
});
|
|
104
|
+
const extension = getFormatExtension(format);
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
const filename = cleanFileName(
|
|
107
|
+
fileName + ("__" + (variantApiId || "base")) + extension
|
|
108
|
+
);
|
|
109
|
+
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
let dataString = data;
|
|
112
|
+
if (extension === ".json") {
|
|
113
|
+
dataString = JSON.stringify(data, null, 2);
|
|
114
|
+
}
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
117
|
+
if (!dataIsValid(dataString)) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
112
120
|
|
|
113
|
-
|
|
121
|
+
fs.writeFileSync(filepath, dataString);
|
|
122
|
+
return getSavedMessage(filename);
|
|
123
|
+
})
|
|
124
|
+
);
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
}
|
|
126
|
+
return savedMessages.join("");
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
async function downloadAndSaveVariants(
|
|
130
|
+
variants: { apiID: string }[],
|
|
120
131
|
projects: Project[],
|
|
121
|
-
format:
|
|
132
|
+
format: SupportedFormat,
|
|
122
133
|
status: string | undefined,
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
richText: boolean | undefined,
|
|
135
|
+
token?: Token
|
|
125
136
|
) {
|
|
126
|
-
const meta = options ? options.meta : {};
|
|
127
|
-
|
|
128
|
-
const { data: variants } = await api.get("/variants", {
|
|
129
|
-
params: {
|
|
130
|
-
...meta,
|
|
131
|
-
projectIds: projects.map(({ id }) => id),
|
|
132
|
-
},
|
|
133
|
-
headers: { Authorization: `token ${token}` },
|
|
134
|
-
});
|
|
135
|
-
|
|
136
137
|
const messages = await Promise.all([
|
|
137
|
-
downloadAndSaveVariant(null, projects, format, status, token),
|
|
138
|
+
downloadAndSaveVariant(null, projects, format, status, richText, token),
|
|
138
139
|
...variants.map(({ apiID }: { apiID: string }) =>
|
|
139
|
-
downloadAndSaveVariant(apiID, projects, format, status, token)
|
|
140
|
+
downloadAndSaveVariant(apiID, projects, format, status, richText, token)
|
|
140
141
|
),
|
|
141
142
|
]);
|
|
142
143
|
|
|
@@ -145,59 +146,44 @@ async function downloadAndSaveVariants(
|
|
|
145
146
|
|
|
146
147
|
async function downloadAndSaveBase(
|
|
147
148
|
projects: Project[],
|
|
148
|
-
format:
|
|
149
|
+
format: SupportedFormat,
|
|
149
150
|
status: string | undefined,
|
|
151
|
+
richText?: boolean | undefined,
|
|
150
152
|
token?: Token,
|
|
151
153
|
options?: PullOptions
|
|
152
154
|
) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const extension = getExtension(format);
|
|
174
|
-
const filename = `${fileName}${extension}`;
|
|
175
|
-
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
176
|
-
|
|
177
|
-
let dataString = data;
|
|
178
|
-
if (extension === ".json") {
|
|
179
|
-
dataString = JSON.stringify(data, null, 2);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
fs.writeFileSync(filepath, dataString);
|
|
183
|
-
|
|
184
|
-
return getSavedMessage(filename);
|
|
185
|
-
})
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
return savedMessages.join("");
|
|
189
|
-
} else {
|
|
190
|
-
const { data } = await api.get(`/projects`, {
|
|
191
|
-
params: { ...params, projectIds: projects.map(({ id }) => id) },
|
|
192
|
-
headers: { Authorization: `token ${token}` },
|
|
193
|
-
});
|
|
155
|
+
const params = { ...options?.meta };
|
|
156
|
+
if (format) params.format = format;
|
|
157
|
+
if (status) params.status = status;
|
|
158
|
+
if (richText) params.includeRichText = richText.toString();
|
|
159
|
+
|
|
160
|
+
const savedMessages = await Promise.all(
|
|
161
|
+
projects.map(async ({ id, fileName }: Project) => {
|
|
162
|
+
const { data } = await api.get(`/projects/${id}`, {
|
|
163
|
+
params,
|
|
164
|
+
headers: { Authorization: `token ${token}` },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const extension = getFormatExtension(format);
|
|
168
|
+
const filename = cleanFileName(`${fileName}__base${extension}`);
|
|
169
|
+
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
170
|
+
|
|
171
|
+
let dataString = data;
|
|
172
|
+
if (extension === ".json") {
|
|
173
|
+
dataString = JSON.stringify(data, null, 2);
|
|
174
|
+
}
|
|
194
175
|
|
|
195
|
-
|
|
176
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
177
|
+
if (!dataIsValid(dataString)) {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
196
180
|
|
|
197
|
-
|
|
181
|
+
fs.writeFileSync(filepath, dataString);
|
|
182
|
+
return getSavedMessage(filename);
|
|
183
|
+
})
|
|
184
|
+
);
|
|
198
185
|
|
|
199
|
-
|
|
200
|
-
}
|
|
186
|
+
return savedMessages.join("");
|
|
201
187
|
}
|
|
202
188
|
|
|
203
189
|
function getSavedMessage(file: string) {
|
|
@@ -219,166 +205,124 @@ function cleanOutputFiles() {
|
|
|
219
205
|
return "Cleaning old output files..\n";
|
|
220
206
|
}
|
|
221
207
|
|
|
222
|
-
// compatability with legacy method of specifying project ids
|
|
223
|
-
// that is still used by the default format
|
|
224
|
-
const stringifyProjectId = (projectId: string) =>
|
|
225
|
-
projectId === "ditto_component_library" ? projectId : `project_${projectId}`;
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Generates an index.js file that can be consumed
|
|
229
|
-
* by an SDK - this is a big DX improvement because
|
|
230
|
-
* it provides a single entry point to get all data
|
|
231
|
-
* (including variants) instead of having to import
|
|
232
|
-
* each generated file individually.
|
|
233
|
-
*
|
|
234
|
-
* The generated file will have a unified format
|
|
235
|
-
* independent of the CLI configuration used to fetch
|
|
236
|
-
* data from Ditto.
|
|
237
|
-
*/
|
|
238
|
-
function generateJsDriver(
|
|
239
|
-
projects: Project[],
|
|
240
|
-
variants: boolean,
|
|
241
|
-
format: string | undefined
|
|
242
|
-
) {
|
|
243
|
-
const fileNames = fs
|
|
244
|
-
.readdirSync(consts.TEXT_DIR)
|
|
245
|
-
.filter((fileName) => /\.json$/.test(fileName));
|
|
246
|
-
|
|
247
|
-
const projectIdsByName: Record<string, string> = projects.reduce(
|
|
248
|
-
(agg, project) => {
|
|
249
|
-
if (project.fileName) {
|
|
250
|
-
return { ...agg, [project.fileName]: project.id };
|
|
251
|
-
}
|
|
252
|
-
return agg;
|
|
253
|
-
},
|
|
254
|
-
{}
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
const data = fileNames.reduce(
|
|
258
|
-
(obj: Record<string, Record<string, string>>, fileName) => {
|
|
259
|
-
// filename format: {project-name}__{variant-api-id}.json
|
|
260
|
-
// file format: flat or structured
|
|
261
|
-
if (variants && format) {
|
|
262
|
-
const [projectName, rest] = fileName.split("__");
|
|
263
|
-
const [variantApiId] = rest.split(".");
|
|
264
|
-
|
|
265
|
-
const projectId = projectIdsByName[projectName];
|
|
266
|
-
if (!projectId) {
|
|
267
|
-
throw new Error(`Couldn't find id for ${projectName}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const projectIdStr = stringifyProjectId(projectId);
|
|
271
|
-
|
|
272
|
-
if (!obj[projectIdStr]) {
|
|
273
|
-
obj[projectIdStr] = {};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
|
|
277
|
-
}
|
|
278
|
-
// filename format: {variant-api-id}.json
|
|
279
|
-
// file format: default
|
|
280
|
-
else if (variants) {
|
|
281
|
-
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
282
|
-
const [variantApiId] = fileName.split(".");
|
|
283
|
-
|
|
284
|
-
Object.keys(file.projects).forEach((projectId) => {
|
|
285
|
-
if (!obj[projectId]) {
|
|
286
|
-
obj[projectId] = {};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const project = file.projects[projectId];
|
|
290
|
-
obj[projectId][variantApiId] = project.frames || project.components;
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
// filename format: {project-name}.json
|
|
294
|
-
// file format: flat or structured
|
|
295
|
-
else if (format) {
|
|
296
|
-
const [projectName] = fileName.split(".");
|
|
297
|
-
const projectId = projectIdsByName[projectName];
|
|
298
|
-
if (!projectId) {
|
|
299
|
-
throw new Error(`Couldn't find id for ${projectName}`);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
obj[stringifyProjectId(projectId)] = {
|
|
303
|
-
base: `require('./${fileName}')`,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
// filename format: text.json (single file)
|
|
307
|
-
// file format: default
|
|
308
|
-
else {
|
|
309
|
-
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
310
|
-
Object.keys(file.projects).forEach((projectId) => {
|
|
311
|
-
const project = file.projects[projectId];
|
|
312
|
-
obj[projectId] = { base: project.frames || project.components };
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return obj;
|
|
317
|
-
},
|
|
318
|
-
{}
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
|
|
322
|
-
// remove quotes around require statements
|
|
323
|
-
.replace(/"require\((.*)\)"/g, "require($1)");
|
|
324
|
-
|
|
325
|
-
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
326
|
-
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
|
327
|
-
|
|
328
|
-
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
208
|
async function downloadAndSave(
|
|
332
|
-
|
|
209
|
+
source: SourceInformation,
|
|
333
210
|
token?: Token,
|
|
334
211
|
options?: PullOptions
|
|
335
212
|
) {
|
|
336
213
|
const {
|
|
337
214
|
validProjects,
|
|
338
|
-
|
|
339
|
-
format,
|
|
215
|
+
format: formatFromSource,
|
|
340
216
|
shouldFetchComponentLibrary,
|
|
341
217
|
status,
|
|
342
|
-
|
|
218
|
+
richText,
|
|
219
|
+
componentFolders,
|
|
220
|
+
} = source;
|
|
343
221
|
|
|
344
|
-
|
|
345
|
-
validProjects,
|
|
346
|
-
shouldFetchComponentLibrary
|
|
347
|
-
)}\n`;
|
|
222
|
+
const format = getFormat(formatFromSource);
|
|
348
223
|
|
|
224
|
+
let msg = "";
|
|
349
225
|
const spinner = ora(msg);
|
|
350
226
|
spinner.start();
|
|
351
227
|
|
|
352
|
-
|
|
353
|
-
// point down the road we stop allowing the component
|
|
354
|
-
// library to be returned from the /projects endpoint
|
|
355
|
-
if (shouldFetchComponentLibrary) {
|
|
356
|
-
validProjects.push({
|
|
357
|
-
id: "ditto_component_library",
|
|
358
|
-
name: "Ditto Component Library",
|
|
359
|
-
fileName: "ditto-component-library",
|
|
360
|
-
});
|
|
361
|
-
}
|
|
228
|
+
const variants = await fetchVariants(source);
|
|
362
229
|
|
|
363
230
|
try {
|
|
364
231
|
msg += cleanOutputFiles();
|
|
232
|
+
msg += `\nFetching the latest text from ${sourcesToText(
|
|
233
|
+
validProjects,
|
|
234
|
+
shouldFetchComponentLibrary
|
|
235
|
+
)}\n`;
|
|
365
236
|
|
|
366
237
|
const meta = options ? options.meta : {};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
238
|
+
|
|
239
|
+
if (shouldFetchComponentLibrary) {
|
|
240
|
+
// Always include a variant with an apiID of undefined to ensure that we
|
|
241
|
+
// fetch the base text for the component library.
|
|
242
|
+
const componentVariants = [{ apiID: undefined }, ...(variants || [])];
|
|
243
|
+
|
|
244
|
+
const params = new URLSearchParams();
|
|
245
|
+
if (options?.meta)
|
|
246
|
+
Object.entries(options.meta).forEach(([k, v]) => params.append(k, v));
|
|
247
|
+
if (format) params.append("format", format);
|
|
248
|
+
if (status) params.append("status", status);
|
|
249
|
+
if (richText) params.append("includeRichText", richText.toString());
|
|
250
|
+
if (componentFolders) {
|
|
251
|
+
componentFolders.forEach(({ id }) => params.append("folder_id[]", id));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const messages = await Promise.all(
|
|
255
|
+
componentVariants.map(async ({ apiID: variantApiId }) => {
|
|
256
|
+
const p = new URLSearchParams(params);
|
|
257
|
+
if (variantApiId) p.append("variant", variantApiId);
|
|
258
|
+
|
|
259
|
+
const { data } = await api.get(`/components`, { params: p });
|
|
260
|
+
|
|
261
|
+
const nameExt = getFormatExtension(format);
|
|
262
|
+
const nameBase = "ditto-component-library";
|
|
263
|
+
const namePostfix = `__${variantApiId || "base"}`;
|
|
264
|
+
|
|
265
|
+
const fileName = cleanFileName(`${nameBase}${namePostfix}${nameExt}`);
|
|
266
|
+
const filePath = path.join(consts.TEXT_DIR, fileName);
|
|
267
|
+
|
|
268
|
+
let dataString = data;
|
|
269
|
+
if (nameExt === ".json") {
|
|
270
|
+
dataString = JSON.stringify(data, null, 2);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
274
|
+
if (!dataIsValid(dataString)) {
|
|
275
|
+
return "";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await new Promise((r) => fs.writeFile(filePath, dataString, r));
|
|
279
|
+
return getSavedMessage(fileName);
|
|
370
280
|
})
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
msg += messages.join("");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (validProjects.length) {
|
|
287
|
+
msg += variants
|
|
288
|
+
? await downloadAndSaveVariants(
|
|
289
|
+
variants,
|
|
290
|
+
validProjects,
|
|
291
|
+
format,
|
|
292
|
+
status,
|
|
293
|
+
richText,
|
|
294
|
+
token
|
|
295
|
+
)
|
|
296
|
+
: await downloadAndSaveBase(
|
|
297
|
+
validProjects,
|
|
298
|
+
format,
|
|
299
|
+
status,
|
|
300
|
+
richText,
|
|
301
|
+
token,
|
|
302
|
+
{
|
|
303
|
+
meta,
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
}
|
|
374
307
|
|
|
375
|
-
|
|
308
|
+
const sources = [...validProjects];
|
|
309
|
+
if (shouldFetchComponentLibrary) {
|
|
310
|
+
sources.push({
|
|
311
|
+
id: "ditto_component_library",
|
|
312
|
+
name: "Ditto Component Library",
|
|
313
|
+
fileName: "ditto-component-library",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (JSON_FORMATS.includes(format)) msg += generateJsDriver(sources);
|
|
376
318
|
|
|
377
319
|
msg += `\n${output.success("Done")}!`;
|
|
378
320
|
|
|
379
321
|
spinner.stop();
|
|
380
322
|
return console.log(msg);
|
|
381
323
|
} catch (e: any) {
|
|
324
|
+
console.error(e);
|
|
325
|
+
|
|
382
326
|
spinner.stop();
|
|
383
327
|
let error = e.message;
|
|
384
328
|
if (e.response && e.response.status === 404) {
|
|
@@ -415,7 +359,7 @@ async function downloadAndSave(
|
|
|
415
359
|
}
|
|
416
360
|
}
|
|
417
361
|
|
|
418
|
-
interface PullOptions {
|
|
362
|
+
export interface PullOptions {
|
|
419
363
|
meta?: Record<string, string>;
|
|
420
364
|
}
|
|
421
365
|
|
package/lib/remove-project.ts
CHANGED
|
@@ -19,15 +19,9 @@ async function removeProject() {
|
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const allProjects = isUsingComponents
|
|
23
|
-
? [{ id: "components", name: "Ditto Component Library" }, ...projects]
|
|
24
|
-
: projects;
|
|
25
|
-
|
|
26
22
|
const projectToRemove = await promptForProject({
|
|
27
|
-
projects
|
|
28
|
-
message:
|
|
29
|
-
? "Select a project or library to remove"
|
|
30
|
-
: "Select a project to remove",
|
|
23
|
+
projects,
|
|
24
|
+
message: "Select a project to remove",
|
|
31
25
|
});
|
|
32
26
|
if (!projectToRemove) return;
|
|
33
27
|
|
package/lib/types.ts
CHANGED
|
@@ -5,12 +5,31 @@ export interface Project {
|
|
|
5
5
|
fileName?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export type Source = Project;
|
|
9
|
+
|
|
10
|
+
interface ComponentFolder {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export interface ConfigYAML {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
sources?: {
|
|
17
|
+
components?: {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
folders?: ComponentFolder[];
|
|
20
|
+
};
|
|
21
|
+
projects?: Project[];
|
|
22
|
+
};
|
|
23
|
+
format?: "flat" | "structured" | "android-xml" | "ios-strings";
|
|
12
24
|
status?: string;
|
|
13
25
|
variants?: boolean;
|
|
26
|
+
richText?: boolean;
|
|
27
|
+
|
|
28
|
+
// these are legacy fields - if they exist, we should output
|
|
29
|
+
// a deprecation error, and suggest that they nest them under
|
|
30
|
+
// a top-level `sources` property
|
|
31
|
+
components?: boolean;
|
|
32
|
+
projects?: Project[];
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
export interface SourceInformation {
|
|
@@ -20,6 +39,8 @@ export interface SourceInformation {
|
|
|
20
39
|
variants: boolean;
|
|
21
40
|
format: string | undefined;
|
|
22
41
|
status: string | undefined;
|
|
42
|
+
richText: boolean | undefined;
|
|
43
|
+
componentFolders: ComponentFolder[] | null;
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
export type Token = string | undefined;
|