@dittowords/cli 4.5.2 → 5.0.0-beta.1

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 (171) hide show
  1. package/README.md +6 -5
  2. package/bin/ditto.js +110 -285
  3. package/package.json +16 -10
  4. package/.github/actions/install-node-dependencies/action.yml +0 -24
  5. package/.github/workflows/required-checks.yml +0 -24
  6. package/.husky/pre-commit +0 -4
  7. package/.prettierignore +0 -0
  8. package/.prettierrc.json +0 -1
  9. package/__mocks__/fs.js +0 -2
  10. package/babel.config.js +0 -6
  11. package/bin/__mocks__/api.js +0 -48
  12. package/bin/__mocks__/api.js.map +0 -1
  13. package/bin/add-project.js +0 -104
  14. package/bin/add-project.js.map +0 -1
  15. package/bin/api.js +0 -54
  16. package/bin/api.js.map +0 -1
  17. package/bin/component-folders.js +0 -59
  18. package/bin/component-folders.js.map +0 -1
  19. package/bin/config.js +0 -242
  20. package/bin/config.js.map +0 -1
  21. package/bin/config.test.js +0 -93
  22. package/bin/config.test.js.map +0 -1
  23. package/bin/consts.js +0 -57
  24. package/bin/consts.js.map +0 -1
  25. package/bin/ditto.js.map +0 -1
  26. package/bin/generate-suggestions.js +0 -183
  27. package/bin/generate-suggestions.js.map +0 -1
  28. package/bin/generate-suggestions.test.js +0 -200
  29. package/bin/generate-suggestions.test.js.map +0 -1
  30. package/bin/http/__mocks__/fetchComponentFolders.js +0 -71
  31. package/bin/http/__mocks__/fetchComponentFolders.js.map +0 -1
  32. package/bin/http/__mocks__/fetchComponents.js +0 -73
  33. package/bin/http/__mocks__/fetchComponents.js.map +0 -1
  34. package/bin/http/__mocks__/fetchVariants.js +0 -71
  35. package/bin/http/__mocks__/fetchVariants.js.map +0 -1
  36. package/bin/http/fetchComponentFolders.js +0 -64
  37. package/bin/http/fetchComponentFolders.js.map +0 -1
  38. package/bin/http/fetchComponents.js +0 -78
  39. package/bin/http/fetchComponents.js.map +0 -1
  40. package/bin/http/fetchVariants.js +0 -87
  41. package/bin/http/fetchVariants.js.map +0 -1
  42. package/bin/http/http.test.js +0 -159
  43. package/bin/http/http.test.js.map +0 -1
  44. package/bin/http/importComponents.js +0 -114
  45. package/bin/http/importComponents.js.map +0 -1
  46. package/bin/importComponents.js +0 -65
  47. package/bin/importComponents.js.map +0 -1
  48. package/bin/init/init.js +0 -126
  49. package/bin/init/init.js.map +0 -1
  50. package/bin/init/project.js +0 -182
  51. package/bin/init/project.js.map +0 -1
  52. package/bin/init/project.test.js +0 -26
  53. package/bin/init/project.test.js.map +0 -1
  54. package/bin/init/token.js +0 -196
  55. package/bin/init/token.js.map +0 -1
  56. package/bin/init/token.test.js +0 -147
  57. package/bin/init/token.test.js.map +0 -1
  58. package/bin/output.js +0 -76
  59. package/bin/output.js.map +0 -1
  60. package/bin/pull-lib.test.js +0 -379
  61. package/bin/pull-lib.test.js.map +0 -1
  62. package/bin/pull.js +0 -562
  63. package/bin/pull.js.map +0 -1
  64. package/bin/pull.test.js +0 -151
  65. package/bin/pull.test.js.map +0 -1
  66. package/bin/remove-project.js +0 -99
  67. package/bin/remove-project.js.map +0 -1
  68. package/bin/replace.js +0 -171
  69. package/bin/replace.js.map +0 -1
  70. package/bin/replace.test.js +0 -197
  71. package/bin/replace.test.js.map +0 -1
  72. package/bin/types.js +0 -21
  73. package/bin/types.js.map +0 -1
  74. package/bin/utils/cleanFileName.js +0 -40
  75. package/bin/utils/cleanFileName.js.map +0 -1
  76. package/bin/utils/cleanFileName.test.js +0 -15
  77. package/bin/utils/cleanFileName.test.js.map +0 -1
  78. package/bin/utils/createSentryContext.js +0 -43
  79. package/bin/utils/createSentryContext.js.map +0 -1
  80. package/bin/utils/determineModuleType.js +0 -79
  81. package/bin/utils/determineModuleType.js.map +0 -1
  82. package/bin/utils/determineModuleType.test.js +0 -60
  83. package/bin/utils/determineModuleType.test.js.map +0 -1
  84. package/bin/utils/generateIOSBundles.js +0 -147
  85. package/bin/utils/generateIOSBundles.js.map +0 -1
  86. package/bin/utils/generateJsDriver.js +0 -178
  87. package/bin/utils/generateJsDriver.js.map +0 -1
  88. package/bin/utils/generateJsDriverTypeFile.js +0 -105
  89. package/bin/utils/generateJsDriverTypeFile.js.map +0 -1
  90. package/bin/utils/generateSwiftDriver.js +0 -93
  91. package/bin/utils/generateSwiftDriver.js.map +0 -1
  92. package/bin/utils/getSelectedProjects.js +0 -67
  93. package/bin/utils/getSelectedProjects.js.map +0 -1
  94. package/bin/utils/processMetaOption.js +0 -40
  95. package/bin/utils/processMetaOption.js.map +0 -1
  96. package/bin/utils/processMetaOption.test.js +0 -45
  97. package/bin/utils/processMetaOption.test.js.map +0 -1
  98. package/bin/utils/projectsToText.js +0 -58
  99. package/bin/utils/projectsToText.js.map +0 -1
  100. package/bin/utils/promptForProject.js +0 -96
  101. package/bin/utils/promptForProject.js.map +0 -1
  102. package/bin/utils/quit.js +0 -73
  103. package/bin/utils/quit.js.map +0 -1
  104. package/bin/utils/sourcesToText.js +0 -57
  105. package/bin/utils/sourcesToText.js.map +0 -1
  106. package/etsc.config.js +0 -13
  107. package/jest.config.ts +0 -16
  108. package/jsconfig.json +0 -5
  109. package/lib/__mocks__/api.ts +0 -12
  110. package/lib/add-project.ts +0 -48
  111. package/lib/api.ts +0 -16
  112. package/lib/component-folders.ts +0 -9
  113. package/lib/config.test.ts +0 -79
  114. package/lib/config.ts +0 -279
  115. package/lib/consts.ts +0 -22
  116. package/lib/ditto.ts +0 -285
  117. package/lib/generate-suggestions.test.ts +0 -169
  118. package/lib/generate-suggestions.ts +0 -166
  119. package/lib/http/__mocks__/fetchComponentFolders.ts +0 -23
  120. package/lib/http/__mocks__/fetchComponents.ts +0 -24
  121. package/lib/http/__mocks__/fetchVariants.ts +0 -21
  122. package/lib/http/fetchComponentFolders.ts +0 -23
  123. package/lib/http/fetchComponents.ts +0 -43
  124. package/lib/http/fetchVariants.ts +0 -42
  125. package/lib/http/http.test.ts +0 -122
  126. package/lib/http/importComponents.ts +0 -79
  127. package/lib/importComponents.ts +0 -24
  128. package/lib/init/init.ts +0 -79
  129. package/lib/init/project.test.ts +0 -26
  130. package/lib/init/project.ts +0 -136
  131. package/lib/init/token.test.ts +0 -99
  132. package/lib/init/token.ts +0 -156
  133. package/lib/output.ts +0 -21
  134. package/lib/pull-lib.test.ts +0 -367
  135. package/lib/pull.test.ts +0 -117
  136. package/lib/pull.ts +0 -629
  137. package/lib/remove-project.ts +0 -44
  138. package/lib/replace.test.ts +0 -157
  139. package/lib/replace.ts +0 -140
  140. package/lib/types.ts +0 -83
  141. package/lib/utils/cleanFileName.test.ts +0 -11
  142. package/lib/utils/cleanFileName.ts +0 -8
  143. package/lib/utils/createSentryContext.ts +0 -20
  144. package/lib/utils/determineModuleType.test.ts +0 -48
  145. package/lib/utils/determineModuleType.ts +0 -55
  146. package/lib/utils/generateIOSBundles.ts +0 -122
  147. package/lib/utils/generateJsDriver.ts +0 -207
  148. package/lib/utils/generateJsDriverTypeFile.ts +0 -75
  149. package/lib/utils/generateSwiftDriver.ts +0 -48
  150. package/lib/utils/getSelectedProjects.ts +0 -36
  151. package/lib/utils/processMetaOption.test.ts +0 -18
  152. package/lib/utils/processMetaOption.ts +0 -16
  153. package/lib/utils/projectsToText.ts +0 -29
  154. package/lib/utils/promptForProject.ts +0 -61
  155. package/lib/utils/quit.ts +0 -7
  156. package/lib/utils/sourcesToText.ts +0 -25
  157. package/pull_request_template.md +0 -20
  158. package/testfiles/en.json +0 -5
  159. package/testfiles/es.json +0 -5
  160. package/testfiles/fr.json +0 -5
  161. package/testfiles/test1.jsx +0 -18
  162. package/testfiles/test2.jsx +0 -9
  163. package/testing/.gitkeep +0 -0
  164. package/testing/fixtures/bad-yaml.yml +0 -6
  165. package/testing/fixtures/ditto-config-no-token +0 -2
  166. package/testing/fixtures/project-config-empty-projects.yml +0 -1
  167. package/testing/fixtures/project-config-no-id.yml +0 -2
  168. package/testing/fixtures/project-config-no-name.yml +0 -2
  169. package/testing/fixtures/project-config-pull.yml +0 -0
  170. package/testing/fixtures/project-config-working.yml +0 -3
  171. package/tsconfig.json +0 -16
