@dittowords/cli 2.8.0 → 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 -141
- package/bin/add-project.js +5 -7
- package/bin/add-project.js.map +1 -1
- package/bin/config.js +37 -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 +136 -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/config.ts +56 -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 +199 -279
- package/lib/remove-project.ts +2 -8
- package/lib/types.ts +22 -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,93 +80,60 @@ 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,
|
|
56
85
|
richText: boolean | undefined,
|
|
57
86
|
token?: Token
|
|
58
87
|
) {
|
|
59
|
-
const params: Record<string, string | null> = {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
projects.map(async ({ id, fileName }: Project) => {
|
|
75
|
-
const { data } = await api.get(`/projects/${id}`, {
|
|
76
|
-
params,
|
|
77
|
-
headers: { Authorization: `token ${token}` },
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!hasVariantData(data)) {
|
|
81
|
-
return "";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const extension = getExtension(format);
|
|
85
|
-
|
|
86
|
-
const filename =
|
|
87
|
-
fileName + ("__" + (variantApiId || "base")) + extension;
|
|
88
|
-
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
89
|
-
|
|
90
|
-
let dataString = data;
|
|
91
|
-
if (extension === ".json") {
|
|
92
|
-
dataString = JSON.stringify(data, null, 2);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fs.writeFileSync(filepath, dataString);
|
|
96
|
-
|
|
97
|
-
return getSavedMessage(filename);
|
|
98
|
-
})
|
|
99
|
-
);
|
|
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
|
+
}
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
} else {
|
|
103
|
-
const { data } = await api.get("/projects", {
|
|
104
|
-
params: { ...params, projectIds: projects.map(({ id }) => id) },
|
|
105
|
-
headers: { Authorization: `token ${token}` },
|
|
106
|
-
});
|
|
104
|
+
const extension = getFormatExtension(format);
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
const filename = cleanFileName(
|
|
107
|
+
fileName + ("__" + (variantApiId || "base")) + extension
|
|
108
|
+
);
|
|
109
|
+
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
let dataString = data;
|
|
112
|
+
if (extension === ".json") {
|
|
113
|
+
dataString = JSON.stringify(data, null, 2);
|
|
114
|
+
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
117
|
+
if (!dataIsValid(dataString)) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
fs.writeFileSync(filepath, dataString);
|
|
122
|
+
return getSavedMessage(filename);
|
|
123
|
+
})
|
|
124
|
+
);
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
}
|
|
126
|
+
return savedMessages.join("");
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
async function downloadAndSaveVariants(
|
|
130
|
+
variants: { apiID: string }[],
|
|
124
131
|
projects: Project[],
|
|
125
|
-
format:
|
|
132
|
+
format: SupportedFormat,
|
|
126
133
|
status: string | undefined,
|
|
127
134
|
richText: boolean | undefined,
|
|
128
|
-
token?: Token
|
|
129
|
-
options?: PullOptions
|
|
135
|
+
token?: Token
|
|
130
136
|
) {
|
|
131
|
-
const meta = options ? options.meta : {};
|
|
132
|
-
|
|
133
|
-
const { data: variants } = await api.get("/variants", {
|
|
134
|
-
params: {
|
|
135
|
-
...meta,
|
|
136
|
-
projectIds: projects.map(({ id }) => id),
|
|
137
|
-
},
|
|
138
|
-
headers: { Authorization: `token ${token}` },
|
|
139
|
-
});
|
|
140
|
-
|
|
141
137
|
const messages = await Promise.all([
|
|
142
138
|
downloadAndSaveVariant(null, projects, format, status, richText, token),
|
|
143
139
|
...variants.map(({ apiID }: { apiID: string }) =>
|
|
@@ -150,63 +146,44 @@ async function downloadAndSaveVariants(
|
|
|
150
146
|
|
|
151
147
|
async function downloadAndSaveBase(
|
|
152
148
|
projects: Project[],
|
|
153
|
-
format:
|
|
149
|
+
format: SupportedFormat,
|
|
154
150
|
status: string | undefined,
|
|
155
|
-
richText
|
|
151
|
+
richText?: boolean | undefined,
|
|
156
152
|
token?: Token,
|
|
157
153
|
options?: PullOptions
|
|
158
154
|
) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
headers: { Authorization: `token ${token}` },
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const extension = getExtension(format);
|
|
183
|
-
const filename = `${fileName}${extension}`;
|
|
184
|
-
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
185
|
-
|
|
186
|
-
let dataString = data;
|
|
187
|
-
if (extension === ".json") {
|
|
188
|
-
dataString = JSON.stringify(data, null, 2);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
fs.writeFileSync(filepath, dataString);
|
|
192
|
-
|
|
193
|
-
return getSavedMessage(filename);
|
|
194
|
-
})
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
return savedMessages.join("");
|
|
198
|
-
} else {
|
|
199
|
-
const { data } = await api.get(`/projects`, {
|
|
200
|
-
params: { ...params, projectIds: projects.map(({ id }) => id) },
|
|
201
|
-
headers: { Authorization: `token ${token}` },
|
|
202
|
-
});
|
|
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
|
+
}
|
|
203
175
|
|
|
204
|
-
|
|
176
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
177
|
+
if (!dataIsValid(dataString)) {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
205
180
|
|
|
206
|
-
|
|
181
|
+
fs.writeFileSync(filepath, dataString);
|
|
182
|
+
return getSavedMessage(filename);
|
|
183
|
+
})
|
|
184
|
+
);
|
|
207
185
|
|
|
208
|
-
|
|
209
|
-
}
|
|
186
|
+
return savedMessages.join("");
|
|
210
187
|
}
|
|
211
188
|
|
|
212
189
|
function getSavedMessage(file: string) {
|
|
@@ -228,181 +205,124 @@ function cleanOutputFiles() {
|
|
|
228
205
|
return "Cleaning old output files..\n";
|
|
229
206
|
}
|
|
230
207
|
|
|
231
|
-
// compatability with legacy method of specifying project ids
|
|
232
|
-
// that is still used by the default format
|
|
233
|
-
const stringifyProjectId = (projectId: string) =>
|
|
234
|
-
projectId === "ditto_component_library" ? projectId : `project_${projectId}`;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Generates an index.js file that can be consumed
|
|
238
|
-
* by an SDK - this is a big DX improvement because
|
|
239
|
-
* it provides a single entry point to get all data
|
|
240
|
-
* (including variants) instead of having to import
|
|
241
|
-
* each generated file individually.
|
|
242
|
-
*
|
|
243
|
-
* The generated file will have a unified format
|
|
244
|
-
* independent of the CLI configuration used to fetch
|
|
245
|
-
* data from Ditto.
|
|
246
|
-
*/
|
|
247
|
-
function generateJsDriver(
|
|
248
|
-
projects: Project[],
|
|
249
|
-
variants: boolean,
|
|
250
|
-
format: string | undefined
|
|
251
|
-
) {
|
|
252
|
-
const fileNames = fs
|
|
253
|
-
.readdirSync(consts.TEXT_DIR)
|
|
254
|
-
.filter((fileName) => /\.json$/.test(fileName));
|
|
255
|
-
|
|
256
|
-
const projectIdsByName: Record<string, string> = projects.reduce(
|
|
257
|
-
(agg, project) => {
|
|
258
|
-
if (project.fileName) {
|
|
259
|
-
return { ...agg, [project.fileName]: project.id };
|
|
260
|
-
}
|
|
261
|
-
return agg;
|
|
262
|
-
},
|
|
263
|
-
{}
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
const data = fileNames.reduce(
|
|
267
|
-
(obj: Record<string, Record<string, string>>, fileName) => {
|
|
268
|
-
// filename format: {project-name}__{variant-api-id}.json
|
|
269
|
-
// file format: flat or structured
|
|
270
|
-
if (variants && format) {
|
|
271
|
-
const [projectName, rest] = fileName.split("__");
|
|
272
|
-
const [variantApiId] = rest.split(".");
|
|
273
|
-
|
|
274
|
-
const projectId = projectIdsByName[projectName];
|
|
275
|
-
if (!projectId) {
|
|
276
|
-
throw new Error(`Couldn't find id for ${projectName}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const projectIdStr = stringifyProjectId(projectId);
|
|
280
|
-
|
|
281
|
-
if (!obj[projectIdStr]) {
|
|
282
|
-
obj[projectIdStr] = {};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
|
|
286
|
-
}
|
|
287
|
-
// filename format: {variant-api-id}.json
|
|
288
|
-
// file format: default
|
|
289
|
-
else if (variants) {
|
|
290
|
-
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
291
|
-
const [variantApiId] = fileName.split(".");
|
|
292
|
-
|
|
293
|
-
Object.keys(file.projects).forEach((projectId) => {
|
|
294
|
-
if (!obj[projectId]) {
|
|
295
|
-
obj[projectId] = {};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const project = file.projects[projectId];
|
|
299
|
-
obj[projectId][variantApiId] = project.frames || project.components;
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
// filename format: {project-name}.json
|
|
303
|
-
// file format: flat or structured
|
|
304
|
-
else if (format) {
|
|
305
|
-
const [projectName] = fileName.split(".");
|
|
306
|
-
const projectId = projectIdsByName[projectName];
|
|
307
|
-
if (!projectId) {
|
|
308
|
-
throw new Error(`Couldn't find id for ${projectName}`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
obj[stringifyProjectId(projectId)] = {
|
|
312
|
-
base: `require('./${fileName}')`,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
// filename format: text.json (single file)
|
|
316
|
-
// file format: default
|
|
317
|
-
else {
|
|
318
|
-
const file = require(path.resolve(consts.TEXT_DIR, `./${fileName}`));
|
|
319
|
-
Object.keys(file.projects).forEach((projectId) => {
|
|
320
|
-
const project = file.projects[projectId];
|
|
321
|
-
obj[projectId] = { base: project.frames || project.components };
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return obj;
|
|
326
|
-
},
|
|
327
|
-
{}
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
|
|
331
|
-
// remove quotes around require statements
|
|
332
|
-
.replace(/"require\((.*)\)"/g, "require($1)");
|
|
333
|
-
|
|
334
|
-
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
335
|
-
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
|
336
|
-
|
|
337
|
-
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
208
|
async function downloadAndSave(
|
|
341
|
-
|
|
209
|
+
source: SourceInformation,
|
|
342
210
|
token?: Token,
|
|
343
211
|
options?: PullOptions
|
|
344
212
|
) {
|
|
345
213
|
const {
|
|
346
214
|
validProjects,
|
|
347
|
-
|
|
348
|
-
format,
|
|
215
|
+
format: formatFromSource,
|
|
349
216
|
shouldFetchComponentLibrary,
|
|
350
217
|
status,
|
|
351
218
|
richText,
|
|
352
|
-
|
|
219
|
+
componentFolders,
|
|
220
|
+
} = source;
|
|
353
221
|
|
|
354
|
-
|
|
355
|
-
validProjects,
|
|
356
|
-
shouldFetchComponentLibrary
|
|
357
|
-
)}\n`;
|
|
222
|
+
const format = getFormat(formatFromSource);
|
|
358
223
|
|
|
224
|
+
let msg = "";
|
|
359
225
|
const spinner = ora(msg);
|
|
360
226
|
spinner.start();
|
|
361
227
|
|
|
362
|
-
|
|
363
|
-
// point down the road we stop allowing the component
|
|
364
|
-
// library to be returned from the /projects endpoint
|
|
365
|
-
if (shouldFetchComponentLibrary) {
|
|
366
|
-
validProjects.push({
|
|
367
|
-
id: "ditto_component_library",
|
|
368
|
-
name: "Ditto Component Library",
|
|
369
|
-
fileName: "ditto-component-library",
|
|
370
|
-
});
|
|
371
|
-
}
|
|
228
|
+
const variants = await fetchVariants(source);
|
|
372
229
|
|
|
373
230
|
try {
|
|
374
231
|
msg += cleanOutputFiles();
|
|
232
|
+
msg += `\nFetching the latest text from ${sourcesToText(
|
|
233
|
+
validProjects,
|
|
234
|
+
shouldFetchComponentLibrary
|
|
235
|
+
)}\n`;
|
|
375
236
|
|
|
376
237
|
const meta = options ? options.meta : {};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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);
|
|
386
271
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
status,
|
|
392
|
-
richText,
|
|
393
|
-
token,
|
|
394
|
-
{
|
|
395
|
-
meta,
|
|
272
|
+
|
|
273
|
+
const dataIsValid = getFormatDataIsValid[format];
|
|
274
|
+
if (!dataIsValid(dataString)) {
|
|
275
|
+
return "";
|
|
396
276
|
}
|
|
397
|
-
);
|
|
398
277
|
|
|
399
|
-
|
|
278
|
+
await new Promise((r) => fs.writeFile(filePath, dataString, r));
|
|
279
|
+
return getSavedMessage(fileName);
|
|
280
|
+
})
|
|
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
|
+
}
|
|
307
|
+
|
|
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);
|
|
400
318
|
|
|
401
319
|
msg += `\n${output.success("Done")}!`;
|
|
402
320
|
|
|
403
321
|
spinner.stop();
|
|
404
322
|
return console.log(msg);
|
|
405
323
|
} catch (e: any) {
|
|
324
|
+
console.error(e);
|
|
325
|
+
|
|
406
326
|
spinner.stop();
|
|
407
327
|
let error = e.message;
|
|
408
328
|
if (e.response && e.response.status === 404) {
|
|
@@ -439,7 +359,7 @@ async function downloadAndSave(
|
|
|
439
359
|
}
|
|
440
360
|
}
|
|
441
361
|
|
|
442
|
-
interface PullOptions {
|
|
362
|
+
export interface PullOptions {
|
|
443
363
|
meta?: Record<string, string>;
|
|
444
364
|
}
|
|
445
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
|
|