@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,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const { AutoComplete } = require("enquirer");
7
+ const output_1 = __importDefault(require("../output"));
8
+ const projectsToText_1 = require("./projectsToText");
9
+ function formatProjectChoice(project) {
10
+ return (project.name + " " + output_1.default.subtle(project.url || (0, projectsToText_1.getSourceUrl)(project.id)));
11
+ }
12
+ function parseResponse(response) {
13
+ if (!response) {
14
+ return null;
15
+ }
16
+ const [, name, id] = response.split(/^(.*)\s.*http.*\/(\w+).*$/);
17
+ if (id === "all") {
18
+ return { name, id: "ditto_component_library" };
19
+ }
20
+ return { name, id };
21
+ }
22
+ const promptForProject = async ({ message, projects, limit = 10, }) => {
23
+ output_1.default.nl();
24
+ const choices = projects.map(formatProjectChoice);
25
+ const prompt = new AutoComplete({
26
+ name: "project",
27
+ message,
28
+ limit,
29
+ choices,
30
+ });
31
+ let response;
32
+ try {
33
+ response = await prompt.run();
34
+ }
35
+ catch (e) {
36
+ // this catch handles the case where someone presses
37
+ // Ctrl + C to kill the AutoComplete process
38
+ response = null;
39
+ }
40
+ return parseResponse(response);
41
+ };
42
+ exports.default = promptForProject;
43
+ //# sourceMappingURL=promptForProject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptForProject.js","sourceRoot":"","sources":["../../../lib/utils/promptForProject.ts"],"names":[],"mappings":";;;;;AAAA,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAE7C,uDAA+B;AAE/B,qDAAgD;AAEhD,SAAS,mBAAmB,CAAC,OAAgB;IAC3C,OAAO,CACL,OAAO,CAAC,IAAI,GAAG,GAAG,GAAG,gBAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,IAAA,6BAAY,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAC5E,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,CAAC,QAAQ,EAAE;QACb,OAAO,IAAI,CAAC;KACb;IAED,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAEjE,IAAI,EAAE,KAAK,KAAK,EAAE;QAChB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,yBAAyB,EAAE,CAAC;KAChD;IAED,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AACtB,CAAC;AAQD,MAAM,gBAAgB,GAAG,KAAK,EAAE,EAC9B,OAAO,EACP,QAAQ,EACR,KAAK,GAAG,EAAE,GACU,EAAE,EAAE;IACxB,gBAAM,CAAC,EAAE,EAAE,CAAC;IAEZ,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC;QAC9B,IAAI,EAAE,SAAS;QACf,OAAO;QACP,KAAK;QACL,OAAO;KACR,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC;IAEb,IAAI;QACF,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;KAC/B;IAAC,OAAO,CAAC,EAAE;QACV,oDAAoD;QACpD,4CAA4C;QAC5C,QAAQ,GAAG,IAAI,CAAC;KACjB;IAED,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC,CAAC;AAEF,kBAAe,gBAAgB,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quit = void 0;
4
+ function quit(message, exitCode = 2) {
5
+ console.log(`\n${message}\n`);
6
+ process.exitCode = exitCode;
7
+ process.exit();
8
+ }
9
+ exports.quit = quit;
10
+ //# sourceMappingURL=quit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quit.js","sourceRoot":"","sources":["../../../lib/utils/quit.ts"],"names":[],"mappings":";;;AAAA,SAAgB,IAAI,CAAC,OAAe,EAAE,QAAQ,GAAG,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC5B,OAAO,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC;AAJD,oBAIC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const output_1 = __importDefault(require("../output"));
7
+ const projectsToText_1 = __importDefault(require("./projectsToText"));
8
+ const sourcesToText = (projects, componentLibrary) => {
9
+ let message = "";
10
+ if (componentLibrary) {
11
+ message += `the ${output_1.default.info("Ditto Component Library")}`;
12
+ if ((projects || []).length) {
13
+ message += " and ";
14
+ }
15
+ else {
16
+ message += "..";
17
+ }
18
+ }
19
+ if ((projects || []).length) {
20
+ message += `the following projects: ${(0, projectsToText_1.default)(projects)}\n`;
21
+ }
22
+ return message;
23
+ };
24
+ exports.default = sourcesToText;
25
+ //# sourceMappingURL=sourcesToText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourcesToText.js","sourceRoot":"","sources":["../../../lib/utils/sourcesToText.ts"],"names":[],"mappings":";;;;;AAAA,uDAA+B;AAC/B,sEAA8C;AAG9C,MAAM,aAAa,GAAG,CAAC,QAAmB,EAAE,gBAAyB,EAAE,EAAE;IACvE,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,IAAI,gBAAgB,EAAE;QACpB,OAAO,IAAI,OAAO,gBAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAE3D,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAC3B,OAAO,IAAI,OAAO,CAAC;SACpB;aAAM;YACL,OAAO,IAAI,IAAI,CAAC;SACjB;KACF;IAED,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAC3B,OAAO,IAAI,2BAA2B,IAAA,wBAAc,EAAC,QAAQ,CAAC,IAAI,CAAC;KACpE;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,kBAAe,aAAa,CAAC"}
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@dittowords/cli",
3
+ "version": "3.2.1-alpha",
4
+ "description": "Command Line Interface for Ditto (dittowords.com).",
5
+ "main": "bin/index.js",
6
+ "scripts": {
7
+ "prepublish": "tsc",
8
+ "prepare": "husky install",
9
+ "start": "tsc && node bin/ditto.js",
10
+ "sync": "tsc && node bin/ditto.js pull",
11
+ "dev": "tsc --watch"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/dittowords/cli.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/dittowords/cli/issues"
19
+ },
20
+ "author": "Ditto Tech Inc.",
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "ditto",
24
+ "dittowords",
25
+ "copy",
26
+ "microcopy",
27
+ "product",
28
+ "cli",
29
+ "api"
30
+ ],
31
+ "types": "bin/ditto.d.ts",
32
+ "bin": {
33
+ "ditto-cli": "bin/ditto.js"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/preset-env": "^7.20.2",
37
+ "@babel/preset-typescript": "^7.18.6",
38
+ "@tsconfig/node16": "^1.0.3",
39
+ "@types/jest": "^26.0.9",
40
+ "@types/js-yaml": "^4.0.5",
41
+ "@types/node": "^18.0.0",
42
+ "babel-jest": "^29.3.1",
43
+ "eslint": "^8.27.0",
44
+ "eslint-config-airbnb-base": "^14.2.0",
45
+ "husky": "^7.0.4",
46
+ "jest": "^29.3.1",
47
+ "lint-staged": "^11.2.4",
48
+ "prettier": "2.4.1",
49
+ "rewire": "^6.0.0",
50
+ "source-map": "^0.7.3",
51
+ "tempy": "^0.6.0",
52
+ "typescript": "^4.7.4"
53
+ },
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",
61
+ "axios": "^0.27.2",
62
+ "boxen": "^5.1.2",
63
+ "chalk": "^4.1.0",
64
+ "commander": "^6.1.0",
65
+ "enquirer": "^2.3.6",
66
+ "faker": "^5.1.0",
67
+ "fs-extra": "^11.1.1",
68
+ "glob": "^9.3.4",
69
+ "js-yaml": "^4.1.0",
70
+ "ora": "^5.0.0",
71
+ "v8-compile-cache": "^2.1.1"
72
+ },
73
+ "lint-staged": {
74
+ "src/**/*.{js,jsx,ts,tsx,css,json}": "prettier --write"
75
+ }
76
+ }
package/bin/replace.js ADDED
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.replaceJSXTextInFile = exports.parseOptions = exports.replace = void 0;
30
+ const fs_extra_1 = __importDefault(require("fs-extra"));
31
+ const parser_1 = require("@babel/parser");
32
+ const traverse_1 = __importDefault(require("@babel/traverse"));
33
+ const t = __importStar(require("@babel/types"));
34
+ const core_1 = require("@babel/core");
35
+ async function replaceJSXTextInFile(filePath, replacement) {
36
+ const code = await fs_extra_1.default.readFile(filePath, "utf-8");
37
+ const ast = (0, parser_1.parse)(code, {
38
+ sourceType: "module",
39
+ plugins: ["jsx", "typescript"],
40
+ });
41
+ (0, traverse_1.default)(ast, {
42
+ JSXText(path) {
43
+ const { searchString, replaceWith } = replacement;
44
+ const regex = new RegExp(searchString, "gi");
45
+ if (regex.test(path.node.value)) {
46
+ const splitValues = splitByCaseInsensitive(path.node.value, searchString);
47
+ const nodes = [];
48
+ splitValues.forEach((splitValue) => {
49
+ if (splitValue.toLowerCase() === searchString.toLowerCase()) {
50
+ const identifier = t.jsxIdentifier("DittoComponent");
51
+ const componentId = t.jsxAttribute(t.jsxIdentifier("componentId"), t.stringLiteral(replaceWith));
52
+ const o = t.jsxOpeningElement(identifier, [componentId], true);
53
+ const jsxElement = t.jsxElement(o, undefined, [], true);
54
+ nodes.push(jsxElement);
55
+ }
56
+ else {
57
+ nodes.push(t.jsxText(splitValue));
58
+ }
59
+ });
60
+ path.replaceWithMultiple(nodes);
61
+ }
62
+ },
63
+ });
64
+ // transfromFromAst types are wrong?
65
+ /* @ts-ignore */
66
+ const { code: transformedCode } = (0, core_1.transformFromAst)(ast, code, {
67
+ // Don't let this codebase's Babel config affect the code we're transforming.
68
+ configFile: false,
69
+ });
70
+ fs_extra_1.default.writeFile(filePath, transformedCode);
71
+ }
72
+ exports.replaceJSXTextInFile = replaceJSXTextInFile;
73
+ function splitByCaseInsensitive(str, delimiter) {
74
+ return str.split(new RegExp(`(${delimiter})`, "gi")).filter((s) => s !== "");
75
+ }
76
+ function replace(options) {
77
+ let filePath;
78
+ let searchString;
79
+ let replaceWith;
80
+ try {
81
+ const parsedOptions = parseOptions(options);
82
+ filePath = parsedOptions.filePath;
83
+ searchString = parsedOptions.searchString;
84
+ replaceWith = parsedOptions.replaceWith;
85
+ }
86
+ catch (e) {
87
+ console.error(e);
88
+ console.error("Usage for replace: ditto-cli replace <file path> <search string> <replace with>");
89
+ return;
90
+ }
91
+ replaceJSXTextInFile(filePath, { searchString, replaceWith });
92
+ }
93
+ exports.replace = replace;
94
+ function parseOptions(options) {
95
+ if (options.length !== 3) {
96
+ throw new Error("The options array must contain <file path> <search string> <replace with>.");
97
+ }
98
+ const filePath = options[0];
99
+ // Check if the file path exists and points to a regular file (not a directory or other file system object).
100
+ const isFilePathValid = fs_extra_1.default.existsSync(filePath) && fs_extra_1.default.lstatSync(filePath).isFile();
101
+ if (!isFilePathValid) {
102
+ throw new Error(`${filePath} is not a valid file path.`);
103
+ }
104
+ return { filePath, searchString: options[1], replaceWith: options[2] };
105
+ }
106
+ exports.parseOptions = parseOptions;
107
+ //# sourceMappingURL=replace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replace.js","sourceRoot":"","sources":["../lib/replace.ts"],"names":[],"mappings":"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"}
package/lib/ditto.ts CHANGED
@@ -3,16 +3,37 @@
3
3
  import { program } from "commander";
