@dittowords/cli 2.0.0 → 2.1.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/lib/config.js +77 -2
- package/lib/init/init.js +19 -11
- package/lib/init/project.js +4 -5
- package/lib/pull.js +31 -66
- package/lib/utils/getSelectedProjects.js +1 -1
- package/lib/utils/projectsToText.js +1 -1
- package/lib/utils/sourcesToText.js +24 -0
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const url = require("url");
|
|
4
|
-
|
|
5
4
|
const yaml = require("js-yaml");
|
|
6
5
|
|
|
6
|
+
const consts = require("./consts");
|
|
7
|
+
|
|
7
8
|
function createFileIfMissing(filename) {
|
|
8
9
|
const dir = path.dirname(filename);
|
|
9
10
|
|
|
@@ -14,7 +15,13 @@ function createFileIfMissing(filename) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Read data from a file
|
|
20
|
+
* @param {*} file defaults to `PROJECT_CONFIG_FILE` defined in `constants.js`
|
|
21
|
+
* @param {*} defaultData defaults to `{}`
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
function readData(file = consts.PROJECT_CONFIG_FILE, defaultData = {}) {
|
|
18
25
|
createFileIfMissing(file);
|
|
19
26
|
const fileContents = fs.readFileSync(file, "utf8");
|
|
20
27
|
return yaml.safeLoad(fileContents) || defaultData;
|
|
@@ -72,6 +79,73 @@ function save(file, key, value) {
|
|
|
72
79
|
writeData(file, data);
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
const IS_DUPLICATE = /-(\d+$)/;
|
|
83
|
+
function dedupeProjectName(projectNames, projectName) {
|
|
84
|
+
let dedupedName = projectName;
|
|
85
|
+
|
|
86
|
+
if (projectNames[dedupedName]) {
|
|
87
|
+
while (projectNames[dedupedName]) {
|
|
88
|
+
const [_, numberStr] = dedupedName.match(IS_DUPLICATE) || [];
|
|
89
|
+
if (numberStr && !isNaN(parseInt(numberStr))) {
|
|
90
|
+
dedupedName = `${dedupedName.replace(IS_DUPLICATE, "")}-${
|
|
91
|
+
parseInt(numberStr) + 1
|
|
92
|
+
}`;
|
|
93
|
+
} else {
|
|
94
|
+
dedupedName = `${dedupedName}-1`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return dedupedName;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Reads from the config file, filters out
|
|
104
|
+
* invalid projects, dedupes those remaining, and returns:
|
|
105
|
+
* - whether or not the data required to `pull` is present
|
|
106
|
+
* - whether or not the component library should be fetched
|
|
107
|
+
* - an array of valid, deduped projects
|
|
108
|
+
* - the `variants` and `format` config options
|
|
109
|
+
*/
|
|
110
|
+
function parseSourceInformation() {
|
|
111
|
+
const { projects, components, variants, format } = readData();
|
|
112
|
+
|
|
113
|
+
const projectNames = {};
|
|
114
|
+
const validProjects = [];
|
|
115
|
+
|
|
116
|
+
let componentLibraryInProjects = false;
|
|
117
|
+
|
|
118
|
+
(projects || []).forEach((project) => {
|
|
119
|
+
const isValid = project.id && project.name;
|
|
120
|
+
if (!isValid) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (project.id === "ditto_component_library") {
|
|
125
|
+
componentLibraryInProjects = true;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
project.fileName = dedupeProjectName(projectNames, project.name);
|
|
130
|
+
projectNames[project.fileName] = true;
|
|
131
|
+
|
|
132
|
+
validProjects.push(project);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const shouldFetchComponentLibrary =
|
|
136
|
+
!!components || componentLibraryInProjects;
|
|
137
|
+
|
|
138
|
+
const hasSourceData = validProjects.length || shouldFetchComponentLibrary;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
hasSourceData,
|
|
142
|
+
validProjects,
|
|
143
|
+
shouldFetchComponentLibrary,
|
|
144
|
+
variants,
|
|
145
|
+
format,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
75
149
|
module.exports = {
|
|
76
150
|
createFileIfMissing,
|
|
77
151
|
readData,
|
|
@@ -81,4 +155,5 @@ module.exports = {
|
|
|
81
155
|
deleteToken,
|
|
82
156
|
getToken,
|
|
83
157
|
save,
|
|
158
|
+
parseSourceInformation,
|
|
84
159
|
};
|
package/lib/init/init.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// expected to be run once per project.
|
|
3
3
|
const boxen = require("boxen");
|
|
4
4
|
const chalk = require("chalk");
|
|
5
|
-
const getSelectedProjects = require("../utils/getSelectedProjects");
|
|
6
5
|
const projectsToText = require("../utils/projectsToText");
|
|
7
6
|
|
|
8
|
-
const {
|
|
7
|
+
const { needsSource, collectAndSaveProject } = require("./project");
|
|
9
8
|
const { needsToken, collectAndSaveToken } = require("./token");
|
|
10
9
|
|
|
11
|
-
const
|
|
10
|
+
const config = require("../config");
|
|
11
|
+
const output = require("../output");
|
|
12
|
+
const sourcesToText = require("../utils/sourcesToText");
|
|
13
|
+
|
|
14
|
+
const needsInit = () => needsToken() || needsSource();
|
|
12
15
|
|
|
13
16
|
function welcome() {
|
|
14
17
|
const msg = chalk.white(`${chalk.bold(
|
|
@@ -22,19 +25,24 @@ We're glad to have you here.`);
|
|
|
22
25
|
|
|
23
26
|
async function init() {
|
|
24
27
|
welcome();
|
|
28
|
+
|
|
25
29
|
if (needsToken()) {
|
|
26
30
|
await collectAndSaveToken();
|
|
27
31
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)}\n`
|
|
34
|
-
);
|
|
35
|
-
} else {
|
|
32
|
+
|
|
33
|
+
const { hasSourceData, validProjects, shouldFetchComponentLibrary } =
|
|
34
|
+
config.parseSourceInformation();
|
|
35
|
+
|
|
36
|
+
if (!hasSourceData) {
|
|
36
37
|
await collectAndSaveProject(true);
|
|
38
|
+
return;
|
|
37
39
|
}
|
|
40
|
+
|
|
41
|
+
const message =
|
|
42
|
+
"You're currently set up to sync text from " +
|
|
43
|
+
sourcesToText(validProjects, shouldFetchComponentLibrary);
|
|
44
|
+
|
|
45
|
+
console.log(message);
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
module.exports = { needsInit, init };
|
package/lib/init/project.js
CHANGED
|
@@ -20,9 +20,8 @@ function saveProject(file, name, id) {
|
|
|
20
20
|
config.writeData(file, { projects });
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
return !(projects && projects.length);
|
|
23
|
+
function needsSource() {
|
|
24
|
+
return !config.parseSourceInformation().hasSourceData;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
async function askForAnotherToken() {
|
|
@@ -60,7 +59,7 @@ async function collectProject(token, initialize) {
|
|
|
60
59
|
const path = process.cwd();
|
|
61
60
|
if (initialize) {
|
|
62
61
|
console.log(
|
|
63
|
-
`Looks like are no Ditto projects selected for your current directory: ${output.info(
|
|
62
|
+
`Looks like there are no Ditto projects selected for your current directory: ${output.info(
|
|
64
63
|
path
|
|
65
64
|
)}.`
|
|
66
65
|
);
|
|
@@ -113,4 +112,4 @@ async function collectAndSaveProject(initialize = false) {
|
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
module.exports = {
|
|
115
|
+
module.exports = { needsSource, collectAndSaveProject };
|
package/lib/pull.js
CHANGED
|
@@ -9,6 +9,7 @@ const consts = require("./consts");
|
|
|
9
9
|
const output = require("./output");
|
|
10
10
|
const { collectAndSaveToken } = require("./init/token");
|
|
11
11
|
const projectsToText = require("./utils/projectsToText");
|
|
12
|
+
const sourcesToText = require("./utils/sourcesToText");
|
|
12
13
|
|
|
13
14
|
const NON_DEFAULT_FORMATS = ["flat", "structured"];
|
|
14
15
|
|
|
@@ -30,54 +31,6 @@ async function askForAnotherToken() {
|
|
|
30
31
|
await collectAndSaveToken(message);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function cleanProjectName(projectName) {
|
|
34
|
-
return (
|
|
35
|
-
projectName
|
|
36
|
-
.replace(/\s/g, "-")
|
|
37
|
-
// replace double underscore since this is what we use
|
|
38
|
-
// to separate project names and API IDs
|
|
39
|
-
.replace(/__/g, "_")
|
|
40
|
-
.toLowerCase()
|
|
41
|
-
.trim()
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Return the passed array of projects with `project.name` modified
|
|
47
|
-
* to be `${project.name}-${duplicate_number}` for each project
|
|
48
|
-
* that has the same name as another project in the original array.
|
|
49
|
-
*/
|
|
50
|
-
const IS_DUPLICATE = /-(\d+$)/;
|
|
51
|
-
function getProjectsWithDedupedNames(projects) {
|
|
52
|
-
const projectsWithDedupedNames = [];
|
|
53
|
-
const projectNames = {};
|
|
54
|
-
|
|
55
|
-
projects.forEach(({ id, name }) => {
|
|
56
|
-
let dedupedName = name;
|
|
57
|
-
|
|
58
|
-
if (projectNames[dedupedName]) {
|
|
59
|
-
while (projectNames[dedupedName]) {
|
|
60
|
-
const [_, numberStr] = dedupedName.match(IS_DUPLICATE) || [];
|
|
61
|
-
if (numberStr && !isNaN(parseInt(numberStr))) {
|
|
62
|
-
dedupedName = `${dedupedName.replace(IS_DUPLICATE, "")}-${
|
|
63
|
-
parseInt(numberStr) + 1
|
|
64
|
-
}`;
|
|
65
|
-
} else {
|
|
66
|
-
dedupedName = `${dedupedName}-1`;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
projectNames[dedupedName] = true;
|
|
72
|
-
projectsWithDedupedNames.push({
|
|
73
|
-
id,
|
|
74
|
-
name: cleanProjectName(dedupedName),
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return projectsWithDedupedNames;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
34
|
/**
|
|
82
35
|
* For a given variant:
|
|
83
36
|
* - if format is unspecified, fetch data for all projects from `/projects` and
|
|
@@ -93,7 +46,7 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
|
|
|
93
46
|
|
|
94
47
|
if (NON_DEFAULT_FORMATS.includes(format)) {
|
|
95
48
|
const savedMessages = await Promise.all(
|
|
96
|
-
projects.map(async ({ id,
|
|
49
|
+
projects.map(async ({ id, fileName }) => {
|
|
97
50
|
const { data } = await api.get(`/projects/${id}`, {
|
|
98
51
|
params,
|
|
99
52
|
headers: { Authorization: `token ${token}` },
|
|
@@ -103,7 +56,7 @@ async function downloadAndSaveVariant(variantApiId, projects, format, token) {
|
|
|
103
56
|
return "";
|
|
104
57
|
}
|
|
105
58
|
|
|
106
|
-
const filename =
|
|
59
|
+
const filename = fileName + ("__" + (variantApiId || "base")) + ".json";
|
|
107
60
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
108
61
|
|
|
109
62
|
const dataString = JSON.stringify(data, null, 2);
|
|
@@ -162,13 +115,13 @@ async function downloadAndSaveBase(projects, format, token) {
|
|
|
162
115
|
|
|
163
116
|
if (NON_DEFAULT_FORMATS.includes(format)) {
|
|
164
117
|
const savedMessages = await Promise.all(
|
|
165
|
-
projects.map(async ({ id,
|
|
118
|
+
projects.map(async ({ id, fileName }) => {
|
|
166
119
|
const { data } = await api.get(`/projects/${id}`, {
|
|
167
120
|
params,
|
|
168
121
|
headers: { Authorization: `token ${token}` },
|
|
169
122
|
});
|
|
170
123
|
|
|
171
|
-
const filename = `${
|
|
124
|
+
const filename = `${fileName}.json`;
|
|
172
125
|
const filepath = path.join(consts.TEXT_DIR, filename);
|
|
173
126
|
|
|
174
127
|
const dataString = JSON.stringify(data, null, 2);
|
|
@@ -195,7 +148,7 @@ async function downloadAndSaveBase(projects, format, token) {
|
|
|
195
148
|
}
|
|
196
149
|
|
|
197
150
|
function getSavedMessage(file) {
|
|
198
|
-
return `Successfully saved to ${output.info(file)}
|
|
151
|
+
return `Successfully saved to ${output.info(file)}\n`;
|
|
199
152
|
}
|
|
200
153
|
|
|
201
154
|
function cleanOutputFiles() {
|
|
@@ -235,7 +188,7 @@ function generateJsDriver(projects, variants, format) {
|
|
|
235
188
|
.filter((fileName) => /\.json$/.test(fileName));
|
|
236
189
|
|
|
237
190
|
const projectIdsByName = projects.reduce(
|
|
238
|
-
(agg, project) => ({ ...agg, [project.
|
|
191
|
+
(agg, project) => ({ ...agg, [project.fileName]: project.id }),
|
|
239
192
|
{}
|
|
240
193
|
);
|
|
241
194
|
|
|
@@ -305,28 +258,40 @@ function generateJsDriver(projects, variants, format) {
|
|
|
305
258
|
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
306
259
|
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
|
307
260
|
|
|
308
|
-
return `Generated .js SDK driver at ${output.info(filePath)}
|
|
261
|
+
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
309
262
|
}
|
|
310
263
|
|
|
311
|
-
async function downloadAndSave(
|
|
312
|
-
const {
|
|
313
|
-
|
|
264
|
+
async function downloadAndSave(sourceInformation, token) {
|
|
265
|
+
const { validProjects, variants, format, shouldFetchComponentLibrary } =
|
|
266
|
+
sourceInformation;
|
|
314
267
|
|
|
315
|
-
let msg = `\nFetching the latest text from
|
|
316
|
-
|
|
268
|
+
let msg = `\nFetching the latest text from ${sourcesToText(
|
|
269
|
+
validProjects,
|
|
270
|
+
shouldFetchComponentLibrary
|
|
317
271
|
)}\n`;
|
|
318
272
|
|
|
319
273
|
const spinner = ora(msg);
|
|
320
274
|
spinner.start();
|
|
321
275
|
|
|
276
|
+
// We'll need to move away from this solution if at some
|
|
277
|
+
// point down the road we stop allowing the component
|
|
278
|
+
// library to be returned from the /projects endpoint
|
|
279
|
+
if (shouldFetchComponentLibrary) {
|
|
280
|
+
validProjects.push({
|
|
281
|
+
id: "ditto_component_library",
|
|
282
|
+
name: "Ditto Component Library",
|
|
283
|
+
fileName: "ditto-component-library",
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
322
287
|
try {
|
|
323
288
|
msg += cleanOutputFiles();
|
|
324
289
|
|
|
325
290
|
msg += variants
|
|
326
|
-
? await downloadAndSaveVariants(
|
|
327
|
-
: await downloadAndSaveBase(
|
|
291
|
+
? await downloadAndSaveVariants(validProjects, format, token)
|
|
292
|
+
: await downloadAndSaveBase(validProjects, format, token);
|
|
328
293
|
|
|
329
|
-
msg += generateJsDriver(
|
|
294
|
+
msg += generateJsDriver(validProjects, variants, format);
|
|
330
295
|
|
|
331
296
|
msg += `\n${output.success("Done")}!`;
|
|
332
297
|
|
|
@@ -371,15 +336,15 @@ async function downloadAndSave(projectConfig, token) {
|
|
|
371
336
|
|
|
372
337
|
function pull() {
|
|
373
338
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
374
|
-
const
|
|
375
|
-
|
|
339
|
+
const sourceInformation = config.parseSourceInformation();
|
|
340
|
+
|
|
341
|
+
return downloadAndSave(sourceInformation, token);
|
|
376
342
|
}
|
|
377
343
|
|
|
378
344
|
module.exports = {
|
|
379
345
|
pull,
|
|
380
346
|
_testing: {
|
|
381
347
|
cleanOutputFiles,
|
|
382
|
-
getProjectsWithDedupedNames,
|
|
383
348
|
downloadAndSaveVariant,
|
|
384
349
|
downloadAndSaveVariants,
|
|
385
350
|
downloadAndSaveBase,
|
|
@@ -29,7 +29,7 @@ function getSelectedProjects(configFile = PROJECT_CONFIG_FILE) {
|
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
return
|
|
32
|
+
return contentjson.projects.filter(({ name, id }) => name && id);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
module.exports = getSelectedProjects;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const output = require("../output");
|
|
2
|
+
const projectsToText = require("./projectsToText");
|
|
3
|
+
|
|
4
|
+
function sourcesToText(projects, componentLibrary) {
|
|
5
|
+
let message = "";
|
|
6
|
+
|
|
7
|
+
if (componentLibrary) {
|
|
8
|
+
message += `the ${output.info("Ditto Component Library")}`;
|
|
9
|
+
|
|
10
|
+
if ((projects || []).length) {
|
|
11
|
+
message += " and ";
|
|
12
|
+
} else {
|
|
13
|
+
message += "..";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if ((projects || []).length) {
|
|
18
|
+
message += ` the following projects: ${projectsToText(projects)}\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return message;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = sourcesToText;
|