@@ -1,79 +0,0 @@
1
- import { createApiClient } from "../api";
2
- import FormData from "form-data";
3
- import fs from "fs";
4
-
5
- export interface ImportComponentResponse {
6
- componentsInserted: number;
7
- firstImportedId: string;
8
- }
9
-
10
- export interface CsvColumnMapping {
11
- text: number;
12
- name: string;
13
- notes?: number;
14
- tags?: number;
15
- status?: number;
16
- componentId?: number;
17
- }
18
-
19
- export async function importComponents(
20
- path: string,
21
- options: {
22
- csvColumnMapping?: CsvColumnMapping;
23
- }
24
- ): Promise<ImportComponentResponse> {
25
- const api = createApiClient();
26
-
27
- if (!fs.existsSync(path)) {
28
- console.error("Failed to import file: couldn't find file at path " + path);
29
- return {
30
- componentsInserted: 0,
31
- firstImportedId: "null",
32
- };
33
- }
34
-
35
- const form = new FormData();
36
- form.append("import", fs.createReadStream(path));
37
-
38
- const requestOptions = {
39
- method: "POST",
40
- url: "/v1/components/file",
41
- params: {
42
- ...(options.csvColumnMapping?.name
43
- ? { name: `[${options.csvColumnMapping.name}]` }
44
- : {}),
45
- ...(options.csvColumnMapping?.text
46
- ? { text: options.csvColumnMapping.text }
47
- : {}),
48
- ...(options.csvColumnMapping?.notes
49
- ? { notes: options.csvColumnMapping.notes }
50
- : {}),
51
- ...(options.csvColumnMapping?.status
52
- ? { status: options.csvColumnMapping.status }
53
- : {}),
54
- ...(options.csvColumnMapping?.tags
55
- ? { tags: options.csvColumnMapping.tags }
56
- : {}),
57
- ...(options.csvColumnMapping?.componentId
58
- ? { componentId: options.csvColumnMapping.componentId }
59
- : {}),
60
- },
61
- headers: {
62
- "content-type": "multipart/form-data",
63
- },
64
- data: form,
65
- };
66
-
67
- try {
68
- const { data } = await api(requestOptions);
69
-
70
- return data;
71
- } catch (e: any) {
72
- console.error("Failed to import file.");
73
- console.error(e.response?.data?.message || e.message);
74
- return {
75
- componentsInserted: 0,
76
- firstImportedId: "null",
77
- };
78
- }
79
- }
@@ -1,24 +0,0 @@
1
- import {
2
- CsvColumnMapping,
3
- importComponents as importComponentsHttp,
4
- } from "./http/importComponents";
5
-
6
- async function importComponents(
7
- filePath: string,
8
- options: {
9
- csvColumnMapping?: CsvColumnMapping;
10
- }
11
- ) {
12
- if (
13
- filePath.endsWith(".csv") &&
14
- (!options.csvColumnMapping?.name || !options.csvColumnMapping?.text)
15
- ) {
16
- throw new Error(
17
- ".csv files require the --component-name and --text flags."
18
- );
19
- }
20
- const importResult = await importComponentsHttp(filePath, options);
21
- console.log(JSON.stringify(importResult));
22
- }
23
-
24
- export { importComponents };
package/lib/init/init.ts DELETED
@@ -1,79 +0,0 @@
1
- // Related to initializing a user/environment to ditto.
2
- // expected to be run once per project.
3
- import boxen from "boxen";
4
- import chalk from "chalk";
5
- import projectsToText from "../utils/projectsToText";
6
-
7
- import { needsSource, collectAndSaveSource } from "./project";
8
- import { needsToken, collectAndSaveToken } from "./token";
9
-
10
- import config from "../config";
11
- import output from "../output";
12
- import sourcesToText from "../utils/sourcesToText";
13
- import { quit } from "../utils/quit";
14
-
15
- export const needsTokenOrSource = () => needsToken() || needsSource();
16
-
17
- function welcome() {
18
- const msg = chalk.white(`${chalk.bold(
19
- "Welcome to the",
20
- chalk.magentaBright("Ditto CLI")
21
- )}.
22
-
23
- We're glad to have you here.`);
24
- console.log(boxen(msg, { padding: 1 }));
25
- }
26
-
27
- export const init = async () => {
28
- welcome();
29
-
30
- if (needsToken()) {
31
- await collectAndSaveToken();
32
- }
33
-
34
- const {
35
- hasSourceData,
36
- validProjects,
37
- shouldFetchComponentLibrary,
38
- hasTopLevelComponentsField,
39
- hasTopLevelProjectsField,
40
- } = config.parseSourceInformation();
41
-
42
- if (hasTopLevelProjectsField) {
43
- return await quit(`${output.errorText(
44
- `Support for ${output.warnText(
45
- "projects"
46
- )} as a top-level field has been removed; please configure ${output.warnText(
47
- "sources.projects"
48
- )} instead.`
49
- )}
50
- See ${output.url("https://github.com/dittowords/cli")} for more information.`);
51
- }
52
-
53
- if (hasTopLevelComponentsField) {
54
- return await quit(
55
- `${output.errorText(
56
- "Support for `components` as a top-level field has been removed; please configure `sources.components` instead."
57
- )}
58
- See ${output.url("https://github.com/dittowords/cli")} for more information.`
59
- );
60
- }
61
-
62
- if (!hasSourceData) {
63
- console.log(
64
- `Looks like there are no Ditto sources selected for your current directory: ${output.info(
65
- process.cwd()
66
- )}.`
67
- );
68
- await collectAndSaveSource({ initialize: true, components: true });
69
- return;
70
- }
71
-
72
- const message =
73
- "You're currently set up to sync text from " +
74
- sourcesToText(validProjects, shouldFetchComponentLibrary);
75
-
76
- console.log(message);
77
- };
78
-
79
- export default { init };
@@ -1,26 +0,0 @@
1
- const fs = require("fs");
2
- const yaml = require("js-yaml");
3
-
4
- import { _testing } from "./project";
5
-
6
- const fakeProjectDir = "/";
7
-
8
- jest.mock("fs");
9
-
10
- describe("saveProject", () => {
11
- const configFile = "/ditto/config.yml";
12
- const projectName = "My Amazing Project";
13
- const projectId = "5f284259ce1d451b2eb2e23c";
14
-
15
- beforeEach(() => {
16
- _testing.saveProject(configFile, projectName, projectId);
17
- });
18
-
19
- it("creates a config file with config data", () => {
20
- const fileContents = fs.readFileSync(configFile, "utf8");
21
- const data = yaml.load(fileContents);
22
- expect(data.sources.projects).toBeDefined();
23
- expect(data.sources.projects[0].name).toEqual(projectName);
24
- expect(data.sources.projects[0].id).toEqual(projectId);
25
- });
26
- });
@@ -1,136 +0,0 @@
1
- import ora from "ora";
2
-
3
- import { createApiClient } 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
- getSelectedProjects,
10
- getIsUsingComponents,
11
- } from "../utils/getSelectedProjects";
12
- import promptForProject from "../utils/promptForProject";
13
- import { AxiosResponse } from "axios";
14
- import { Project, Token } from "../types";
15
- import { quit } from "../utils/quit";
16
-
17
- function saveProject(file: string, name: string, id: string) {
18
- if (id === "components") {
19
- config.writeProjectConfigData(file, {
20
- sources: { components: true },
21
- });
22
- return;
23
- }
24
-
25
- const projects = [...getSelectedProjects(file), { name, id }];
26
- config.writeProjectConfigData(file, { sources: { projects } });
27
- }
28
-
29
- export const needsSource = () => {
30
- return !config.parseSourceInformation().hasSourceData;
31
- };
32
-
33
- async function askForAnotherToken() {
34
- config.deleteToken(consts.CONFIG_FILE, consts.API_HOST);
35
- const message =
36
- "Looks like the API key you have saved no longer works. Please enter another one.";
37
- await collectAndSaveToken(message);
38
- }
39
-
40
- async function listProjects(token: Token, projectsAlreadySelected: Project[]) {
41
- const api = createApiClient();
42
- const spinner = ora("Fetching sources in your workspace...");
43
- spinner.start();
44
-
45
- let response: AxiosResponse<{ id: string; name: string }[]>;
46
- try {
47
- response = await api.get("/v1/projects");
48
- } catch (e) {
49
- spinner.stop();
50
- throw e;
51
- }
52
-
53
- const projectsAlreadySelectedSet = projectsAlreadySelected.reduce(
54
- (set, project) => set.add(project.id.toString()),
55
- new Set<string>()
56
- );
57
-
58
- const result = response.data.filter(
59
- ({ id }) =>
60
- // covers an edge case where v0 of the API includes the component library
61
- // in the response from the `/project-names` endpoint
62
- id !== "ditto_component_library" &&
63
- !projectsAlreadySelectedSet.has(id.toString())
64
- );
65
-
66
- spinner.stop();
67
-
68
- return result;
69
- }
70
-
71
- async function collectSource(token: Token, includeComponents: boolean) {
72
- const projectsAlreadySelected = getSelectedProjects();
73
- const componentSourceSelected = getIsUsingComponents();
74
-
75
- let sources = await listProjects(token, projectsAlreadySelected);
76
- if (includeComponents && !componentSourceSelected) {
77
- sources = [
78
- { id: "ditto_component_library", name: "Ditto Component Library" },
79
- ...sources,
80
- ];
81
- }
82
-
83
- if (!sources?.length) {
84
- console.log("You're currently syncing all projects in your workspace.");
85
- console.log(
86
- output.warnText(
87
- "Not seeing a project that you were expecting? Verify that developer mode is enabled on that project. More info: https://www.dittowords.com/docs/ditto-developer-mode"
88
- )
89
- );
90
- return null;
91
- }
92
-
93
- return promptForProject({
94
- projects: sources,
95
- message: "Choose the source you'd like to sync text from",
96
- });
97
- }
98
-
99
- export const collectAndSaveSource = async (
100
- { components = false }: { initialize?: boolean; components?: boolean } = {
101
- components: false,
102
- }
103
- ) => {
104
- try {
105
- const token = config.getToken(consts.CONFIG_FILE, consts.API_HOST);
106
- const project = await collectSource(token, components);
107
- if (!project) {
108
- await quit(null, 0);
109
- return;
110
- }
111
-
112
- console.log(
113
- "\n" +
114
- `Thanks for adding ${output.info(
115
- project.name
116
- )} to your selected sources.\n` +
117
- `We saved your updated configuration to: ${output.info(
118
- consts.PROJECT_CONFIG_FILE
119
- )}\n`
120
- );
121
-
122
- saveProject(consts.PROJECT_CONFIG_FILE, project.name, project.id);
123
- } catch (e: any) {
124
- console.log(e);
125
- if (e.response && e.response.status === 404) {
126
- await askForAnotherToken();
127
- await collectAndSaveSource({ components });
128
- } else {
129
- await quit(null, 2);
130
- }
131
- }
132
- };
133
-
134
- export const _testing = { saveProject, needsSource };
135
-
136
- export default { needsSource, collectAndSaveSource };
@@ -1,99 +0,0 @@
1
- import fs from "fs";
2
- import config from "../config";
3
- import { randomUUID } from "crypto";
4
- import { needsToken } from "./token";
5
- import { _test } from "./token";
6
- import { vol } from "memfs";
7
- import { jest } from "@jest/globals";
8
- import axios from "axios";
9
-
10
- jest.mock("fs");
11
- jest.mock("../api");
12
-
13
- const axiosMocked = jest.mocked(axios);
14
-
15
- const defaultEnv = { ...process.env };
16
-
17
- beforeEach(() => {
18
- vol.reset();
19
- process.env = { ...defaultEnv };
20
- });
21
-
22
- describe("needsToken", () => {
23
- it("is true if there is no config file", () => {
24
- expect(needsToken(randomUUID())).toBeTruthy();
25
- });
26
-
27
- it("is false if there is a token in the environment", () => {
28
- process.env.DITTO_API_KEY = "xxx-xxx-xxx";
29
- expect(needsToken(randomUUID())).toBe(false);
30
- });
31
-
32
- describe("with a config file", () => {
33
- let configFile = "";
34
-
35
- beforeEach(async () => {
36
- configFile = `/${randomUUID()}`;
37
- await new Promise((resolve, reject) =>
38
- fs.writeFile(configFile, "", (err) => {
39
- if (err) reject(err);
40
- else {
41
- resolve(null);
42
- }
43
- })
44
- );
45
- });
46
-
47
- it("returns true if empty", () => {
48
- expect(needsToken(configFile, "testing.dittowords.com")).toBeTruthy();
49
- });
50
-
51
- describe("with some data", () => {
52
- beforeEach(() => {
53
- config.saveToken(configFile, "badtesting.dittowords.com", "faketoken");
54
- });
55
-
56
- it("is true if there is no entries for our API host", () => {
57
- expect(needsToken(configFile, "testing.dittowords.com")).toBeTruthy();
58
- });
59
-
60
- it("is false if we have a token listed", () => {
61
- config.saveToken(configFile, "testing.dittowords.com", "faketoken");
62
- expect(needsToken(configFile, "testing.dittowords.com")).toBeFalsy();
63
- });
64
- });
65
-
66
- it("is true if there is no token listed", () => {
67
- const configNoToken = "../../testing/fixtures/ditto-config-no-token";
68
- expect(needsToken(configNoToken, "testing.dittowords.com")).toBeTruthy();
69
- });
70
-
71
- it("is strips the protocol when writing an entry", () => {
72
- config.saveToken(
73
- configFile,
74
- "https://testing.dittowords.com",
75
- "faketoken"
76
- );
77
- expect(needsToken(configFile, "testing.dittowords.com")).toBeFalsy();
78
- });
79
- });
80
- });
81
-
82
- const { verifyTokenUsingTokenCheck } = _test;
83
- describe("verifyTokenUsingTokenCheck", () => {
84
- it("returns success: true for api success response", async () => {
85
- axiosMocked.get.mockResolvedValueOnce({ status: 200 });
86
- const result = await verifyTokenUsingTokenCheck("xxx-xxx");
87
- expect(result.success).toBe(true);
88
- });
89
- it("returns success: false for api unauthorized response", async () => {
90
- axiosMocked.get.mockResolvedValueOnce({ status: 401 });
91
- const result = await verifyTokenUsingTokenCheck("xxx-xxx");
92
- expect(result.success).toBe(false);
93
- });
94
- it("returns success: false for api invalid response", async () => {
95
- axiosMocked.get.mockResolvedValueOnce("error");
96
- const result = await verifyTokenUsingTokenCheck("xxx-xxx");
97
- expect(result.success).toBe(false);
98
- });
99
- });
package/lib/init/token.ts DELETED
@@ -1,156 +0,0 @@
1
- import fs from "fs";
2
- import * as Sentry from "@sentry/node";
3
-
4
- import chalk from "chalk";
5
-
6
- import { prompt } from "enquirer";
7
-
8
- import { createApiClient } from "../api";
9
- import consts from "../consts";
10
- import output from "../output";
11
- import config from "../config";
12
- import { quit } from "../utils/quit";
13
- import { AxiosError, AxiosResponse } from "axios";
14
-
15
- export const needsToken = (configFile?: string, host = consts.API_HOST) => {
16
- if (config.getTokenFromEnv()) {
17
- return false;
18
- }
19
-
20
- const file = configFile || consts.CONFIG_FILE;
21
- if (!fs.existsSync(file)) return true;
22
- const configData = config.readGlobalConfigData(file);
23
- if (
24
- !configData[config.justTheHost(host)] ||
25
- configData[config.justTheHost(host)][0].token === ""
26
- )
27
- return true;
28
- return false;
29
- };
30
-
31
- async function verifyTokenUsingTokenCheck(
32
- token: string
33
- ): Promise<{ success: true } | { success: false; output: string[] }> {
34
- const axios = createApiClient(token);
35
- const endpoint = "/token-check";
36
-
37
- let resOrError: AxiosResponse<any> | undefined;
38
- try {
39
- resOrError = await axios.get(endpoint);
40
- } catch (e: unknown) {
41
- if (!(e instanceof AxiosError)) {
42
- return {
43
- success: false,
44
- output: [
45
- output.warnText(
46
- "Sorry! We're having trouble reaching the Ditto API."
47
- ),
48
- ],
49
- };
50
- }
51
-
52
- if (e.code === "ENOTFOUND") {
53
- return {
54
- success: false,
55
- output: [
56
- output.errorText(
57
- `Can't connect to API: ${output.url(consts.API_HOST)}`
58
- ),
59
- ],
60
- };
61
- }
62
-
63
- if (e.response?.status === 401 || e.response?.status === 404) {
64
- return {
65
- success: false,
66
- output: [
67
- output.errorText("This API key isn't valid. Please try another."),
68
- ],
69
- };
70
- }
71
- }
72
-
73
- if (typeof resOrError === "string") {
74
- return {
75
- success: false,
76
- output: [resOrError],
77
- };
78
- }
79
-
80
- if (resOrError?.status === 200) {
81
- return { success: true };
82
- }
83
-
84
- return {
85
- success: false,
86
- output: [output.errorText("This API key isn't valid. Please try another.")],
87
- };
88
- }
89
-
90
- // Returns true if valid, otherwise an error message.
91
- async function checkToken(token: string): Promise<any> {
92
- const result = await verifyTokenUsingTokenCheck(token);
93
- if (!result.success) {
94
- return result.output.join("\n");
95
- }
96
-
97
- return true;
98
- }
99
-
100
- async function collectToken(message: string | null) {
101
- const blue = output.info;
102
- const apiUrl = output.url("https://app.dittowords.com/account/devtools");
103
- const breadcrumbs = `${chalk.bold(blue("API Keys"))}`;
104
- const tokenDescription =
105
- message ||
106
- `To get started, you'll need your Ditto API key. You can find this at: ${apiUrl} under "${breadcrumbs}".`;
107
- console.log(tokenDescription);
108
-
109
- const response = await prompt<{ token: string }>({
110
- type: "input",
111
- name: "token",
112
- message: "What is your API key?",
113
- validate: (token) => checkToken(token),
114
- });
115
- return response.token;
116
- }
117
-
118
- /**
119
- *
120
- * @param {string | null} message
121
- * @returns
122
- */
123
- export const collectAndSaveToken = async (message: string | null = null) => {
124
- try {
125
- const token = await collectToken(message);
126
- console.log(
127
- `Thanks for authenticating. We'll save the key to: ${output.info(
128
- consts.CONFIG_FILE
129
- )}`
130
- );
131
- output.nl();
132
-
133
- config.saveToken(consts.CONFIG_FILE, consts.API_HOST, token);
134
- return token;
135
- } catch (error) {
136
- // https://github.com/enquirer/enquirer/issues/225#issue-516043136
137
- // Empty string corresponds to the user hitting Ctrl + C
138
- if (error === "") {
139
- await quit("", 0);
140
- return;
141
- }
142
-
143
- const eventId = Sentry.captureException(error);
144
- const eventStr = `\n\nError ID: ${output.info(eventId)}`;
145
-
146
- return await quit(
147
- output.errorText(
148
- "Something went wrong. Please contact support or try again later."
149
- ) + eventStr
150
- );
151
- }
152
- };
153
-
154
- export default { needsToken, collectAndSaveToken };
155
-
156
- export const _test = { verifyTokenUsingTokenCheck };
package/lib/output.ts DELETED
@@ -1,21 +0,0 @@
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
- };