@dittowords/cli 2.5.1 → 2.5.2

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.
Files changed (71) hide show
  1. package/README.md +14 -7
  2. package/babel.config.js +2 -8
  3. package/bin/add-project.js +38 -0
  4. package/bin/add-project.js.map +1 -0
  5. package/bin/api.js +20 -0
  6. package/bin/api.js.map +1 -0
  7. package/bin/config.js +174 -0
  8. package/bin/config.js.map +1 -0
  9. package/bin/consts.js +21 -0
  10. package/bin/consts.js.map +1 -0
  11. package/bin/ditto.js +71 -74
  12. package/bin/ditto.js.map +1 -0
  13. package/bin/init/init.js +39 -0
  14. package/bin/init/init.js.map +1 -0
  15. package/bin/init/project.js +115 -0
  16. package/bin/init/project.js.map +1 -0
  17. package/bin/init/token.js +89 -0
  18. package/bin/init/token.js.map +1 -0
  19. package/bin/output.js +34 -0
  20. package/bin/output.js.map +1 -0
  21. package/bin/pull.js +288 -0
  22. package/bin/pull.js.map +1 -0
  23. package/bin/remove-project.js +40 -0
  24. package/bin/remove-project.js.map +1 -0
  25. package/bin/types.js +3 -0
  26. package/bin/types.js.map +1 -0
  27. package/bin/utils/getSelectedProjects.js +76 -0
  28. package/bin/utils/getSelectedProjects.js.map +1 -0
  29. package/bin/utils/processMetaOption.js +15 -0
  30. package/bin/utils/processMetaOption.js.map +1 -0
  31. package/bin/utils/projectsToText.js +16 -0
  32. package/bin/utils/projectsToText.js.map +1 -0
  33. package/bin/utils/promptForProject.js +44 -0
  34. package/bin/utils/promptForProject.js.map +1 -0
  35. package/bin/utils/sourcesToText.js +25 -0
  36. package/bin/utils/sourcesToText.js.map +1 -0
  37. package/jest.config.js +1 -1
  38. package/lib/{add-project.js → add-project.ts} +8 -8
  39. package/lib/api.ts +15 -0
  40. package/lib/{config.test.js → config.test.ts} +14 -25
  41. package/lib/config.ts +214 -0
  42. package/lib/consts.ts +20 -0
  43. package/lib/ditto.ts +97 -0
  44. package/lib/init/{init.js → init.ts} +11 -12
  45. package/lib/init/project.test.ts +49 -0
  46. package/lib/init/{project.js → project.ts} +31 -29
  47. package/lib/init/{token.test.js → token.test.ts} +3 -3
  48. package/lib/init/{token.js → token.ts} +23 -19
  49. package/lib/output.ts +21 -0
  50. package/lib/pull.test.ts +142 -0
  51. package/lib/{pull.js → pull.ts} +119 -92
  52. package/lib/{remove-project.js → remove-project.ts} +11 -15
  53. package/lib/types.ts +23 -0
  54. package/lib/utils/getSelectedProjects.ts +55 -0
  55. package/lib/utils/processMetaOption.test.ts +18 -0
  56. package/lib/utils/processMetaOption.ts +16 -0
  57. package/lib/utils/{projectsToText.js → projectsToText.ts} +5 -4
  58. package/lib/utils/{promptForProject.js → promptForProject.ts} +18 -9
  59. package/lib/utils/{sourcesToText.js → sourcesToText.ts} +6 -5
  60. package/package.json +20 -15
  61. package/testing/fixtures/project-config-pull.yml +0 -0
  62. package/tsconfig.json +12 -0
  63. package/lib/api.js +0 -18
  64. package/lib/config.js +0 -169
  65. package/lib/consts.js +0 -14
  66. package/lib/init/project.test.js +0 -99
  67. package/lib/output.js +0 -21
  68. package/lib/pull.test.js +0 -173
  69. package/lib/utils/getSelectedProjects.js +0 -44
  70. package/lib/utils/processMetaOption.js +0 -16
  71. package/lib/utils/processMetaOption.test.js +0 -16
@@ -1,17 +1,16 @@
1
1
  // Related to initializing a user/environment to ditto.
2
2
  // expected to be run once per project.
3
- const boxen = require("boxen");
4
- const chalk = require("chalk");
5
- const projectsToText = require("../utils/projectsToText");
3
+ import boxen from "boxen";
4
+ import chalk from "chalk";
5
+ import projectsToText from "../utils/projectsToText";
6
6
 
