@dittowords/cli 2.5.1 → 2.6.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/.idea/workspace.xml +124 -0
- package/README.md +23 -10
- package/babel.config.js +2 -8
- package/bin/add-project.js +38 -0
- package/bin/add-project.js.map +1 -0
- package/bin/api.js +20 -0
- package/bin/api.js.map +1 -0
- package/bin/config.js +174 -0
- package/bin/config.js.map +1 -0
- package/bin/consts.js +21 -0
- package/bin/consts.js.map +1 -0
- package/bin/ditto.js +71 -74
- package/bin/ditto.js.map +1 -0
- package/bin/init/init.js +39 -0
- package/bin/init/init.js.map +1 -0
- package/bin/init/project.js +115 -0
- package/bin/init/project.js.map +1 -0
- package/bin/init/token.js +89 -0
- package/bin/init/token.js.map +1 -0
- package/bin/output.js +34 -0
- package/bin/output.js.map +1 -0
- package/bin/pull.js +305 -0
- package/bin/pull.js.map +1 -0
- package/bin/remove-project.js +40 -0
- package/bin/remove-project.js.map +1 -0
- package/bin/types.js +3 -0
- package/bin/types.js.map +1 -0
- package/bin/utils/getSelectedProjects.js +76 -0
- package/bin/utils/getSelectedProjects.js.map +1 -0
- package/bin/utils/processMetaOption.js +15 -0
- package/bin/utils/processMetaOption.js.map +1 -0
- package/bin/utils/projectsToText.js +16 -0
- package/bin/utils/projectsToText.js.map +1 -0
- package/bin/utils/promptForProject.js +44 -0
- package/bin/utils/promptForProject.js.map +1 -0
- package/bin/utils/sourcesToText.js +25 -0
- package/bin/utils/sourcesToText.js.map +1 -0
- package/jest.config.js +1 -1
- package/lib/{add-project.js → add-project.ts} +8 -8
- package/lib/api.ts +15 -0
- package/lib/{config.test.js → config.test.ts} +14 -25
- package/lib/config.ts +214 -0
- package/lib/consts.ts +20 -0
- package/lib/ditto.ts +97 -0
- package/lib/init/{init.js → init.ts} +11 -12
- package/lib/init/project.test.ts +49 -0
- package/lib/init/{project.js → project.ts} +31 -29
- package/lib/init/{token.test.js → token.test.ts} +3 -3
- package/lib/init/{token.js → token.ts} +23 -19
- package/lib/output.ts +21 -0
- package/lib/pull.test.ts +172 -0
- package/lib/{pull.js → pull.ts} +145 -98
- package/lib/{remove-project.js → remove-project.ts} +11 -15
- package/lib/types.ts +23 -0
- package/lib/utils/getSelectedProjects.ts +55 -0
- package/lib/utils/processMetaOption.test.ts +18 -0
- package/lib/utils/processMetaOption.ts +16 -0
- package/lib/utils/{projectsToText.js → projectsToText.ts} +5 -4
- package/lib/utils/{promptForProject.js → promptForProject.ts} +18 -9
- package/lib/utils/{sourcesToText.js → sourcesToText.ts} +6 -5
- package/package.json +20 -15
- package/testing/fixtures/project-config-pull.yml +0 -0
- package/tsconfig.json +12 -0
- package/yarn-error.log +4466 -0
- package/lib/api.js +0 -18
- package/lib/config.js +0 -169
- package/lib/consts.js +0 -14
- package/lib/init/project.test.js +0 -99
- package/lib/output.js +0 -21
- package/lib/pull.test.js +0 -173
- package/lib/utils/getSelectedProjects.js +0 -44
- package/lib/utils/processMetaOption.js +0 -16
- package/lib/utils/processMetaOption.test.js +0 -16
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const config = require("./config");
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import tempy from "tempy";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import config from "./config";
|
|
9
7
|
|
|
10
8
|
const fakeHomedir = fs.mkdtempSync(path.join(__dirname, "../testing/tmp"));
|
|
11
9
|
|
|
@@ -57,11 +55,15 @@ describe("Tokens in config files", () => {
|
|
|
57
55
|
describe("saveToken", () => {
|
|
58
56
|
it("creates a config file with config data", () => {
|
|
59
57
|
const fileContents = fs.readFileSync(configFile, "utf8");
|
|
60
|
-
const configData = yaml.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
58
|
+
const configData = yaml.load(fileContents);
|
|
59
|
+
if (configData && typeof configData === "object") {
|
|
60
|
+
expect(configData["testing.dittowords.com"]).toBeDefined();
|
|
61
|
+
expect(configData["testing.dittowords.com"][0].token).toEqual(
|
|
62
|
+
"faketoken"
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
fail("Config Data should have been an object!");
|
|
66
|
+
}
|
|
65
67
|
});
|
|
66
68
|
});
|
|
67
69
|
|
|
@@ -73,16 +75,3 @@ describe("Tokens in config files", () => {
|
|
|
73
75
|
});
|
|
74
76
|
});
|
|
75
77
|
});
|
|
76
|
-
|
|
77
|
-
describe("save", () => {
|
|
78
|
-
let data;
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
const file = tempy.writeSync("");
|
|
81
|
-
config.save(file, "test.setting", true);
|
|
82
|
-
data = config.readData(file);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("writes a setting correctly", () => {
|
|
86
|
-
expect(data.test.setting).toEqual(true);
|
|
87
|
-
});
|
|
88
|
-
});
|
package/lib/config.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import url from "url";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
|
|
6
|
+
import consts from "./consts";
|
|
7
|
+
import { Project, ConfigYAML } from "./types";
|
|
8
|
+
|
|
9
|
+
function createFileIfMissing(filename: string) {
|
|
10
|
+
const dir = path.dirname(filename);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(filename)) {
|
|
15
|
+
fs.closeSync(fs.openSync(filename, "w"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function jsonIsConfigYAML(json: unknown): json is ConfigYAML {
|
|
20
|
+
return typeof json === "object";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function jsonIsGlobalYAML(
|
|
24
|
+
json: unknown
|
|
25
|
+
): json is Record<string, { token: string }[]> {
|
|
26
|
+
return (
|
|
27
|
+
!!json &&
|
|
28
|
+
typeof json === "object" &&
|
|
29
|
+
Object.values(json).every((arr) =>
|
|
30
|
+
arr.every(
|
|
31
|
+
(val: any) =>
|
|
32
|
+
typeof val === "object" && Object.keys(val).includes("token")
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read data from a project config file
|
|
40
|
+
* @param {string} file defaults to `PROJECT_CONFIG_FILE` defined in `constants.js`
|
|
41
|
+
* @param {*} defaultData defaults to `{}`
|
|
42
|
+
* @returns { ConfigYAML }
|
|
43
|
+
*/
|
|
44
|
+
function readProjectConfigData(
|
|
45
|
+
file = consts.PROJECT_CONFIG_FILE,
|
|
46
|
+
defaultData = {}
|
|
47
|
+
): ConfigYAML {
|
|
48
|
+
createFileIfMissing(file);
|
|
49
|
+
const fileContents = fs.readFileSync(file, "utf8");
|
|
50
|
+
const yamlData = yaml.load(fileContents);
|
|
51
|
+
if (jsonIsConfigYAML(yamlData)) {
|
|
52
|
+
return yamlData;
|
|
53
|
+
}
|
|
54
|
+
return defaultData;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read data from a global config file
|
|
59
|
+
* @param {string} file defaults to `CONFIG_FILE` defined in `constants.js`
|
|
60
|
+
* @param {*} defaultData defaults to `{}`
|
|
61
|
+
* @returns { Record<string, { token: string }[]> }
|
|
62
|
+
*/
|
|
63
|
+
function readGlobalConfigData(
|
|
64
|
+
file = consts.CONFIG_FILE,
|
|
65
|
+
defaultData = {}
|
|
66
|
+
): Record<string, { token: string }[]> {
|
|
67
|
+
createFileIfMissing(file);
|
|
68
|
+
const fileContents = fs.readFileSync(file, "utf8");
|
|
69
|
+
const yamlData = yaml.load(fileContents);
|
|
70
|
+
if (jsonIsGlobalYAML(yamlData)) {
|
|
71
|
+
return yamlData;
|
|
72
|
+
}
|
|
73
|
+
return defaultData;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeProjectConfigData(file: string, data: object) {
|
|
77
|
+
createFileIfMissing(file);
|
|
78
|
+
const existingData = readProjectConfigData(file);
|
|
79
|
+
const yamlStr = yaml.dump({ ...existingData, ...data });
|
|
80
|
+
fs.writeFileSync(file, yamlStr, "utf8");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function writeGlobalConfigData(file: string, data: object) {
|
|
84
|
+
createFileIfMissing(file);
|
|
85
|
+
const existingData = readGlobalConfigData(file);
|
|
86
|
+
const yamlStr = yaml.dump({ ...existingData, ...data });
|
|
87
|
+
fs.writeFileSync(file, yamlStr, "utf8");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function justTheHost(host: string) {
|
|
91
|
+
if (!host.includes("://")) return host;
|
|
92
|
+
return url.parse(host).hostname || "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function deleteToken(file: string, host: string) {
|
|
96
|
+
const data = readGlobalConfigData(file);
|
|
97
|
+
const hostParsed = justTheHost(host);
|
|
98
|
+
data[hostParsed] = [];
|
|
99
|
+
data[hostParsed][0] = { token: "" };
|
|
100
|
+
writeGlobalConfigData(file, data);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function saveToken(file: string, host: string, token: string) {
|
|
104
|
+
const data = readGlobalConfigData(file);
|
|
105
|
+
const hostParsed = justTheHost(host);
|
|
106
|
+
data[hostParsed] = []; // only allow one token per host
|
|
107
|
+
data[hostParsed][0] = { token };
|
|
108
|
+
writeGlobalConfigData(file, data);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getTokenFromEnv() {
|
|
112
|
+
return process.env.DITTO_API_KEY;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
*
|
|
117
|
+
* @param {string} file
|
|
118
|
+
* @param {string} host
|
|
119
|
+
* @returns {Token}
|
|
120
|
+
*/
|
|
121
|
+
function getToken(file: string, host: string) {
|
|
122
|
+
const tokenFromEnv = getTokenFromEnv();
|
|
123
|
+
if (tokenFromEnv) {
|
|
124
|
+
return tokenFromEnv;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const data = readGlobalConfigData(file);
|
|
128
|
+
const hostEntry = data[justTheHost(host)];
|
|
129
|
+
if (!hostEntry) return undefined;
|
|
130
|
+
const { length } = hostEntry;
|
|
131
|
+
return hostEntry[length - 1].token;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const IS_DUPLICATE = /-(\d+$)/;
|
|
135
|
+
|
|
136
|
+
function dedupeProjectName(projectNames: Set<string>, projectName: string) {
|
|
137
|
+
let dedupedName = projectName;
|
|
138
|
+
|
|
139
|
+
if (projectNames.has(dedupedName)) {
|
|
140
|
+
while (projectNames.has(dedupedName)) {
|
|
141
|
+
const [_, numberStr] = dedupedName.match(IS_DUPLICATE) || [];
|
|
142
|
+
if (numberStr && !isNaN(parseInt(numberStr))) {
|
|
143
|
+
dedupedName = `${dedupedName.replace(IS_DUPLICATE, "")}-${
|
|
144
|
+
parseInt(numberStr) + 1
|
|
145
|
+
}`;
|
|
146
|
+
} else {
|
|
147
|
+
dedupedName = `${dedupedName}-1`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return dedupedName;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reads from the config file, filters out
|
|
157
|
+
* invalid projects, dedupes those remaining, and returns:
|
|
158
|
+
* - whether or not the data required to `pull` is present
|
|
159
|
+
* - whether or not the component library should be fetched
|
|
160
|
+
* - an array of valid, deduped projects
|
|
161
|
+
* - the `variants` and `format` config options
|
|
162
|
+
*/
|
|
163
|
+
function parseSourceInformation() {
|
|
164
|
+
const { projects, components, variants, format } = readProjectConfigData();
|
|
165
|
+
|
|
166
|
+
const projectNames = new Set<string>();
|
|
167
|
+
const validProjects: Project[] = [];
|
|
168
|
+
|
|
169
|
+
let componentLibraryInProjects = false;
|
|
170
|
+
|
|
171
|
+
(projects || []).forEach((project) => {
|
|
172
|
+
const isValid = project.id && project.name;
|
|
173
|
+
if (!isValid) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (project.id === "ditto_component_library") {
|
|
178
|
+
componentLibraryInProjects = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
project.fileName = dedupeProjectName(projectNames, project.name);
|
|
183
|
+
projectNames.add(project.fileName);
|
|
184
|
+
|
|
185
|
+
validProjects.push(project);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const shouldFetchComponentLibrary =
|
|
189
|
+
!!components || componentLibraryInProjects;
|
|
190
|
+
|
|
191
|
+
const hasSourceData = !!validProjects.length || shouldFetchComponentLibrary;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
hasSourceData,
|
|
195
|
+
validProjects,
|
|
196
|
+
shouldFetchComponentLibrary,
|
|
197
|
+
variants: variants || false,
|
|
198
|
+
format,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export default {
|
|
203
|
+
createFileIfMissing,
|
|
204
|
+
readProjectConfigData,
|
|
205
|
+
readGlobalConfigData,
|
|
206
|
+
writeGlobalConfigData,
|
|
207
|
+
writeProjectConfigData,
|
|
208
|
+
justTheHost,
|
|
209
|
+
saveToken,
|
|
210
|
+
deleteToken,
|
|
211
|
+
getToken,
|
|
212
|
+
getTokenFromEnv,
|
|
213
|
+
parseSourceInformation,
|
|
214
|
+
};
|
package/lib/consts.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export const API_HOST =
|
|
5
|
+
process.env.DITTO_API_HOST || "https://api.dittowords.com";
|
|
6
|
+
export const CONFIG_FILE =
|
|
7
|
+
process.env.DITTO_CONFIG_FILE || path.join(homedir(), ".config", "ditto");
|
|
8
|
+
export const PROJECT_CONFIG_FILE = path.normalize(
|
|
9
|
+
path.join("ditto", "config.yml")
|
|
10
|
+
);
|
|
11
|
+
export const TEXT_DIR = process.env.DITTO_TEXT_DIR || "ditto";
|
|
12
|
+
export const TEXT_FILE = path.normalize(path.join(TEXT_DIR, "text.json"));
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
API_HOST,
|
|
16
|
+
CONFIG_FILE,
|
|
17
|
+
PROJECT_CONFIG_FILE,
|
|
18
|
+
TEXT_DIR,
|
|
19
|
+
TEXT_FILE,
|
|
20
|
+
};
|
package/lib/ditto.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This is the main entry point for the ditto-cli command.
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
// to use V8's code cache to speed up instantiation time
|
|
5
|
+
import "v8-compile-cache";
|
|
6
|
+
|
|
7
|
+
import { init, needsInit } from "./init/init";
|
|
8
|
+
import { pull } from "./pull";
|
|
9
|
+
|
|
10
|
+
import addProject from "./add-project";
|
|
11
|
+
import removeProject from "./remove-project";
|
|
12
|
+
import processMetaOption from "./utils/processMetaOption";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Catch and report unexpected error.
|
|
16
|
+
* @param {any} error The thrown error object.
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
function quit(exitCode = 2) {
|
|
20
|
+
console.log("\nExiting Ditto CLI...\n");
|
|
21
|
+
process.exitCode = exitCode;
|
|
22
|
+
process.exit();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const setupCommands = () => {
|
|
26
|
+
program.name("ditto-cli");
|
|
27
|
+
program
|
|
28
|
+
.command("pull")
|
|
29
|
+
.description("Sync copy from Ditto into working directory")
|
|
30
|
+
.action(() => checkInit("pull"));
|
|
31
|
+
|
|
32
|
+
const projectDescription = "Add a Ditto project to sync copy from";
|
|
33
|
+
const projectCommand = program
|
|
34
|
+
.command("project")
|
|
35
|
+
.description(projectDescription)
|
|
36
|
+
.action(() => checkInit("project"));
|
|
37
|
+
|
|
38
|
+
projectCommand
|
|
39
|
+
.command("add")
|
|
40
|
+
.description(projectDescription)
|
|
41
|
+
.action(() => checkInit("project"));
|
|
42
|
+
|
|
43
|
+
projectCommand
|
|
44
|
+
.command("remove")
|
|
45
|
+
.description("Stop syncing copy from a Ditto project")
|
|
46
|
+
.action(() => checkInit("project remove"));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const setupOptions = () => {
|
|
50
|
+
program.option(
|
|
51
|
+
"-m, --meta <data...>",
|
|
52
|
+
"Optional metadata for this command to send arbitrary data to the backend. Ex: -m githubActionRequest:true trigger:manual"
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const checkInit = async (command: string) => {
|
|
57
|
+
if (needsInit() && command !== "project remove") {
|
|
58
|
+
try {
|
|
59
|
+
await init();
|
|
60
|
+
if (command === "pull") main(); // re-run to actually pull text now that init is finished
|
|
61
|
+
} catch (error) {
|
|
62
|
+
quit();
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
const { meta } = program.opts();
|
|
66
|
+
switch (command) {
|
|
67
|
+
case "pull":
|
|
68
|
+
pull({ meta: processMetaOption(meta) });
|
|
69
|
+
break;
|
|
70
|
+
case "project":
|
|
71
|
+
case "project add":
|
|
72
|
+
addProject();
|
|
73
|
+
break;
|
|
74
|
+
case "project remove":
|
|
75
|
+
removeProject();
|
|
76
|
+
break;
|
|
77
|
+
case "none":
|
|
78
|
+
setupCommands();
|
|
79
|
+
program.help();
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
quit();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const main = async () => {
|
|
88
|
+
if (process.argv.length <= 2 && process.argv[1].includes("ditto-cli")) {
|
|
89
|
+
await checkInit("none");
|
|
90
|
+
} else {
|
|
91
|
+
setupCommands();
|
|
92
|
+
setupOptions();
|
|
93
|
+
}
|
|
94
|
+
program.parse(process.argv);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
main();
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
// Related to initializing a user/environment to ditto.
|
|
2
2
|
// expected to be run once per project.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import boxen from "boxen";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import projectsToText from "../utils/projectsToText";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { needsSource, collectAndSaveProject } from "./project";
|
|
8
|
+
import { needsToken, collectAndSaveToken } from "./token";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
}
|
|
12
|
-
|
|
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 "
|
|
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.
|
|
27
|
+
config.writeProjectConfigData(file, { components: true });
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const projects = [...getSelectedProjects(), { name, id }];
|
|
30
32
|
|
|
31
|
-
config.
|
|
33
|
+
config.writeProjectConfigData(file, { projects });
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
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
|
|
54
|
-
|
|
55
|
+
let response: AxiosResponse<{ id: string; name: string }[]>;
|
|
55
56
|
try {
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
export default { needsSource, collectAndSaveProject };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import tempy from "tempy";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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", () => {
|