4
4
  // to use V8's code cache to speed up instantiation time
5
5
  import "v8-compile-cache";
6
+ import fs from "fs";
7
+ import path from "path";
6
8
 
7
9
  import { init, needsTokenOrSource } from "./init/init";
8
10
  import { pull } from "./pull";
9
11
  import { quit } from "./utils/quit";
10
12
  import addProject from "./add-project";
11
13
  import removeProject from "./remove-project";
14
+ import { replace } from "./replace";
15
+ import { generateSuggestions } from "./generate-suggestions";
12
16
 
13
17
  import processMetaOption from "./utils/processMetaOption";
14
18
 
15
- type Command = "pull" | "project" | "project add" | "project remove";
19
+ function getVersion(): string {
20
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
21
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
22
+ const packageJson = JSON.parse(packageJsonContent) as { version: string };
23
+ const version = packageJson.version;
24
+
25
+ return version;
26
+ }
27
+
28
+ const VERSION = getVersion();
29
+
30
+ type Command =
31
+ | "pull"
32
+ | "project"
33
+ | "project add"
34
+ | "project remove"
35
+ | "generate-suggestions"
36
+ | "replace";
16
37
 
17
38
  const COMMANDS = [
18
39
  {
@@ -33,23 +54,36 @@ const COMMANDS = [
33
54
  },
34
55
  ],
35
56
  },