7
- const { needsSource, collectAndSaveProject } = require("./project");
8
- const { needsToken, collectAndSaveToken } = require("./token");
7
+ import { needsSource, collectAndSaveProject } from "./project";
8
+ import { needsToken, collectAndSaveToken } from "./token";
9
9
 
10
- const config = require("../config");
11
- const output = require("../output");
12
- const sourcesToText = require("../utils/sourcesToText");
10
+ import config from "../config";
11
+ import sourcesToText from "../utils/sourcesToText";
13
12
 
14
- const needsInit = () => needsToken() || needsSource();
13
+ export const needsInit = () => needsToken() || needsSource();
15
14
 
16
15
  function welcome() {
17
16
  const msg = chalk.white(`${chalk.bold(
@@ -23,7 +22,7 @@ We're glad to have you here.`);
23
22
  console.log(boxen(msg, { padding: 1 }));
24
23
  }
25
24
 
26
- async function init() {
25
+ export const init = async () => {
27
26
  welcome();
28
27
 
29
28
  if (needsToken()) {
@@ -43,6 +42,6 @@ async function init() {
43
42
  sourcesToText(validProjects, shouldFetchComponentLibrary);
44
43
 
45
44
  console.log(message);
46
- }
45
+ };
47
46
 
48
- module.exports = { needsInit, init };
47
+ export default { needsInit, init };
@@ -0,0 +1,49 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const yaml = require("js-yaml");
5
+ const { createFileIfMissing } = require("../config");
6
+
7
+ import { _testing } from "./project";
8
+
9
+ const fakeProjectDir = path.join(__dirname, "../../testing/tmp");
10
+ const badYaml = path.join(__dirname, "../../testing/fixtures/bad-yaml.yml");
11
+ const configEmptyProjects = path.join(
12
+ __dirname,
13
+ "../../testing/fixtures/bad-yaml.yml"
14
+ );
15
+ const configMissingName = path.join(
16
+ __dirname,
17
+ "../../testing/fixtures/project-config-no-name.yml"
18
+ );
19
+ const configMissingId = path.join(
20
+ __dirname,
21
+ "../../testing/fixtures/project-config-no-id.yml"
22
+ );
23
+ const configLegit = path.join(
24
+ __dirname,
25
+ "../../testing/fixtures/project-config-working.yml"
26
+ );
27
+
28
+ describe("saveProject", () => {
29
+ const configFile = path.join(fakeProjectDir, "ditto/config.yml");
30
+ const projectName = "My Amazing Project";
31
+ const projectId = "5f284259ce1d451b2eb2e23c";
32
+
33
+ beforeEach(() => {
34
+ if (!fs.existsSync(fakeProjectDir)) fs.mkdirSync(fakeProjectDir);
35
+ _testing.saveProject(configFile, projectName, projectId);
36
+ });
37
+
38
+ afterEach(() => {
39
+ fs.rmdirSync(fakeProjectDir, { recursive: true });
40
+ });
41
+
42
+ it("creates a config file with config data", () => {
43
+ const fileContents = fs.readFileSync(configFile, "utf8");
44
+ const data = yaml.load(fileContents);
45
+ expect(data.projects).toBeDefined();
46
+ expect(data.projects[0].name).toEqual(projectName);
47
+ expect(data.projects[0].id).toEqual(projectId);
48
+ });
49
+ });
@@ -1,15 +1,17 @@
1
- const ora = require("ora");
2
-
3
- const api = require("../api").default;
4
- const config = require("../config");
5
- const consts = require("../consts");
6
- const output = require("../output");
7
- const { collectAndSaveToken } = require("../init/token");
8
- const {
1
+ import ora from "ora";
2
+
3
+ import api from "../api";
4
+ import config from "../config";
5
+ import consts from "../consts";
6
+ import output from "../output";
7
+ import { collectAndSaveToken } from "./token";
8
+ import {
9
9
  getSelectedProjects,
10
10
  getIsUsingComponents,
11
- } = require("../utils/getSelectedProjects");
12
- const promptForProject = require("../utils/promptForProject");
11
+ } from "../utils/getSelectedProjects";
12
+ import promptForProject from "../utils/promptForProject";
13
+ import { AxiosResponse } from "axios";
14
+ import { Project, Token } from "../types";
13
15
 
14
16
  function quit(exitCode = 2) {
15
17
  console.log("\nExiting Ditto CLI...\n");
@@ -17,23 +19,23 @@ function quit(exitCode = 2) {
17
19
  process.exit();
18
20
  }
19
21
 
20
- function saveProject(file, name, id) {
21
- // old functionality included "components" in the `projects`
22
+ function saveProject(file: string, name: string, id: string) {
23
+ // old functionality included "ditto_component_library" in the `projects`
22
24
  // array, but we want to always treat the component library as a separate
23
25
  // entity and use the new notation of a top-level `components` key
24
26
  if (id === "components") {
25
- config.writeData(file, { components: true });
27
+ config.writeProjectConfigData(file, { components: true });
26
28
  return;
27
29
  }
28
30
 
29
31
  const projects = [...getSelectedProjects(), { name, id }];
30
32
 
31
- config.writeData(file, { projects });
33
+ config.writeProjectConfigData(file, { projects });
32
34
  }
33
35
 
34
- function needsSource() {
36
+ export const needsSource = () => {
35
37
  return !config.parseSourceInformation().hasSourceData;
36
- }
38
+ };
37
39
 
38
40
  async function askForAnotherToken() {
39
41
  config.deleteToken(consts.CONFIG_FILE, consts.API_HOST);
@@ -43,17 +45,16 @@ async function askForAnotherToken() {
43
45
  }
44
46
 
45
47
  async function listProjects(
46
- token,
47
- projectsAlreadySelected,
48
- componentsSelected
48
+ token: Token,
49
+ projectsAlreadySelected: Project[],
50
+ componentsSelected: boolean
49
51
  ) {
50
52
  const spinner = ora("Fetching projects in your workspace...");
51
53
  spinner.start();
52
54
 
53
- let projects = [];
54
-
55
+ let response: AxiosResponse<{ id: string; name: string }[]>;
55
56
  try {
56
- projects = await api.get("/project-names", {
57
+ response = await api.get("/project-names", {
57
58
  headers: {
58
59
  Authorization: `token ${token}`,
59
60
  },
@@ -64,8 +65,7 @@ async function listProjects(
64
65
  }
65
66
 
66
67
  spinner.stop();
67
-
68
- return projects.data.filter(({ id }) => {
68
+ return response.data.filter(({ id }: Project) => {
69
69
  if (id === "ditto_component_library") {
70
70
  return !componentsSelected;
71
71
  } else {
@@ -74,7 +74,7 @@ async function listProjects(
74
74
  });
75
75
  }
76
76
 
77
- async function collectProject(token, initialize) {
77
+ async function collectProject(token: Token, initialize: boolean) {
78
78
  const path = process.cwd();
79
79
  if (initialize) {
80
80
  console.log(
@@ -114,7 +114,7 @@ async function collectProject(token, initialize) {
114
114
  });
115
115
  }
116
116
 
117
- async function collectAndSaveProject(initialize = false) {
117
+ export const collectAndSaveProject = async (initialize = false) => {
118
118
  try {
119
119
  const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
120
120
  const project = await collectProject(token, initialize);
@@ -134,7 +134,7 @@ async function collectAndSaveProject(initialize = false) {
134
134
  );
135
135
 
136
136
  saveProject(consts.PROJECT_CONFIG_FILE, project.name, project.id);
137
- } catch (e) {
137
+ } catch (e: any) {
138
138
  console.log(e);
139
139
  if (e.response && e.response.status === 404) {
140
140
  await askForAnotherToken();
@@ -143,6 +143,8 @@ async function collectAndSaveProject(initialize = false) {
143
143
  quit();
144
144
  }
145
145
  }
146
- }
146
+ };
147
+
148
+ export const _testing = { saveProject, needsSource };
147
149
 
148
- module.exports = { needsSource, collectAndSaveProject };
150
+ export default { needsSource, collectAndSaveProject };
@@ -1,7 +1,7 @@
1
- const tempy = require("tempy");
1
+ import tempy from "tempy";
2
2
 
3
- const config = require("../config");
4
- const { needsToken } = require("./token");
3
+ import config from "../config";
4
+ import { needsToken } from "./token";
5
5
 
6
6
  describe("needsToken()", () => {
7
7
  it("is true if there is no config file", () => {
@@ -1,38 +1,38 @@
1
- const fs = require("fs");
1
+ import fs from "fs";
2
2
 
3
- const chalk = require("chalk");
3
+ import chalk from "chalk";
4
4
 
5
- const { prompt } = require("enquirer");
5
+ import { prompt } from "enquirer";
6
6
 
7
- const api = require("../api");
8
- const consts = require("../consts");
9
- const output = require("../output");
10
- const config = require("../config");
7
+ import { create } from "../api";
8
+ import consts from "../consts";
9
+ import output from "../output";
10
+ import config from "../config";
11
11
 
12
- function needsToken(configFile, host = consts.API_HOST) {
12
+ export const needsToken = (configFile?: string, host = consts.API_HOST) => {
13
13
  if (config.getTokenFromEnv()) {
14
14
  return false;
15
15
  }
16
16
 
17
17
  const file = configFile || consts.CONFIG_FILE;
18
18
  if (!fs.existsSync(file)) return true;
19
- const configData = config.readData(file);
19
+ const configData = config.readGlobalConfigData(file);
20
20
  if (
21
21
  !configData[config.justTheHost(host)] ||
22
22
  configData[config.justTheHost(host)][0].token === ""
23
23
  )
24
24
  return true;
25
25
  return false;
26
- }
26
+ };
27
27
 
28
28
  // Returns true if valid, otherwise an error message.
29
- async function checkToken(token) {
30
- const axios = api.create(token);
29
+ async function checkToken(token: string): Promise<any> {
30
+ const axios = create(token);
31
31
  const endpoint = "/token-check";
32
32
 
33
33
  const resOrError = await axios
34
34
  .get(endpoint)
35
- .catch((error) => {
35
+ .catch((error: any) => {
36
36
  if (error.code === "ENOTFOUND") {
37
37
  return output.errorText(
38
38
  `Can't connect to API: ${output.url(error.hostname)}`
@@ -48,7 +48,6 @@ async function checkToken(token) {
48
48
  .catch(() =>
49
49
  output.errorText("Sorry! We're having trouble reaching the Ditto API.")
50
50
  );
51
-
52
51
  if (typeof resOrError === "string") return resOrError;
53
52
 
54
53
  if (resOrError.status === 200) return true;
@@ -56,7 +55,7 @@ async function checkToken(token) {
56
55
  return output.errorText("This API key isn't valid. Please try another.");
57
56
  }
58
57
 
59
- async function collectToken(message) {
58
+ async function collectToken(message: string | null) {
60
59
  const blue = output.info;
61
60
  const apiUrl = output.url("https://app.dittowords.com/account/user");
62
61
  const breadcrumbs = `${blue("User")}`;
@@ -67,7 +66,7 @@ async function collectToken(message) {
67
66
  )}".`;
68
67
  console.log(tokenDescription);
69
68
 
70
- const response = await prompt({
69
+ const response = await prompt<{ token: string }>({
71
70
  type: "input",
72
71
  name: "token",
73
72
  message: "What is your API key?",
@@ -82,7 +81,12 @@ function quit(exitCode = 2) {
82
81
  process.exit();
83
82
  }
84
83
 
85
- async function collectAndSaveToken(message = null) {
84
+ /**
85
+ *
86
+ * @param {string | null} message
87
+ * @returns
88
+ */
89
+ export const collectAndSaveToken = async (message: string | null = null) => {
86
90
  try {
87
91
  const token = await collectToken(message);
88
92
  console.log(
@@ -97,6 +101,6 @@ async function collectAndSaveToken(message = null) {
97
101
  } catch (error) {
98
102
  quit();
99
103
  }
100
- }
104
+ };
101
105
 
102
- module.exports = { needsToken, collectAndSaveToken };
106
+ export default { needsToken, collectAndSaveToken };
package/lib/output.ts ADDED
@@ -0,0 +1,21 @@
1
+ import chalk from "chalk";
2
+
3
+ export const errorText = (msg: string) => chalk.magenta(msg);
4
+ export const warnText = (msg: string) => chalk.yellow(msg);
5
+ export const info = (msg: string) => chalk.blueBright(msg);
6
+ export const success = (msg: string) => chalk.green(msg);
7
+ export const url = (msg: string) => chalk.blueBright.underline(msg);
8
+ export const subtle = (msg: string) => chalk.grey(msg);
9
+ export const write = (msg: string) => chalk.white(msg);
10
+ export const nl = () => console.log("\n");
11
+
12
+ export default {
13
+ errorText,
14
+ warnText,
15
+ info,
16
+ success,
17
+ url,
18
+ subtle,
19
+ write,
20
+ nl,
21
+ };
@@ -0,0 +1,142 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import api from "./api";
4
+ import config from "./config";
5
+
6
+ const testProjects = [
7
+ {
8
+ id: "1",
9
+ name: "Project 1",
10
+ fileName: "Project 1",
11
+ },
12
+ { id: "2", name: "Project 2", fileName: "Project 2" },
13
+ ];
14
+
15
+ jest.mock("./api");
16
+ api.get = jest.fn().mockResolvedValue({ data: [] });
17
+
18
+ jest.mock("./consts", () => ({
19
+ TEXT_DIR: ".testing",
20
+ API_HOST: "https://api.dittowords.com",
21
+ CONFIG_FILE: ".testing/ditto",
22
+ PROJECT_CONFIG_FILE: ".testing/config.yml",
23
+ TEXT_FILE: ".testing/text.json",
24
+ }));
25
+
26
+ import consts from "./consts";
27
+ import allPull from "./pull";
28
+
29
+ const {
30
+ _testing: { cleanOutputFiles, downloadAndSaveVariant, downloadAndSaveBase },
31
+ } = allPull;
32
+ const variant = "english";
33
+
34
+ const cleanOutputDir = () => {
35
+ if (fs.existsSync(consts.TEXT_DIR))
36
+ fs.rmSync(consts.TEXT_DIR, { recursive: true, force: true });
37
+
38
+ fs.mkdirSync(consts.TEXT_DIR);
39
+ };
40
+
41
+ afterAll(() => {
42
+ fs.rmSync(consts.TEXT_DIR, { force: true, recursive: true });
43
+ });
44
+
45
+ describe("cleanOutputFiles", () => {
46
+ it("removes .js and .json files", () => {
47
+ cleanOutputDir();
48
+
49
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.json"), "test");
50
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.js"), "test");
51
+ // this file shouldn't be deleted
52
+ fs.writeFileSync(path.resolve(consts.TEXT_DIR, "test.txt"), "test");
53
+
54
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(3);
55
+
56
+ cleanOutputFiles();
57
+
58
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
59
+ });
60
+ });
61
+
62
+ // describe("downloadAndSaveVariant", () => {
63
+ // beforeAll(() => {
64
+ // if (!fs.existsSync(consts.TEXT_DIR)) {
65
+ // fs.mkdirSync(consts.TEXT_DIR);
66
+ // }
67
+ // });
68
+
69
+ // it("writes a single file for default format", async () => {
70
+ // cleanOutputDir();
71
+
72
+ // const output = await downloadAndSaveVariant(variant, testProjects, "");
73
+ // expect(/saved to.*english\.json/.test(output)).toEqual(true);
74
+ // expect(output.match(/saved to/g)?.length).toEqual(1);
75
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
76
+ // });
77
+
78
+ // it("writes multiple files for flat format", async () => {
79
+ // cleanOutputDir();
80
+
81
+ // const output = await downloadAndSaveVariant(variant, testProjects, "flat");
82
+
83
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
84
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
85
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
86
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
87
+ // });
88
+
89
+ // it("writes multiple files for structured format", async () => {
90
+ // cleanOutputDir();
91
+
92
+ // const output = await downloadAndSaveVariant(
93
+ // variant,
94
+ // testProjects,
95
+ // "structured"
96
+ // );
97
+
98
+ // expect(/saved to.*Project 1__english\.json/.test(output)).toEqual(true);
99
+ // expect(/saved to.*Project 2__english\.json/.test(output)).toEqual(true);
100
+ // expect(output.match(/saved to/g)?.length).toEqual(2);
101
+ // expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
102
+ // });
103
+ // });
104
+
105
+ describe("downloadAndSaveBase", () => {
106
+ beforeAll(() => {
107
+ if (!fs.existsSync(consts.TEXT_DIR)) {
108
+ fs.mkdirSync(consts.TEXT_DIR);
109
+ }
110
+ });
111
+
112
+ it("writes to text.json for default format", async () => {
113
+ cleanOutputDir();
114
+
115
+ const output = await downloadAndSaveBase(testProjects, "");
116
+
117
+ expect(/saved to.*text\.json/.test(output)).toEqual(true);
118
+ expect(output.match(/saved to/g)?.length).toEqual(1);
119
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(1);
120
+ });
121
+
122
+ it("writes multiple files for flat format", async () => {
123
+ cleanOutputDir();
124
+
125
+ const output = await downloadAndSaveBase(testProjects, "flat");
126
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
127
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
128
+ expect(output.match(/saved to/g)?.length).toEqual(2);
129
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
130
+ });
131
+
132
+ it("writes multiple files for structured format", async () => {
133
+ cleanOutputDir();
134
+
135
+ const output = await downloadAndSaveBase(testProjects, "structured");
136
+
137
+ expect(/saved to.*Project 1\.json/.test(output)).toEqual(true);
138
+ expect(/saved to.*Project 2\.json/.test(output)).toEqual(true);
139
+ expect(output.match(/saved to/g)?.length).toEqual(2);
140
+ expect(fs.readdirSync(consts.TEXT_DIR).length).toEqual(2);
141
+ });
142
+ });