@dittowords/cli 3.0.0 → 3.2.2-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.
Files changed (64) hide show
  1. package/bin/ditto.js +36 -9
  2. package/bin/ditto.js.map +1 -1
  3. package/bin/generate-suggestions.js +71 -0
  4. package/bin/generate-suggestions.js.map +1 -0
  5. package/bin/http/fetchComponents.js +13 -0
  6. package/bin/http/fetchComponents.js.map +1 -0
  7. package/bin/lib/add-project.js +36 -0
  8. package/bin/lib/add-project.js.map +1 -0
  9. package/bin/lib/api.js +20 -0
  10. package/bin/lib/api.js.map +1 -0
  11. package/bin/lib/config.js +202 -0
  12. package/bin/lib/config.js.map +1 -0
  13. package/bin/lib/consts.js +21 -0
  14. package/bin/lib/consts.js.map +1 -0
  15. package/bin/lib/ditto.js +121 -0
  16. package/bin/lib/ditto.js.map +1 -0
  17. package/bin/lib/generate-suggestions.js +71 -0
  18. package/bin/lib/generate-suggestions.js.map +1 -0
  19. package/bin/lib/http/fetchComponents.js +13 -0
  20. package/bin/lib/http/fetchComponents.js.map +1 -0
  21. package/bin/lib/http/fetchVariants.js +26 -0
  22. package/bin/lib/http/fetchVariants.js.map +1 -0
  23. package/bin/lib/init/init.js +50 -0
  24. package/bin/lib/init/init.js.map +1 -0
  25. package/bin/lib/init/project.js +108 -0
  26. package/bin/lib/init/project.js.map +1 -0
  27. package/bin/lib/init/token.js +91 -0
  28. package/bin/lib/init/token.js.map +1 -0
  29. package/bin/lib/output.js +34 -0
  30. package/bin/lib/output.js.map +1 -0
  31. package/bin/lib/pull.js +264 -0
  32. package/bin/lib/pull.js.map +1 -0
  33. package/bin/lib/remove-project.js +35 -0
  34. package/bin/lib/remove-project.js.map +1 -0
  35. package/bin/lib/replace.js +107 -0
  36. package/bin/lib/replace.js.map +1 -0
  37. package/bin/lib/types.js +3 -0
  38. package/bin/lib/types.js.map +1 -0
  39. package/bin/lib/utils/cleanFileName.js +11 -0
  40. package/bin/lib/utils/cleanFileName.js.map +1 -0
  41. package/bin/lib/utils/generateJsDriver.js +56 -0
  42. package/bin/lib/utils/generateJsDriver.js.map +1 -0
  43. package/bin/lib/utils/getSelectedProjects.js +61 -0
  44. package/bin/lib/utils/getSelectedProjects.js.map +1 -0
  45. package/bin/lib/utils/processMetaOption.js +15 -0
  46. package/bin/lib/utils/processMetaOption.js.map +1 -0
  47. package/bin/lib/utils/projectsToText.js +25 -0
  48. package/bin/lib/utils/projectsToText.js.map +1 -0
  49. package/bin/lib/utils/promptForProject.js +43 -0
  50. package/bin/lib/utils/promptForProject.js.map +1 -0
  51. package/bin/lib/utils/quit.js +10 -0
  52. package/bin/lib/utils/quit.js.map +1 -0
  53. package/bin/lib/utils/sourcesToText.js +25 -0
  54. package/bin/lib/utils/sourcesToText.js.map +1 -0
  55. package/bin/package.json +76 -0
  56. package/bin/replace.js +107 -0
  57. package/bin/replace.js.map +1 -0
  58. package/lib/ditto.ts +54 -10
  59. package/lib/generate-suggestions.test.ts +65 -0
  60. package/lib/generate-suggestions.ts +107 -0
  61. package/lib/http/fetchComponents.ts +14 -0
  62. package/lib/replace.test.ts +101 -0
  63. package/lib/replace.ts +106 -0
  64. package/package.json +9 -2