57
+ {
58
+ name: "generate-suggestions",
59
+ description: "Find text that can be potentially replaced with Ditto text",
60
+ },
61
+ {
62
+ name: "replace",
63
+ description: "Find and replace Ditto text with code",
64
+ },
36
65
  ] as const;
37
66
 
38
67
  const setupCommands = () => {
39
68
  program.name("ditto-cli");
40
69
 
41
- COMMANDS.forEach((config) => {
70
+ COMMANDS.forEach((commandConfig) => {
42
71
  const cmd = program
43
- .command(config.name)
44
- .description(config.description)
45
- .action(() => executeCommand(config.name));
72
+ .command(commandConfig.name)
73
+ .description(commandConfig.description)
74
+ .action((str, options) => executeCommand(commandConfig.name, options));
46
75
 
47
- if ("commands" in config) {
48
- config.commands.forEach((nestedCommand) => {
76
+ if ("commands" in commandConfig) {
77
+ commandConfig.commands.forEach((nestedCommand) => {
49
78
  cmd
50
79
  .command(nestedCommand.name)
51
80
  .description(nestedCommand.description)
52
- .action(() => executeCommand(`${config.name} ${nestedCommand.name}`));
81
+ .action((str, options) =>
82
+ executeCommand(
83
+ `${commandConfig.name} ${nestedCommand.name}`,
84
+ options
85
+ )
86
+ );
53
87
  });
54
88
  }
55
89
  });
@@ -60,9 +94,13 @@ const setupOptions = () => {
60
94
  "-m, --meta <data...>",
61
95
  "Include arbitrary data in requests to the Ditto API. Ex: -m githubActionRequest:true trigger:manual"
62
96
  );
97
+ program.version(VERSION, "-v, --version", "Output the current version");
63
98
  };
64
99
 
65
- const executeCommand = async (command: Command | "none"): Promise<void> => {
100
+ const executeCommand = async (
101
+ command: Command | "none",
102
+ options: string[]
103
+ ): Promise<void> => {
66
104
  const needsInitialization = needsTokenOrSource();
67
105
  if (needsInitialization) {
68
106
  try {
@@ -91,6 +129,12 @@ const executeCommand = async (command: Command | "none"): Promise<void> => {
91
129
  case "project remove": {
92
130
  return removeProject();
93
131
  }
132
+ case "generate-suggestions": {
133
+ return generateSuggestions();
134
+ }
135
+ case "replace": {
136
+ return replace(options);
137
+ }
94
138
  default: {
95
139
  quit("Exiting Ditto CLI...");
96
140
  return;
@@ -103,7 +147,7 @@ const main = async () => {
103
147
  setupOptions();
104
148
 
105
149
  if (process.argv.length <= 2 && process.argv[1].includes("ditto-cli")) {
106
- await executeCommand("none");
150
+ await executeCommand("none", []);
107
151
  return;
108
152
  }
109
153
 
@@ -0,0 +1,65 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { findTextInJSXFiles } from "./generate-suggestions";
4
+
5
+ describe("findTextInJSXFiles", () => {
6
+ async function createTempFile(filename, content) {
7
+ const filePath = path.join(".", filename);
8
+ await fs.writeFile(filePath, content);
9
+ return filePath;
10
+ }
11
+
12
+ async function deleteTempFile(filename) {
13
+ const filePath = path.join(".", filename);
14
+ await fs.unlink(filePath);
15
+ }
16
+
17
+ it("should return an empty array when no files are found", async () => {
18
+ const result = await findTextInJSXFiles(".", "searchString");
19
+
20
+ expect(result).toEqual([]);
21
+ });
22
+
23
+ it("should return an empty array when searchString is not found in any file", async () => {
24
+ const file1 = await createTempFile("file1.jsx", "<div>No match</div>");
25
+ const file2 = await createTempFile("file2.tsx", "<div>No match</div>");
26
+
27
+ const result = await findTextInJSXFiles(".", "searchString");
28
+
29
+ expect(result).toEqual([]);
30
+
31
+ await deleteTempFile(file1);
32
+ await deleteTempFile(file2);
33
+ });
34
+
35
+ it("should return an array with correct occurrences when searchString is found", async () => {
36
+ const file1 = await createTempFile(
37
+ "file1.jsx",
38
+ `<div>Test searchString and another searchString</div>`
39
+ );
40
+
41
+ const expectedResult = [
42
+ {
43
+ file: file1,
44
+ occurrences: [
45
+ {
46
+ lineNumber: 1,
47
+ preview:
48
+ "<div>Test {{searchString}} and another searchString</div>",
49
+ },
50
+ {
51
+ lineNumber: 1,
52
+ preview:
53
+ "<div>Test searchString and another {{searchString}}</div>",
54
+ },
55
+ ],
56
+ },
57
+ ];
58
+
59
+ const result = await findTextInJSXFiles(".", "searchString");
60
+
61
+ expect(result).toEqual(expectedResult);
62
+
63
+ await deleteTempFile(file1);
64
+ });
65
+ });
@@ -0,0 +1,107 @@
1
+ import fs from "fs-extra";
2
+ import glob from "glob";
3
+ import { parse } from "@babel/parser";
4
+ import traverse from "@babel/traverse";
5
+
6
+ import { fetchComponents } from "./http/fetchComponents";
7
+
8
+ async function generateSuggestions() {
9
+ const components = await fetchComponents();
10
+ const results: {
11
+ [compApiId: string]: FindResults;
12
+ } = {};
13
+
14
+ for (const [compApiId, component] of Object.entries(components)) {
15
+ if (!results[compApiId]) {
16
+ results[compApiId] = [];
17
+ }
18
+ const result = await findTextInJSXFiles(".", component.text);
19
+ results[compApiId] = [...results[compApiId], ...result];
20
+ }
21
+
22
+ // Display results to user
23
+ console.log(JSON.stringify(results, null, 2));
24
+ }
25
+
26
+ interface Occurence {
27
+ lineNumber: number;
28
+ preview: string;
29
+ }
30
+
31
+ type FindResults = { file: string; occurrences: Occurence[] }[];
32
+
33
+ async function findTextInJSXFiles(
34
+ path: string,
35
+ searchString: string
36
+ ): Promise<FindResults> {
37
+ const result: { file: string; occurrences: Occurence[] }[] = [];
38
+ const files = glob.sync(`${path}/**/*.+(jsx|tsx)`, {
39
+ ignore: "**/node_modules/**",
40
+ });
41
+
42
+ const promises: Promise<any>[] = [];
43
+
44
+ for (const file of files) {
45
+ promises.push(
46
+ fs.readFile(file, "utf-8").then((code) => {
47
+ const ast = parse(code, {
48
+ sourceType: "module",
49
+ plugins: ["jsx", "typescript"],
50
+ });
51
+
52
+ const occurrences: Occurence[] = [];
53
+
54
+ traverse(ast, {
55
+ JSXText(path) {
56
+ if (path.node.value.includes(searchString)) {
57
+ const regex = new RegExp(searchString, "g");
58
+ let match;
59
+ while ((match = regex.exec(path.node.value)) !== null) {
60
+ const lines = path.node.value.slice(0, match.index).split("\n");
61
+
62
+ if (!path.node.loc) {
63
+ continue;
64
+ }
65
+
66
+ const lineNumber = path.node.loc.start.line + lines.length - 1;
67
+
68
+ const codeLines = code.split("\n");
69
+ const line = codeLines[lineNumber - 1];
70
+ const preview = replaceAt(
71
+ line,
72
+ match.index,
73
+ searchString,
74
+ `{{${searchString}}}`
75
+ );
76
+
77
+ occurrences.push({ lineNumber, preview });
78
+ }
79
+ }
80
+ },
81
+ });
82
+
83
+ if (occurrences.length > 0) {
84
+ result.push({ file, occurrences });
85
+ }
86
+ })
87
+ );
88
+ }
89
+
90
+ await Promise.all(promises);
91
+
92
+ return result;
93
+ }
94
+
95
+ function replaceAt(
96
+ str: string,
97
+ index: number,
98
+ searchString: string,
99
+ replacement: string
100
+ ) {
101
+ return (
102
+ str.substring(0, index) +
103
+ str.substring(index, str.length).replace(searchString, replacement)
104
+ );
105
+ }
106
+
107
+ export { findTextInJSXFiles, generateSuggestions };
@@ -0,0 +1,14 @@
1
+ import api from "../api";
2
+
3
+ export async function fetchComponents() {
4
+ const { data } = await api.get<{
5
+ [compApiId: string]: {
6
+ name: string;
7
+ text: string;
8
+ status: "NONE" | "WIP" | "REVIEW" | "FINAL";
9
+ folder: "string" | null;
10
+ };
11
+ }>("/components", {});
12
+
13
+ return data;
14
+ }