@dittowords/cli 2.8.0 → 3.2.0-alpha
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 +150 -141
- package/bin/add-project.js +5 -7
- package/bin/add-project.js.map +1 -1
- package/bin/config.js +37 -11
- package/bin/config.js.map +1 -1
- package/bin/ditto.js +82 -57
- package/bin/ditto.js.map +1 -1
- package/bin/generate-suggestions.js +71 -0
- package/bin/generate-suggestions.js.map +1 -0
- package/bin/http/fetchComponents.js +13 -0
- package/bin/http/fetchComponents.js.map +1 -0
- package/bin/http/fetchVariants.js +26 -0
- package/bin/http/fetchVariants.js.map +1 -0
- package/bin/init/init.js +17 -6
- package/bin/init/init.js.map +1 -1
- package/bin/init/project.js +38 -45
- package/bin/init/project.js.map +1 -1
- package/bin/init/token.js +22 -20
- package/bin/init/token.js.map +1 -1
- package/bin/pull.js +136 -193
- package/bin/pull.js.map +1 -1
- package/bin/remove-project.js +2 -7
- package/bin/remove-project.js.map +1 -1
- package/bin/replace.js +107 -0
- package/bin/replace.js.map +1 -0
- package/bin/utils/cleanFileName.js +11 -0
- package/bin/utils/cleanFileName.js.map +1 -0
- package/bin/utils/generateJsDriver.js +56 -0
- package/bin/utils/generateJsDriver.js.map +1 -0
- package/bin/utils/getSelectedProjects.js +3 -18
- package/bin/utils/getSelectedProjects.js.map +1 -1
- package/bin/utils/projectsToText.js +10 -1
- package/bin/utils/projectsToText.js.map +1 -1
- package/bin/utils/promptForProject.js +2 -3
- package/bin/utils/promptForProject.js.map +1 -1
- package/bin/utils/quit.js +10 -0
- package/bin/utils/quit.js.map +1 -0
- package/lib/add-project.ts +6 -9
- package/lib/config.ts +56 -19
- package/lib/ditto.ts +111 -60
- package/lib/generate-suggestions.test.ts +65 -0
- package/lib/generate-suggestions.ts +107 -0
- package/lib/http/fetchComponents.ts +14 -0
- package/lib/http/fetchVariants.ts +30 -0
- package/lib/init/init.ts +38 -6
- package/lib/init/project.test.ts +3 -3
- package/lib/init/project.ts +47 -58
- package/lib/init/token.ts +17 -16
- package/lib/pull.ts +199 -279
- package/lib/remove-project.ts +2 -8
- package/lib/replace.test.ts +101 -0
- package/lib/replace.ts +106 -0
- package/lib/types.ts +22 -3
- package/lib/utils/cleanFileName.ts +6 -0
- package/lib/utils/generateJsDriver.ts +68 -0
- package/lib/utils/getSelectedProjects.ts +5 -24
- package/lib/utils/projectsToText.ts +11 -1
- package/lib/utils/promptForProject.ts +2 -3
- package/lib/utils/quit.ts +5 -0
- package/package.json +9 -2
- package/tsconfig.json +2 -1
package/lib/replace.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import { parse } from "@babel/parser";
|
|
3
|
+
import traverse from "@babel/traverse";
|
|
4
|
+
import * as t from "@babel/types";
|
|
5
|
+
import { transformFromAst } from "@babel/core";
|
|
6
|
+
|
|
7
|
+
async function replaceJSXTextInFile(
|
|
8
|
+
filePath: string,
|
|
9
|
+
replacement: { searchString: string; replaceWith: string }
|
|
10
|
+
) {
|
|
11
|
+
const code = await fs.readFile(filePath, "utf-8");
|
|
12
|
+
const ast = parse(code, {
|
|
13
|
+
sourceType: "module",
|
|
14
|
+
plugins: ["jsx", "typescript"],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
traverse(ast, {
|
|
18
|
+
JSXText(path) {
|
|
19
|
+
const { searchString, replaceWith } = replacement;
|
|
20
|
+
|
|
21
|
+
const regex = new RegExp(searchString, "gi");
|
|
22
|
+
if (regex.test(path.node.value)) {
|
|
23
|
+
const splitValues = splitByCaseInsensitive(
|
|
24
|
+
path.node.value,
|
|
25
|
+
searchString
|
|
26
|
+
);
|
|
27
|
+
const nodes: (t.JSXElement | t.JSXText)[] = [];
|
|
28
|
+
|
|
29
|
+
splitValues.forEach((splitValue) => {
|
|
30
|
+
if (splitValue.toLowerCase() === searchString.toLowerCase()) {
|
|
31
|
+
const identifier = t.jsxIdentifier("DittoComponent");
|
|
32
|
+
const componentId = t.jsxAttribute(
|
|
33
|
+
t.jsxIdentifier("componentId"),
|
|
34
|
+
t.stringLiteral(replaceWith)
|
|
35
|
+
);
|
|
36
|
+
const o = t.jsxOpeningElement(identifier, [componentId], true);
|
|
37
|
+
const jsxElement = t.jsxElement(o, undefined, [], true);
|
|
38
|
+
nodes.push(jsxElement);
|
|
39
|
+
} else {
|
|
40
|
+
nodes.push(t.jsxText(splitValue));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
path.replaceWithMultiple(nodes);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// transfromFromAst types are wrong?
|
|
50
|
+
/* @ts-ignore */
|
|
51
|
+
const { code: transformedCode } = transformFromAst(ast, code, {
|
|
52
|
+
// Don't let this codebase's Babel config affect the code we're transforming.
|
|
53
|
+
configFile: false,
|
|
54
|
+
});
|
|
55
|
+
fs.writeFile(filePath, transformedCode);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function splitByCaseInsensitive(str: string, delimiter: string) {
|
|
59
|
+
return str.split(new RegExp(`(${delimiter})`, "gi")).filter((s) => s !== "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function replace(options: string[]) {
|
|
63
|
+
let filePath: string;
|
|
64
|
+
let searchString: string;
|
|
65
|
+
let replaceWith: string;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const parsedOptions = parseOptions(options);
|
|
69
|
+
filePath = parsedOptions.filePath;
|
|
70
|
+
searchString = parsedOptions.searchString;
|
|
71
|
+
replaceWith = parsedOptions.replaceWith;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error(e);
|
|
74
|
+
console.error(
|
|
75
|
+
"Usage for replace: ditto-cli replace <file path> <search string> <replace with>"
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
replaceJSXTextInFile(filePath, { searchString, replaceWith });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseOptions(options: string[]): {
|
|
84
|
+
filePath: string;
|
|
85
|
+
searchString: string;
|
|
86
|
+
replaceWith: string;
|
|
87
|
+
} {
|
|
88
|
+
if (options.length !== 3) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"The options array must contain <file path> <search string> <replace with>."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const filePath = options[0];
|
|
95
|
+
// Check if the file path exists and points to a regular file (not a directory or other file system object).
|
|
96
|
+
const isFilePathValid =
|
|
97
|
+
fs.existsSync(filePath) && fs.lstatSync(filePath).isFile();
|
|
98
|
+
|
|
99
|
+
if (!isFilePathValid) {
|
|
100
|
+
throw new Error(`${filePath} is not a valid file path.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { filePath, searchString: options[1], replaceWith: options[2] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { replace, parseOptions, replaceJSXTextInFile };
|
package/lib/types.ts
CHANGED
|
@@ -5,13 +5,31 @@ export interface Project {
|
|
|
5
5
|
fileName?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export type Source = Project;
|
|
9
|
+
|
|
10
|
+
interface ComponentFolder {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export interface ConfigYAML {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
sources?: {
|
|
17
|
+
components?: {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
folders?: ComponentFolder[];
|
|
20
|
+
};
|
|
21
|
+
projects?: Project[];
|
|
22
|
+
};
|
|
23
|
+
format?: "flat" | "structured" | "android-xml" | "ios-strings";
|
|
12
24
|
status?: string;
|
|
13
25
|
variants?: boolean;
|
|
14
26
|
richText?: boolean;
|
|
27
|
+
|
|
28
|
+
// these are legacy fields - if they exist, we should output
|
|
29
|
+
// a deprecation error, and suggest that they nest them under
|
|
30
|
+
// a top-level `sources` property
|
|
31
|
+
components?: boolean;
|
|
32
|
+
projects?: Project[];
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
export interface SourceInformation {
|
|
@@ -22,6 +40,7 @@ export interface SourceInformation {
|
|
|
22
40
|
format: string | undefined;
|
|
23
41
|
status: string | undefined;
|
|
24
42
|
richText: boolean | undefined;
|
|
43
|
+
componentFolders: ComponentFolder[] | null;
|
|
25
44
|
}
|
|
26
45
|
|
|
27
46
|
export type Token = string | undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import consts from "../consts";
|
|
4
|
+
import output from "../output";
|
|
5
|
+
import { Source } from "../types";
|
|
6
|
+
import { cleanFileName } from "./cleanFileName";
|
|
7
|
+
|
|
8
|
+
// compatability with legacy method of specifying project ids
|
|
9
|
+
// that is still used by the default format
|
|
10
|
+
const stringifySourceId = (projectId: string) =>
|
|
11
|
+
projectId === "ditto_component_library" ? projectId : `project_${projectId}`;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates an index.js file that can be consumed
|
|
15
|
+
* by an SDK - this is a big DX improvement because
|
|
16
|
+
* it provides a single entry point to get all data
|
|
17
|
+
* (including variants) instead of having to import
|
|
18
|
+
* each generated file individually.
|
|
19
|
+
*
|
|
20
|
+
* The generated file will have a unified format
|
|
21
|
+
* independent of the CLI configuration used to fetch
|
|
22
|
+
* data from Ditto.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// TODO: support ESM
|
|
26
|
+
export function generateJsDriver(sources: Source[]) {
|
|
27
|
+
const fileNames = fs
|
|
28
|
+
.readdirSync(consts.TEXT_DIR)
|
|
29
|
+
.filter((fileName) => /\.json$/.test(fileName));
|
|
30
|
+
|
|
31
|
+
const sourceIdsByName: Record<string, string> = sources.reduce(
|
|
32
|
+
(agg, source) => {
|
|
33
|
+
if (source.fileName) {
|
|
34
|
+
return { ...agg, [cleanFileName(source.fileName)]: source.id };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return agg;
|
|
38
|
+
},
|
|
39
|
+
{}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const data = fileNames.reduce(
|
|
43
|
+
(obj: Record<string, Record<string, string>>, fileName) => {
|
|
44
|
+
const [sourceId, rest] = fileName.split("__");
|
|
45
|
+
const [variantApiId] = rest.split(".");
|
|
46
|
+
|
|
47
|
+
const projectId = sourceIdsByName[sourceId];
|
|
48
|
+
const projectIdStr = stringifySourceId(projectId);
|
|
49
|
+
|
|
50
|
+
if (!obj[projectIdStr]) {
|
|
51
|
+
obj[projectIdStr] = {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
obj[projectIdStr][variantApiId] = `require('./${fileName}')`;
|
|
55
|
+
return obj;
|
|
56
|
+
},
|
|
57
|
+
{}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
let dataString = `module.exports = ${JSON.stringify(data, null, 2)}`
|
|
61
|
+
// remove quotes around require statements
|
|
62
|
+
.replace(/"require\((.*)\)"/g, "require($1)");
|
|
63
|
+
|
|
64
|
+
const filePath = path.resolve(consts.TEXT_DIR, "index.js");
|
|
65
|
+
fs.writeFileSync(filePath, dataString, { encoding: "utf8" });
|
|
66
|
+
|
|
67
|
+
return `Generated .js SDK driver at ${output.info(filePath)}`;
|
|
68
|
+
}
|
|
@@ -3,6 +3,7 @@ import yaml, { YAMLException } from "js-yaml";
|
|
|
3
3
|
|
|
4
4
|
import { PROJECT_CONFIG_FILE } from "../consts";
|
|
5
5
|
import { ConfigYAML, Project } from "../types";
|
|
6
|
+
import Config, { DEFAULT_CONFIG_JSON } from "../config";
|
|
6
7
|
|
|
7
8
|
function jsonIsConfigYAML(json: unknown): json is ConfigYAML {
|
|
8
9
|
return typeof json === "object";
|
|
@@ -28,28 +29,8 @@ function yamlToJson(_yaml: string): ConfigYAML | null {
|
|
|
28
29
|
* Returns an array containing all valid projects ({ id, name })
|
|
29
30
|
* currently contained in the project config file.
|
|
30
31
|
*/
|
|
31
|
-
export const getSelectedProjects = (
|
|
32
|
-
configFile
|
|
33
|
-
): Project[] => {
|
|
34
|
-
if (!fs.existsSync(configFile)) return [];
|
|
32
|
+
export const getSelectedProjects = (configFile = PROJECT_CONFIG_FILE) =>
|
|
33
|
+
Config.parseSourceInformation(configFile).validProjects;
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!(contentJson && contentJson.projects)) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return contentJson.projects.filter(({ name, id }) => name && id);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const getIsUsingComponents = (
|
|
47
|
-
configFile = PROJECT_CONFIG_FILE
|
|
48
|
-
): boolean => {
|
|
49
|
-
if (!fs.existsSync(configFile)) return false;
|
|
50
|
-
|
|
51
|
-
const contentYaml = fs.readFileSync(configFile, "utf8");
|
|
52
|
-
const contentJson = yamlToJson(contentYaml);
|
|
53
|
-
|
|
54
|
-
return !!contentJson && !!contentJson.components;
|
|
55
|
-
};
|
|
35
|
+
export const getIsUsingComponents = (configFile = PROJECT_CONFIG_FILE) =>
|
|
36
|
+
Config.parseSourceInformation(configFile).shouldFetchComponentLibrary;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import output from "../output";
|
|
2
2
|
import { Project } from "../types";
|
|
3
3
|
|
|
4
|
+
export const getSourceUrl = (sourceId: string) => {
|
|
5
|
+
let base = "https://app.dittowords.com";
|
|
6
|
+
|
|
7
|
+
if (sourceId === "ditto_component_library") {
|
|
8
|
+
return `${base}/components`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return `${base}/doc/${sourceId}`;
|
|
12
|
+
};
|
|
13
|
+
|
|
4
14
|
const projectsToText = (projects: Project[]) => {
|
|
5
15
|
return (
|
|
6
16
|
(projects || []).reduce(
|
|
@@ -10,7 +20,7 @@ const projectsToText = (projects: Project[]) => {
|
|
|
10
20
|
"- " +
|
|
11
21
|
output.info(name) +
|
|
12
22
|
" " +
|
|
13
|
-
output.subtle(
|
|
23
|
+
output.subtle(getSourceUrl(id))),
|
|
14
24
|
""
|
|
15
25
|
) + "\n"
|
|
16
26
|
);
|
|
@@ -2,12 +2,11 @@ const { AutoComplete } = require("enquirer");
|
|
|
2
2
|
|
|
3
3
|
import output from "../output";
|
|
4
4
|
import { Project } from "../types";
|
|
5
|
+
import { getSourceUrl } from "./projectsToText";
|
|
5
6
|
|
|
6
7
|
function formatProjectChoice(project: Project) {
|
|
7
8
|
return (
|
|
8
|
-
project.name +
|
|
9
|
-
" " +
|
|
10
|
-
output.subtle(project.url || `https://app.dittowords.com/doc/${project.id}`)
|
|
9
|
+
project.name + " " + output.subtle(project.url || getSourceUrl(project.id))
|
|
11
10
|
);
|
|
12
11
|
}
|
|
13
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dittowords/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "3.2.0-alpha",
|
|
4
4
|
"description": "Command Line Interface for Ditto (dittowords.com).",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"ditto-cli": "bin/ditto.js"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@babel/core": "^7.11.4",
|
|
37
36
|
"@babel/preset-env": "^7.20.2",
|
|
38
37
|
"@babel/preset-typescript": "^7.18.6",
|
|
39
38
|
"@tsconfig/node16": "^1.0.3",
|
|
@@ -53,12 +52,20 @@
|
|
|
53
52
|
"typescript": "^4.7.4"
|
|
54
53
|
},
|
|
55
54
|
"dependencies": {
|
|
55
|
+
"@babel/core": "^7.11.4",
|
|
56
|
+
"@babel/parser": "^7.21.4",
|
|
57
|
+
"@babel/traverse": "^7.21.4",
|
|
58
|
+
"@babel/types": "^7.21.4",
|
|
59
|
+
"@types/babel-traverse": "^6.25.7",
|
|
60
|
+
"@types/fs-extra": "^11.0.1",
|
|
56
61
|
"axios": "^0.27.2",
|
|
57
62
|
"boxen": "^5.1.2",
|
|
58
63
|
"chalk": "^4.1.0",
|
|
59
64
|
"commander": "^6.1.0",
|
|
60
65
|
"enquirer": "^2.3.6",
|
|
61
66
|
"faker": "^5.1.0",
|
|
67
|
+
"fs-extra": "^11.1.1",
|
|
68
|
+
"glob": "^9.3.4",
|
|
62
69
|
"js-yaml": "^4.1.0",
|
|
63
70
|
"ora": "^5.0.0",
|
|
64
71
|
"v8-compile-cache": "^2.1.1"
|