@dittowords/cli 1.2.6-beta → 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/README.md +2 -0
- package/lib/config.js +77 -2
- package/lib/init/init.js +19 -11
- package/lib/init/project.js +4 -5
- package/lib/pull.js +41 -92
- 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/README.md
CHANGED
|
@@ -89,6 +89,8 @@ If you run the CLI in a directory that does not contain a `ditto/` folder, the f
|
|
|
89
89
|
|
|
90
90
|
An automatically generated driver file that simplifies the process of passing text data to Ditto JavaScript SDKs. This file has a standardized format that is always the same independent of the CLI configuration used to generate it.
|
|
91
91
|
|
|
92
|
+
**Since this file is designed to be consumed by other internal Ditto libraries, it is not recommended that you depend on it - its format may change between major releases.**
|
|
93
|
+
|
|
92
94
|
```ts
|
|
93
95
|
interface DriverFile {
|
|
94
96
|
[projectId: string]: {
|
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,36 +148,20 @@ 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
|
-
|
|
202
|
-
|
|
203
|
-
fs.
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
if (!exists) {
|
|
207
|
-
await new Promise((resolve) => fs.mkdir(consts.TEXT_DIR, () => resolve()));
|
|
154
|
+
function cleanOutputFiles() {
|
|
155
|
+
if (!fs.existsSync(consts.TEXT_DIR)) {
|
|
156
|
+
fs.mkdirSync(consts.TEXT_DIR);
|
|
208
157
|
}
|
|
209
158
|
|
|
210
|
-
const fileNames =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
resolve(
|
|
214
|
-
}
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
await Promise.all(
|
|
218
|
-
fileNames.map((fileName) => {
|
|
219
|
-
if (/\.js(on)?$/.test(fileName)) {
|
|
220
|
-
return new Promise((resolve) => {
|
|
221
|
-
fs.unlink(path.resolve(consts.TEXT_DIR, fileName), () => resolve());
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return Promise.resolve();
|
|
226
|
-
})
|
|
227
|
-
);
|
|
159
|
+
const fileNames = fs.readdirSync(consts.TEXT_DIR);
|
|
160
|
+
fileNames.forEach((fileName) => {
|
|
161
|
+
if (/\.js(on)?$/.test(fileName)) {
|
|
162
|
+
fs.unlinkSync(path.resolve(consts.TEXT_DIR, fileName));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
228
165
|
|
|
229
166
|
return "Cleaning old output files..\n";
|
|
230
167
|
}
|
|
@@ -251,7 +188,7 @@ function generateJsDriver(projects, variants, format) {
|
|
|
251
188
|
.filter((fileName) => /\.json$/.test(fileName));
|
|
252
189
|
|
|
253
190
|
const projectIdsByName = projects.reduce(
|
|
254
|
-
(agg, project) => ({ ...agg, [project.
|
|
191
|
+
(agg, project) => ({ ...agg, [project.fileName]: project.id }),
|
|
255
192
|
{}
|
|
256
193
|
);
|
|
257
194
|
|
|
@@ -321,28 +258,40 @@ function generateJsDriver(projects, variants, format) {
|
|
|
321
258
|
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
322
259
|
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
|
323
260
|
|
|
324
|
-
return `Generated .js SDK driver at ${output.info(filePath)}
|
|
261
|
+
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
325
262
|
}
|
|
326
263
|
|
|
327
|
-
async function downloadAndSave(
|
|
328
|
-
const {
|
|
329
|
-
|
|
264
|
+
async function downloadAndSave(sourceInformation, token) {
|
|
265
|
+
const { validProjects, variants, format, shouldFetchComponentLibrary } =
|
|
266
|
+
sourceInformation;
|
|
330
267
|
|
|
331
|
-
let msg = `\nFetching the latest text from
|
|
332
|
-
|
|
268
|
+
let msg = `\nFetching the latest text from ${sourcesToText(
|
|
269
|
+
validProjects,
|
|
270
|
+
shouldFetchComponentLibrary
|
|
333
271
|
)}\n`;
|
|
334
272
|
|
|
335
273
|
const spinner = ora(msg);
|
|
336
274
|
spinner.start();
|
|
337
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
|
+
|
|
338
287
|
try {
|
|
339
|
-
msg +=
|
|
288
|
+
msg += cleanOutputFiles();
|
|
340
289
|
|
|
341
290
|
msg += variants
|
|
342
|
-
? await downloadAndSaveVariants(
|
|
343
|
-
: await downloadAndSaveBase(
|
|
291
|
+
? await downloadAndSaveVariants(validProjects, format, token)
|
|
292
|
+
: await downloadAndSaveBase(validProjects, format, token);
|
|
344
293
|
|
|
345
|
-
msg += generateJsDriver(
|
|
294
|
+
msg += generateJsDriver(validProjects, variants, format);
|
|
346
295
|
|
|
347
296
|
msg += `\n${output.success("Done")}!`;
|
|
348
297
|
|
|
@@ -387,15 +336,15 @@ async function downloadAndSave(projectConfig, token) {
|
|
|
387
336
|
|
|
388
337
|
function pull() {
|
|
389
338
|
const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
|
|
390
|
-
const
|
|
391
|
-
|
|
339
|
+
const sourceInformation = config.parseSourceInformation();
|
|
340
|
+
|
|
341
|
+
return downloadAndSave(sourceInformation, token);
|
|
392
342
|
}
|
|
393
343
|
|
|
394
344
|
module.exports = {
|
|
395
345
|
pull,
|
|
396
346
|
_testing: {
|
|
397
347
|
cleanOutputFiles,
|
|
398
|
-
getProjectsWithDedupedNames,
|
|
399
348
|
downloadAndSaveVariant,
|
|
400
349
|
downloadAndSaveVariants,
|
|
401
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;
|