@dittowords/cli 3.0.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/bin/ditto.js CHANGED
@@ -13,6 +13,8 @@ const pull_1 = require("./pull");
13
13
  const quit_1 = require("./utils/quit");
14
14
  const add_project_1 = __importDefault(require("./add-project"));
15
15
  const remove_project_1 = __importDefault(require("./remove-project"));
16
+ const replace_1 = require("./replace");
17
+ const generate_suggestions_1 = require("./generate-suggestions");
16
18
  const processMetaOption_1 = __importDefault(require("./utils/processMetaOption"));
17
19
  const COMMANDS = [
18
20
  {
@@ -33,20 +35,28 @@ const COMMANDS = [
33
35
  },
34
36
  ],
35
37
  },
38
+ {
39
+ name: "generate-suggestions",
40
+ description: "Find text that can be potentially replaced with Ditto text",
41
+ },
42
+ {
43
+ name: "replace",
44
+ description: "Find and replace Ditto text with code",
45
+ },
36
46
  ];
37
47
  const setupCommands = () => {
38
48
  commander_1.program.name("ditto-cli");
39
- COMMANDS.forEach((config) => {
49
+ COMMANDS.forEach((commandConfig) => {
40
50
  const cmd = commander_1.program
41
- .command(config.name)
42
- .description(config.description)
43
- .action(() => executeCommand(config.name));
44
- if ("commands" in config) {
45
- config.commands.forEach((nestedCommand) => {
51
+ .command(commandConfig.name)
52
+ .description(commandConfig.description)
53
+ .action((str, options) => executeCommand(commandConfig.name, options));
54
+ if ("commands" in commandConfig) {
55
+ commandConfig.commands.forEach((nestedCommand) => {
46
56
  cmd
47
57
  .command(nestedCommand.name)
48
58
  .description(nestedCommand.description)
49
- .action(() => executeCommand(`${config.name} ${nestedCommand.name}`));
59
+ .action((str, options) => executeCommand(`${commandConfig.name} ${nestedCommand.name}`, options));
50
60
  });
51
61
  }
52
62
  });
@@ -54,7 +64,7 @@ const setupCommands = () => {
54
64
  const setupOptions = () => {
55
65
  commander_1.program.option("-m, --meta <data...>", "Include arbitrary data in requests to the Ditto API. Ex: -m githubActionRequest:true trigger:manual");
56
66
  };
57
- const executeCommand = async (command) => {
67
+ const executeCommand = async (command, options) => {
58
68
  const needsInitialization = (0, init_1.needsTokenOrSource)();
59
69
  if (needsInitialization) {
60
70
  try {
@@ -83,6 +93,12 @@ const executeCommand = async (command) => {
83
93
  case "project remove": {
84
94
  return (0, remove_project_1.default)();
85
95
  }
96
+ case "generate-suggestions": {
97
+ return (0, generate_suggestions_1.generateSuggestions)();
98
+ }
99
+ case "replace": {
100
+ return (0, replace_1.replace)(options);
101
+ }
86
102
  default: {
87
103
  (0, quit_1.quit)("Exiting Ditto CLI...");
88
104
  return;
@@ -93,7 +109,7 @@ const main = async () => {
93
109
  setupCommands();
94
110
  setupOptions();
95
111
  if (process.argv.length <= 2 && process.argv[1].includes("ditto-cli")) {
96
- await executeCommand("none");
112
+ await executeCommand("none", []);
97
113
  return;
98
114
  }
99
115
  commander_1.program.parse(process.argv);
package/bin/ditto.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ditto.js","sourceRoot":"","sources":["../lib/ditto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ditto.js","sourceRoot":"","sources":["../lib/ditto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
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
+ exports.generateSuggestions = exports.findTextInJSXFiles = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const glob_1 = __importDefault(require("glob"));
9
+ const parser_1 = require("@babel/parser");
10
+ const traverse_1 = __importDefault(require("@babel/traverse"));
11
+ const fetchComponents_1 = require("./http/fetchComponents");
12
+ async function generateSuggestions() {
13
+ const components = await (0, fetchComponents_1.fetchComponents)();
14
+ const results = {};
15
+ for (const [compApiId, component] of Object.entries(components)) {
16
+ if (!results[compApiId]) {
17
+ results[compApiId] = [];
18
+ }
19
+ const result = await findTextInJSXFiles(".", component.text);
20
+ results[compApiId] = [...results[compApiId], ...result];
21
+ }
22
+ // Display results to user
23
+ console.log(JSON.stringify(results, null, 2));
24
+ }
25
+ exports.generateSuggestions = generateSuggestions;
26
+ async function findTextInJSXFiles(path, searchString) {
27
+ const result = [];
28
+ const files = glob_1.default.sync(`${path}/**/*.+(jsx|tsx)`, {
29
+ ignore: "**/node_modules/**",
30
+ });
31
+ const promises = [];
32
+ for (const file of files) {
33
+ promises.push(fs_extra_1.default.readFile(file, "utf-8").then((code) => {
34
+ const ast = (0, parser_1.parse)(code, {
35
+ sourceType: "module",
36
+ plugins: ["jsx", "typescript"],
37
+ });
38
+ const occurrences = [];
39
+ (0, traverse_1.default)(ast, {
40
+ JSXText(path) {
41
+ if (path.node.value.includes(searchString)) {
42
+ const regex = new RegExp(searchString, "g");
43
+ let match;
44
+ while ((match = regex.exec(path.node.value)) !== null) {
45
+ const lines = path.node.value.slice(0, match.index).split("\n");
46
+ if (!path.node.loc) {
47
+ continue;
48
+ }
49
+ const lineNumber = path.node.loc.start.line + lines.length - 1;
50
+ const codeLines = code.split("\n");
51
+ const line = codeLines[lineNumber - 1];
52
+ const preview = replaceAt(line, match.index, searchString, `{{${searchString}}}`);
53
+ occurrences.push({ lineNumber, preview });
54
+ }
55
+ }
56
+ },
57
+ });
58
+ if (occurrences.length > 0) {
59
+ result.push({ file, occurrences });
60
+ }
61
+ }));
62
+ }
63
+ await Promise.all(promises);
64
+ return result;
65
+ }
66
+ exports.findTextInJSXFiles = findTextInJSXFiles;
67
+ function replaceAt(str, index, searchString, replacement) {
68
+ return (str.substring(0, index) +
69
+ str.substring(index, str.length).replace(searchString, replacement));
70
+ }
71
+ //# sourceMappingURL=generate-suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-suggestions.js","sourceRoot":"","sources":["../lib/generate-suggestions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
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
+ exports.fetchComponents = void 0;
7
+ const api_1 = __importDefault(require("../api"));
8
+ async function fetchComponents() {
9
+ const { data } = await api_1.default.get("/components", {});
10
+ return data;
11
+ }
12
+ exports.fetchComponents = fetchComponents;
13
+ //# sourceMappingURL=fetchComponents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchComponents.js","sourceRoot":"","sources":["../../lib/http/fetchComponents.ts"],"names":[],"mappings":";;;;;;AAAA,iDAAyB;AAElB,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,aAAG,CAAC,GAAG,CAO3B,aAAa,EAAE,EAAE,CAAC,CAAC;IAEtB,OAAO,IAAI,CAAC;AACd,CAAC;AAXD,0CAWC"}
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
@@ -4,15 +4,27 @@ import { program } from "commander";
4
4
  // to use V8's code cache to speed up instantiation time
5
5
  import "v8-compile-cache";
6
6
 
7
+ import packageJson from "../package.json";
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
+ const VERSION = packageJson.version;
20
+
21
+ type Command =
22
+ | "pull"
23
+ | "project"
24
+ | "project add"
25
+ | "project remove"
26
+ | "generate-suggestions"
27
+ | "replace";
16
28
 
17
29
  const COMMANDS = [
18
30
  {
@@ -33,23 +45,36 @@ const COMMANDS = [
33
45
  },
34
46
  ],
35
47
  },
48
+ {
49
+ name: "generate-suggestions",
50
+ description: "Find text that can be potentially replaced with Ditto text",
51
+ },
52
+ {
53
+ name: "replace",
54
+ description: "Find and replace Ditto text with code",
55
+ },
36
56
  ] as const;
37
57
 
38
58
  const setupCommands = () => {
39
59
  program.name("ditto-cli");
40
60
 
41
- COMMANDS.forEach((config) => {
61
+ COMMANDS.forEach((commandConfig) => {
42
62
  const cmd = program
43
- .command(config.name)
44
- .description(config.description)
45
- .action(() => executeCommand(config.name));
63
+ .command(commandConfig.name)
64
+ .description(commandConfig.description)
65
+ .action((str, options) => executeCommand(commandConfig.name, options));
46
66
 
47
- if ("commands" in config) {
48
- config.commands.forEach((nestedCommand) => {
67
+ if ("commands" in commandConfig) {
68
+ commandConfig.commands.forEach((nestedCommand) => {
49
69
  cmd
50
70
  .command(nestedCommand.name)
51
71
  .description(nestedCommand.description)
52
- .action(() => executeCommand(`${config.name} ${nestedCommand.name}`));
72
+ .action((str, options) =>
73
+ executeCommand(
74
+ `${commandConfig.name} ${nestedCommand.name}`,
75
+ options
76
+ )
77
+ );
53
78
  });
54
79
  }
55
80
  });
@@ -60,9 +85,13 @@ const setupOptions = () => {
60
85
  "-m, --meta <data...>",
61
86
  "Include arbitrary data in requests to the Ditto API. Ex: -m githubActionRequest:true trigger:manual"
62
87
  );
88
+ program.version(VERSION, "-v, --version", "Output the current version");
63
89
  };
64
90
 
65
- const executeCommand = async (command: Command | "none"): Promise<void> => {
91
+ const executeCommand = async (
92
+ command: Command | "none",
93
+ options: string[]
94
+ ): Promise<void> => {
66
95
  const needsInitialization = needsTokenOrSource();
67
96
  if (needsInitialization) {
68
97
  try {
@@ -91,6 +120,12 @@ const executeCommand = async (command: Command | "none"): Promise<void> => {
91
120
  case "project remove": {
92
121
  return removeProject();
93
122
  }
123
+ case "generate-suggestions": {
124
+ return generateSuggestions();
125
+ }
126
+ case "replace": {
127
+ return replace(options);
128
+ }
94
129
  default: {
95
130
  quit("Exiting Ditto CLI...");
96
131
  return;
@@ -103,7 +138,7 @@ const main = async () => {
103
138
  setupOptions();
104
139
 
105
140
  if (process.argv.length <= 2 && process.argv[1].includes("ditto-cli")) {
106
- await executeCommand("none");
141
+ await executeCommand("none", []);
107
142
  return;
108
143
  }
109
144
 
@@ -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
+ }
@@ -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.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"