@@ -0,0 +1,101 @@
1
+ import fs from "fs/promises";
2
+ import { parseOptions, replaceJSXTextInFile } from "./replace"; // Assuming the function is exported in a separate file
3
+
4
+ // Helper function to create a temporary file
5
+ async function createTempJSXFile(content: string): Promise<string> {
6
+ const tempFile = "tempFile.jsx";
7
+ await fs.writeFile(tempFile, content);
8
+ return tempFile;
9
+ }
10
+
11
+ // Helper function to delete the temporary file
12
+ async function deleteTempFile(filePath: string): Promise<void> {
13
+ await fs.unlink(filePath);
14
+ }
15
+
16
+ describe("parseOptions", () => {
17
+ test("should pass with valid input", async () => {
18
+ const tempFile = await createTempJSXFile("<div>Hello, world!</div>");
19
+ expect(() =>
20
+ parseOptions([tempFile, "secondString", "thirdString"])
21
+ ).not.toThrow();
22
+
23
+ const result = parseOptions([tempFile, "secondString", "thirdString"]);
24
+ expect(result).toEqual({
25
+ filePath: tempFile,
26
+ searchString: "secondString",
27
+ replaceWith: "thirdString",
28
+ });
29
+
30
+ deleteTempFile(tempFile);
31
+ });
32
+
33
+ test("should throw error when options array does not have exactly three strings", () => {
34
+ expect(() => parseOptions(["oneString"])).toThrow(
35
+ "The options array must contain <file path> <search string> <replace with>."
36
+ );
37
+ expect(() => parseOptions(["one", "two"])).toThrow(
38
+ "The options array must contain <file path> <search string> <replace with>."
39
+ );
40
+ expect(() => parseOptions(["one", "two", "three", "four"])).toThrow(
41
+ "The options array must contain <file path> <search string> <replace with>."
42
+ );
43
+ });
44
+
45
+ test("should throw error when the first string is not a valid file path", () => {
46
+ const invalidFilePath = "/path/to/invalid/file.txt";
47
+ expect(() =>
48
+ parseOptions([invalidFilePath, "secondString", "thirdString"])
49
+ ).toThrow(`${invalidFilePath} is not a valid file path.`);
50
+ });
51
+
52
+ test("should throw error when the first string is a directory", () => {
53
+ const directoryPath = ".";
54
+ expect(() =>
55
+ parseOptions([directoryPath, "secondString", "thirdString"])
56
+ ).toThrow(`${directoryPath} is not a valid file path.`);
57
+ });
58
+ });
59
+
60
+ describe("replaceJSXTextInFile", () => {
61
+ afterEach(async () => {
62
+ await deleteTempFile("tempFile.jsx");
63
+ });
64
+
65
+ test("should replace JSX text with a DittoComponent", async () => {
66
+ const tempFile = await createTempJSXFile("<div>Hello, world</div>");
67
+ const searchString = "world";
68
+ const replaceWith = "some-id";
69
+
70
+ await replaceJSXTextInFile(tempFile, { searchString, replaceWith });
71
+
72
+ const transformedCode = await fs.readFile(tempFile, "utf-8");
73
+ expect(transformedCode).toContain(
74
+ `<div>Hello, <DittoComponent componentId="${replaceWith}" /></div>`
75
+ );
76
+ });
77
+
78
+ test("should handle case-insensitive search", async () => {
79
+ const tempFile = await createTempJSXFile("<div>HeLLo, WoRlD</div>");
80
+ const searchString = "world";
81
+ const replaceWith = "some-id";
82
+
83
+ await replaceJSXTextInFile(tempFile, { searchString, replaceWith });
84
+
85
+ const transformedCode = await fs.readFile(tempFile, "utf-8");
86
+ expect(transformedCode).toContain(
87
+ `<div>HeLLo, <DittoComponent componentId="${replaceWith}" /></div>`
88
+ );
89
+ });
90
+
91
+ test("should not replace JSX text if searchString is not found", async () => {
92
+ const tempFile = await createTempJSXFile("<div>Hello, world!</div>");
93
+ const searchString = "foobar";
94
+ const replaceWith = "some-id";
95
+
96
+ await replaceJSXTextInFile(tempFile, { searchString, replaceWith });
97
+
98
+ const transformedCode = await fs.readFile(tempFile, "utf-8");
99
+ expect(transformedCode).toContain("<div>Hello, world!</div>");
100
+ });
101
+ });
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dittowords/cli",
3
- "version": "3.0.0",
3
+ "version": "3.2.2-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"