@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 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
- function readData(file, defaultData = {}) {
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 { needsProjects, collectAndSaveProject } = require("./project");
7
+ const { needsSource, collectAndSaveProject } = require("./project");
9
8
  const { needsToken, collectAndSaveToken } = require("./token");
10
9
 
11
- const needsInit = () => needsToken() || needsProjects();
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
- const selectedProjects = getSelectedProjects();
29
- if (selectedProjects.length) {
30
- console.log(
31
- `You're currently set up to sync text from the following projects: ${projectsToText(
32
- selectedProjects
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 };
@@ -20,9 +20,8 @@ function saveProject(file, name, id) {
20
20
  config.writeData(file, { projects });
21
21
  }
22
22
 
23
- function needsProjects(configFile) {
24
- const projects = getSelectedProjects(configFile);
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 = { needsProjects, collectAndSaveProject };
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, name }) => {
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 = name + ("__" + (variantApiId || "base")) + ".json";
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, name }) => {
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 = `${name}.json`;
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)}.\n`;
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.name]: project.id }),
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(projectConfig, token) {
312
- const { projects, variants, format } = projectConfig;
313
- const projectsDeduped = getProjectsWithDedupedNames(projects);
264
+ async function downloadAndSave(sourceInformation, token) {
265
+ const { validProjects, variants, format, shouldFetchComponentLibrary } =
266
+ sourceInformation;
314
267
 
315
- let msg = `\nFetching the latest text from your selected projects: ${projectsToText(
316
- projects
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(projectsDeduped, format, token)
327
- : await downloadAndSaveBase(projectsDeduped, format, token);
291
+ ? await downloadAndSaveVariants(validProjects, format, token)
292
+ : await downloadAndSaveBase(validProjects, format, token);
328
293
 
329
- msg += generateJsDriver(projectsDeduped, variants, format);
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 pConfig = config.readData(consts.PROJECT_CONFIG_FILE);
375
- return downloadAndSave(pConfig, token);
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 contentJson.projects.filter(({ name, id }) => name && id);
32
+ return contentjson.projects.filter(({ name, id }) => name && id);
33
33
  }
34
34
 
35
35
  module.exports = getSelectedProjects;
@@ -2,7 +2,7 @@ const output = require("../output");
2
2
 
3
3
  function projectsToText(projects) {
4
4
  return (
5
- projects.reduce(
5
+ (projects || []).reduce(
6
6
  (outputString, { name, id }) =>
7
7
  outputString +
8
8
  ("\n" +
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dittowords/cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Command Line Interface for Ditto (dittowords.com).",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {