@reliverse/dler 1.5.5 → 1.5.6
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 +29 -18
- package/bin/app/migrate/cmd.d.ts +12 -0
- package/bin/app/migrate/cmd.js +92 -0
- package/bin/app/migrate/codemods/lib-pathe-pathkit.d.ts +9 -0
- package/bin/app/migrate/codemods/lib-pathe-pathkit.js +258 -0
- package/bin/app/migrate/codemods/ts-module-resolution.d.ts +10 -0
- package/bin/app/migrate/codemods/ts-module-resolution.js +231 -0
- package/bin/app/relifso/rename/cmd.js +0 -1
- package/bin/init/info.js +1 -1
- package/bin/libs/sdk/sdk-impl/spell/spell-executors.d.ts +5 -0
- package/bin/libs/sdk/sdk-impl/spell/spell-executors.js +287 -0
- package/bin/libs/sdk/sdk-impl/spell/spell-parser.js +2 -2
- package/bin/libs/sdk/sdk-impl/spell/spell-types.d.ts +27 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -295,42 +295,53 @@ dler rempts init --cmds
|
|
|
295
295
|
**available spell types:**
|
|
296
296
|
|
|
297
297
|
- `replace-line` — injects contents from one file into another
|
|
298
|
+
- `replace-range` — replaces a range of lines with content from another file
|
|
298
299
|
- `rename-file` — renames the current file
|
|
299
300
|
- `remove-comment` — removes a specific comment from code
|
|
300
301
|
- `remove-line` — removes a specific line from code
|
|
301
302
|
- `remove-file` — deletes the current file
|
|
302
303
|
- `transform-content` — applies a transformation to the file content
|
|
304
|
+
- `transform-line` — applies a transformation to a specific line
|
|
303
305
|
- `copy-file` — copies the current file to a new location
|
|
304
306
|
- `move-file` — moves the current file to a new location
|
|
305
307
|
- `insert-at` — inserts content at a specific position in the file
|
|
308
|
+
- `insert-before` — inserts content before a specific line
|
|
309
|
+
- `insert-after` — inserts content after a specific line
|
|
310
|
+
- `conditional-execute` — executes spells conditionally
|
|
306
311
|
|
|
307
312
|
**params:**
|
|
308
313
|
|
|
309
314
|
params are optional and comma-separated.
|
|
310
315
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
- `hooked` (boolean, default: `false`)
|
|
314
|
-
- `false`: dler handles the spell automatically at postbuild
|
|
316
|
+
- `hooked` (boolean, default: `true`)
|
|
315
317
|
- `true`: disables default behavior, so you can trigger the spell yourself (e.g. from your own cli function)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
+
- `false`: dler handles the spell automatically at postbuild
|
|
319
|
+
- `startLine` (number) — line number to start the operation (for range operations)
|
|
320
|
+
- `endLine` (number) — line number to end the operation (for range operations)
|
|
321
|
+
- `condition` (string) — condition to check before executing the spell
|
|
322
|
+
- `skipIfMissing` (boolean) — whether to skip the spell if the target file doesn't exist
|
|
323
|
+
- `createDir` (boolean) — whether to create the target directory if it doesn't exist
|
|
318
324
|
|
|
319
325
|
**usage examples:**
|
|
320
326
|
|
|
321
|
-
- `export * from "../../types.js"; //
|
|
322
|
-
- `// @ts-expect-error
|
|
323
|
-
- `//
|
|
324
|
-
- `//
|
|
325
|
-
- `//
|
|
327
|
+
- `export * from "../../types.js"; // dler-replace-line` — injects file contents at this line (hooked=true by default)
|
|
328
|
+
- `// @ts-expect-error dler-remove-comment` — removes just this comment (hooked=true by default)
|
|
329
|
+
- `// dler-remove-line` — removes this line (hooked=true by default)
|
|
330
|
+
- `// dler-remove-file` — deletes this file (hooked=true by default)
|
|
331
|
+
- `// dler-rename-file-"tsconfig.json"-{hooked=false}` — renames this file (runs at postbuild because `hooked=false`)
|
|
332
|
+
- `// dler-replace-range-"../../types.js"-{startLine=1,endLine=5}` — replaces lines 1-5 with content from types.js
|
|
333
|
+
- `// dler-transform-line-"return line.toUpperCase()"` — transforms the line to uppercase
|
|
334
|
+
- `// dler-insert-before-"import { x } from 'y';"` — inserts import statement before this line
|
|
335
|
+
- `// dler-insert-after-"export { x };"` — inserts export statement after this line
|
|
336
|
+
- `// dler-conditional-execute-{condition="content.includes('TODO')"}` — executes spells only if file contains TODO
|
|
326
337
|
|
|
327
|
-
**using `hooked=
|
|
338
|
+
**using `hooked=false`:**
|
|
328
339
|
|
|
329
|
-
- `//
|
|
340
|
+
- `// dler-rename-file-"tsconfig.json"-{hooked=false}` — renames the file immediately at postbuild (not hooked)
|
|
330
341
|
|
|
331
342
|
**triggering spells:**
|
|
332
343
|
|
|
333
|
-
from dler
|
|
344
|
+
from dler's cli:
|
|
334
345
|
|
|
335
346
|
- `dler spell --trigger rename-file,... --files tsconfig.json,...`
|
|
336
347
|
- `dler spell --trigger all`
|
|
@@ -340,10 +351,10 @@ from your own code:
|
|
|
340
351
|
|
|
341
352
|
```ts
|
|
342
353
|
await dler.spell({ spells: ["rename-file"], files: [] });
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
354
|
+
await dler.spell({}) // all spells, all files
|
|
355
|
+
spells: ["all"] // means all spells
|
|
356
|
+
spells: [] // also means all spells
|
|
357
|
+
files: [] // means all files
|
|
347
358
|
```
|
|
348
359
|
|
|
349
360
|
p.s. [see how rse cli uses hooked=true](https://github.com/reliverse/rse/blob/main/src/postbuild.ts)
|
package/bin/app/migrate/cmd.d.ts
CHANGED
package/bin/app/migrate/cmd.js
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { defineArgs, defineCommand } from "@reliverse/rempts";
|
|
2
|
+
import {
|
|
3
|
+
migratePatheToPathkit,
|
|
4
|
+
migratePathkitToPathe
|
|
5
|
+
} from "./codemods/lib-pathe-pathkit.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "migrate",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "Migrate between different libraries and usages"
|
|
11
|
+
},
|
|
12
|
+
args: defineArgs({
|
|
13
|
+
lib: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "The migration to perform (pathe-to-pathkit | pathkit-to-pathe)"
|
|
16
|
+
},
|
|
17
|
+
dryRun: {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
description: "Preview changes without applying them",
|
|
20
|
+
default: false
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
23
|
+
async run({ args }) {
|
|
24
|
+
let results = [];
|
|
25
|
+
if (args.lib === "pathe-to-pathkit") {
|
|
26
|
+
console.log("Migrating from pathe to pathkit...");
|
|
27
|
+
results = await migratePatheToPathkit(args.dryRun);
|
|
28
|
+
} else if (args.lib === "pathkit-to-pathe") {
|
|
29
|
+
console.log("Migrating from pathkit to pathe...");
|
|
30
|
+
results = await migratePathkitToPathe(args.dryRun);
|
|
31
|
+
} else {
|
|
32
|
+
console.error(`Unknown migration: ${args.lib}`);
|
|
33
|
+
console.log("Available migrations:");
|
|
34
|
+
console.log(" - pathe-to-pathkit");
|
|
35
|
+
console.log(" - pathkit-to-pathe");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log("\nMigration Results:");
|
|
39
|
+
let successCount = 0;
|
|
40
|
+
let errorCount = 0;
|
|
41
|
+
let warningCount = 0;
|
|
42
|
+
for (const result of results) {
|
|
43
|
+
const status = result.success ? "\u2713" : "\u2717";
|
|
44
|
+
console.log(`${status} ${result.file}: ${result.message}`);
|
|
45
|
+
if (result.changes && result.changes.length > 0) {
|
|
46
|
+
for (const change of result.changes) {
|
|
47
|
+
if (change.startsWith("\u26A0\uFE0F")) {
|
|
48
|
+
console.log(` ${change}`);
|
|
49
|
+
warningCount++;
|
|
50
|
+
} else {
|
|
51
|
+
console.log(` - ${change}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (result.success) successCount++;
|
|
56
|
+
else errorCount++;
|
|
57
|
+
}
|
|
58
|
+
console.log(
|
|
59
|
+
`
|
|
60
|
+
Summary: ${successCount} files updated, ${errorCount} errors, ${warningCount} warnings`
|
|
61
|
+
);
|
|
62
|
+
if (args.dryRun) {
|
|
63
|
+
console.log("\nThis was a dry run. No changes were made.");
|
|
64
|
+
console.log("Run without --dryRun to apply the changes.");
|
|
65
|
+
} else {
|
|
66
|
+
console.log("\nMigration completed!");
|
|
67
|
+
if (args.lib === "pathe-to-pathkit") {
|
|
68
|
+
console.log("Next steps:");
|
|
69
|
+
console.log("1. Run 'bun install' to install @reliverse/pathkit");
|
|
70
|
+
console.log("2. Test your application");
|
|
71
|
+
console.log(
|
|
72
|
+
"3. Consider using advanced pathkit features like alias resolution"
|
|
73
|
+
);
|
|
74
|
+
} else if (args.lib === "pathkit-to-pathe") {
|
|
75
|
+
console.log("Next steps:");
|
|
76
|
+
console.log("1. Run 'bun install' to install pathe");
|
|
77
|
+
console.log("2. Test your application");
|
|
78
|
+
if (warningCount > 0) {
|
|
79
|
+
console.log(
|
|
80
|
+
"3. \u26A0\uFE0F Review files with warnings - they may need manual updates"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
console.error(`Unknown migration: ${args.lib}`);
|
|
85
|
+
console.log("Available migrations:");
|
|
86
|
+
console.log(" - pathe-to-pathkit");
|
|
87
|
+
console.log(" - pathkit-to-pathe");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type MigrationResult = {
|
|
2
|
+
file: string;
|
|
3
|
+
success: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
changes?: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function migratePatheToPathkit(dryRun?: boolean): Promise<MigrationResult[]>;
|
|
8
|
+
export declare function migratePathkitToPathe(dryRun?: boolean): Promise<MigrationResult[]>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
3
|
+
import { join, extname } from "node:path";
|
|
4
|
+
async function getAllTsFiles(dir) {
|
|
5
|
+
const files = [];
|
|
6
|
+
try {
|
|
7
|
+
const entries = await readdir(dir);
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const fullPath = join(dir, entry);
|
|
10
|
+
const stats = await stat(fullPath);
|
|
11
|
+
if (stats.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
12
|
+
const subFiles = await getAllTsFiles(fullPath);
|
|
13
|
+
files.push(...subFiles);
|
|
14
|
+
} else if (stats.isFile()) {
|
|
15
|
+
const ext = extname(entry);
|
|
16
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"].includes(ext)) {
|
|
17
|
+
files.push(fullPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
export async function migratePatheToPathkit(dryRun = false) {
|
|
26
|
+
const results = [];
|
|
27
|
+
const files = await getAllTsFiles(".");
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
try {
|
|
30
|
+
const content = await readFile(file, "utf-8");
|
|
31
|
+
let modified = content;
|
|
32
|
+
const changes = [];
|
|
33
|
+
const patheImportRegex = /from\s+["']pathe["']/g;
|
|
34
|
+
if (patheImportRegex.test(content)) {
|
|
35
|
+
modified = modified.replace(
|
|
36
|
+
patheImportRegex,
|
|
37
|
+
'from "@reliverse/pathkit"'
|
|
38
|
+
);
|
|
39
|
+
changes.push("Updated pathe imports to @reliverse/pathkit");
|
|
40
|
+
}
|
|
41
|
+
const patheUtilsRegex = /from\s+["']pathe\/utils["']/g;
|
|
42
|
+
if (patheUtilsRegex.test(content)) {
|
|
43
|
+
modified = modified.replace(
|
|
44
|
+
patheUtilsRegex,
|
|
45
|
+
'from "@reliverse/pathkit"'
|
|
46
|
+
);
|
|
47
|
+
changes.push("Updated pathe/utils imports to @reliverse/pathkit");
|
|
48
|
+
}
|
|
49
|
+
const patheRequireRegex = /require\s*\(\s*["']pathe["']\s*\)/g;
|
|
50
|
+
if (patheRequireRegex.test(content)) {
|
|
51
|
+
modified = modified.replace(
|
|
52
|
+
patheRequireRegex,
|
|
53
|
+
'require("@reliverse/pathkit")'
|
|
54
|
+
);
|
|
55
|
+
changes.push("Updated pathe require to @reliverse/pathkit");
|
|
56
|
+
}
|
|
57
|
+
const patheUtilsRequireRegex = /require\s*\(\s*["']pathe\/utils["']\s*\)/g;
|
|
58
|
+
if (patheUtilsRequireRegex.test(content)) {
|
|
59
|
+
modified = modified.replace(
|
|
60
|
+
patheUtilsRequireRegex,
|
|
61
|
+
'require("@reliverse/pathkit")'
|
|
62
|
+
);
|
|
63
|
+
changes.push("Updated pathe/utils require to @reliverse/pathkit");
|
|
64
|
+
}
|
|
65
|
+
if (changes.length > 0) {
|
|
66
|
+
if (!dryRun) {
|
|
67
|
+
await writeFile(file, modified, "utf-8");
|
|
68
|
+
}
|
|
69
|
+
results.push({
|
|
70
|
+
file,
|
|
71
|
+
success: true,
|
|
72
|
+
message: `${changes.length} change(s) made`,
|
|
73
|
+
changes
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
results.push({
|
|
78
|
+
file,
|
|
79
|
+
success: false,
|
|
80
|
+
message: `Failed to process: ${error instanceof Error ? error.message : String(error)}`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await updatePackageJson(results, dryRun, {
|
|
85
|
+
remove: ["pathe"],
|
|
86
|
+
add: { "@reliverse/pathkit": "^latest" }
|
|
87
|
+
});
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
export async function migratePathkitToPathe(dryRun = false) {
|
|
91
|
+
const results = [];
|
|
92
|
+
const files = await getAllTsFiles(".");
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
try {
|
|
95
|
+
const content = await readFile(file, "utf-8");
|
|
96
|
+
let modified = content;
|
|
97
|
+
const changes = [];
|
|
98
|
+
const pathkitImportRegex = /from\s+["']@reliverse\/pathkit["']/g;
|
|
99
|
+
if (pathkitImportRegex.test(content)) {
|
|
100
|
+
const advancedFeatures = [
|
|
101
|
+
"getFileImportsExports",
|
|
102
|
+
"convertImportPaths",
|
|
103
|
+
"convertImportExtensionsJsToTs",
|
|
104
|
+
"normalizeAliases",
|
|
105
|
+
"resolveAlias",
|
|
106
|
+
"reverseResolveAlias",
|
|
107
|
+
"normalizeQuotes"
|
|
108
|
+
];
|
|
109
|
+
const usesAdvancedFeatures = advancedFeatures.some(
|
|
110
|
+
(feature) => content.includes(feature)
|
|
111
|
+
);
|
|
112
|
+
if (usesAdvancedFeatures) {
|
|
113
|
+
changes.push(
|
|
114
|
+
"\u26A0\uFE0F File uses advanced pathkit features - manual review needed"
|
|
115
|
+
);
|
|
116
|
+
results.push({
|
|
117
|
+
file,
|
|
118
|
+
success: false,
|
|
119
|
+
message: "Contains advanced pathkit features not available in pathe",
|
|
120
|
+
changes
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const utilsPattern = /import\s*\{([^}]+)\}\s*from\s*["']@reliverse\/pathkit["']/g;
|
|
125
|
+
const utilsMatch = utilsPattern.exec(content);
|
|
126
|
+
if (utilsMatch?.[1]) {
|
|
127
|
+
const imports = utilsMatch[1].split(",").map((s) => s.trim());
|
|
128
|
+
const coreImports = imports.filter(
|
|
129
|
+
(imp) => ![
|
|
130
|
+
"filename",
|
|
131
|
+
"normalizeAliases",
|
|
132
|
+
"resolveAlias",
|
|
133
|
+
"reverseResolveAlias"
|
|
134
|
+
].includes(imp.replace(/\s+as\s+\w+/, ""))
|
|
135
|
+
);
|
|
136
|
+
const utilImports = imports.filter(
|
|
137
|
+
(imp) => [
|
|
138
|
+
"filename",
|
|
139
|
+
"normalizeAliases",
|
|
140
|
+
"resolveAlias",
|
|
141
|
+
"reverseResolveAlias"
|
|
142
|
+
].includes(imp.replace(/\s+as\s+\w+/, ""))
|
|
143
|
+
);
|
|
144
|
+
if (coreImports.length > 0 && utilImports.length > 0) {
|
|
145
|
+
const coreImportStatement = `import { ${coreImports.join(", ")} } from "pathe";`;
|
|
146
|
+
const utilImportStatement = `import { ${utilImports.join(", ")} } from "pathe/utils";`;
|
|
147
|
+
modified = modified.replace(
|
|
148
|
+
utilsMatch[0],
|
|
149
|
+
`${coreImportStatement}
|
|
150
|
+
${utilImportStatement}`
|
|
151
|
+
);
|
|
152
|
+
changes.push(
|
|
153
|
+
"Split pathkit imports into pathe core and pathe/utils"
|
|
154
|
+
);
|
|
155
|
+
} else if (utilImports.length > 0) {
|
|
156
|
+
modified = modified.replace(
|
|
157
|
+
pathkitImportRegex,
|
|
158
|
+
'from "pathe/utils"'
|
|
159
|
+
);
|
|
160
|
+
changes.push("Updated pathkit utils imports to pathe/utils");
|
|
161
|
+
} else {
|
|
162
|
+
modified = modified.replace(pathkitImportRegex, 'from "pathe"');
|
|
163
|
+
changes.push("Updated pathkit imports to pathe");
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
modified = modified.replace(pathkitImportRegex, 'from "pathe"');
|
|
167
|
+
changes.push("Updated pathkit imports to pathe");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const pathkitRequireRegex = /require\s*\(\s*["']@reliverse\/pathkit["']\s*\)/g;
|
|
171
|
+
if (pathkitRequireRegex.test(content)) {
|
|
172
|
+
modified = modified.replace(pathkitRequireRegex, 'require("pathe")');
|
|
173
|
+
changes.push("Updated pathkit require to pathe");
|
|
174
|
+
}
|
|
175
|
+
if (changes.length > 0) {
|
|
176
|
+
if (!dryRun) {
|
|
177
|
+
await writeFile(file, modified, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
results.push({
|
|
180
|
+
file,
|
|
181
|
+
success: true,
|
|
182
|
+
message: `${changes.length} change(s) made`,
|
|
183
|
+
changes
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
results.push({
|
|
188
|
+
file,
|
|
189
|
+
success: false,
|
|
190
|
+
message: `Failed to process: ${error instanceof Error ? error.message : String(error)}`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await updatePackageJson(results, dryRun, {
|
|
195
|
+
remove: ["@reliverse/pathkit"],
|
|
196
|
+
add: { pathe: "^latest" }
|
|
197
|
+
});
|
|
198
|
+
return results;
|
|
199
|
+
}
|
|
200
|
+
async function updatePackageJson(results, dryRun, config) {
|
|
201
|
+
try {
|
|
202
|
+
const packageJsonPath = "./package.json";
|
|
203
|
+
if (existsSync(packageJsonPath)) {
|
|
204
|
+
const packageContent = await readFile(packageJsonPath, "utf-8");
|
|
205
|
+
const packageJson = JSON.parse(packageContent);
|
|
206
|
+
let packageChanged = false;
|
|
207
|
+
const packageChanges = [];
|
|
208
|
+
for (const pkg of config.remove) {
|
|
209
|
+
if (packageJson.dependencies?.[pkg]) {
|
|
210
|
+
packageJson.dependencies = Object.fromEntries(
|
|
211
|
+
Object.entries(packageJson.dependencies).filter(
|
|
212
|
+
([key]) => key !== pkg
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
packageChanged = true;
|
|
216
|
+
packageChanges.push(`Removed ${pkg} from dependencies`);
|
|
217
|
+
}
|
|
218
|
+
if (packageJson.devDependencies?.[pkg]) {
|
|
219
|
+
packageJson.devDependencies = Object.fromEntries(
|
|
220
|
+
Object.entries(packageJson.devDependencies).filter(
|
|
221
|
+
([key]) => key !== pkg
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
packageChanged = true;
|
|
225
|
+
packageChanges.push(`Removed ${pkg} from devDependencies`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const [pkg, version] of Object.entries(config.add)) {
|
|
229
|
+
if (packageChanged && !packageJson.dependencies?.[pkg]) {
|
|
230
|
+
if (!packageJson.dependencies) packageJson.dependencies = {};
|
|
231
|
+
packageJson.dependencies[pkg] = version;
|
|
232
|
+
packageChanges.push(`Added ${pkg} to dependencies`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (packageChanged) {
|
|
236
|
+
if (!dryRun) {
|
|
237
|
+
await writeFile(
|
|
238
|
+
packageJsonPath,
|
|
239
|
+
JSON.stringify(packageJson, null, 2) + "\n",
|
|
240
|
+
"utf-8"
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
results.push({
|
|
244
|
+
file: packageJsonPath,
|
|
245
|
+
success: true,
|
|
246
|
+
message: `${packageChanges.length} change(s) made`,
|
|
247
|
+
changes: packageChanges
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
results.push({
|
|
253
|
+
file: "./package.json",
|
|
254
|
+
success: false,
|
|
255
|
+
message: `Failed to update package.json: ${error instanceof Error ? error.message : String(error)}`
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type MigrationResult = {
|
|
2
|
+
file: string;
|
|
3
|
+
success: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
changes?: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function migrateToNodeNext(dryRun?: boolean): Promise<MigrationResult[]>;
|
|
8
|
+
export declare function migrateToBundler(dryRun?: boolean): Promise<MigrationResult[]>;
|
|
9
|
+
export declare function migrateModuleResolution(target: "nodenext" | "bundler", dryRun?: boolean): Promise<MigrationResult[]>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { getFileImportsExports } from "@reliverse/pathkit";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, writeFile, readdir, stat } from "node:fs/promises";
|
|
4
|
+
import { join, extname } from "node:path";
|
|
5
|
+
async function getAllTsFiles(dir) {
|
|
6
|
+
const files = [];
|
|
7
|
+
try {
|
|
8
|
+
const entries = await readdir(dir);
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = join(dir, entry);
|
|
11
|
+
const stats = await stat(fullPath);
|
|
12
|
+
if (stats.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
13
|
+
const subFiles = await getAllTsFiles(fullPath);
|
|
14
|
+
files.push(...subFiles);
|
|
15
|
+
} else if (stats.isFile()) {
|
|
16
|
+
const ext = extname(entry);
|
|
17
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].includes(ext)) {
|
|
18
|
+
files.push(fullPath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
async function updateTsConfig(targetResolution, dryRun = false) {
|
|
27
|
+
const results = [];
|
|
28
|
+
const tsConfigPath = "./tsconfig.json";
|
|
29
|
+
if (!existsSync(tsConfigPath)) {
|
|
30
|
+
results.push({
|
|
31
|
+
file: tsConfigPath,
|
|
32
|
+
success: false,
|
|
33
|
+
message: "tsconfig.json not found"
|
|
34
|
+
});
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const content = await readFile(tsConfigPath, "utf-8");
|
|
39
|
+
const tsConfig = JSON.parse(content);
|
|
40
|
+
const changes = [];
|
|
41
|
+
if (!tsConfig.compilerOptions) {
|
|
42
|
+
tsConfig.compilerOptions = {};
|
|
43
|
+
}
|
|
44
|
+
if (tsConfig.compilerOptions.moduleResolution !== targetResolution) {
|
|
45
|
+
tsConfig.compilerOptions.moduleResolution = targetResolution;
|
|
46
|
+
changes.push(`Set moduleResolution to ${targetResolution}`);
|
|
47
|
+
}
|
|
48
|
+
if (targetResolution === "nodenext") {
|
|
49
|
+
if (tsConfig.compilerOptions.module !== "nodenext") {
|
|
50
|
+
tsConfig.compilerOptions.module = "nodenext";
|
|
51
|
+
changes.push("Set module to nodenext");
|
|
52
|
+
}
|
|
53
|
+
} else if (targetResolution === "bundler") {
|
|
54
|
+
if (tsConfig.compilerOptions.module !== "preserve") {
|
|
55
|
+
tsConfig.compilerOptions.module = "preserve";
|
|
56
|
+
changes.push("Set module to preserve");
|
|
57
|
+
}
|
|
58
|
+
if (!tsConfig.compilerOptions.noEmit) {
|
|
59
|
+
tsConfig.compilerOptions.noEmit = true;
|
|
60
|
+
changes.push("Enabled noEmit");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!tsConfig.compilerOptions.target) {
|
|
64
|
+
tsConfig.compilerOptions.target = "ES2022";
|
|
65
|
+
changes.push("Set target to ES2022");
|
|
66
|
+
}
|
|
67
|
+
if (changes.length > 0) {
|
|
68
|
+
if (!dryRun) {
|
|
69
|
+
await writeFile(
|
|
70
|
+
tsConfigPath,
|
|
71
|
+
JSON.stringify(tsConfig, null, 2) + "\n",
|
|
72
|
+
"utf-8"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
results.push({
|
|
76
|
+
file: tsConfigPath,
|
|
77
|
+
success: true,
|
|
78
|
+
message: `${changes.length} change(s) made`,
|
|
79
|
+
changes
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
results.push({
|
|
83
|
+
file: tsConfigPath,
|
|
84
|
+
success: true,
|
|
85
|
+
message: "Already configured correctly"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
results.push({
|
|
90
|
+
file: tsConfigPath,
|
|
91
|
+
success: false,
|
|
92
|
+
message: `Failed to update: ${error instanceof Error ? error.message : String(error)}`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
async function updatePackageJson(dryRun = false) {
|
|
98
|
+
const results = [];
|
|
99
|
+
const packageJsonPath = "./package.json";
|
|
100
|
+
if (!existsSync(packageJsonPath)) {
|
|
101
|
+
results.push({
|
|
102
|
+
file: packageJsonPath,
|
|
103
|
+
success: false,
|
|
104
|
+
message: "package.json not found"
|
|
105
|
+
});
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
110
|
+
const packageJson = JSON.parse(content);
|
|
111
|
+
const changes = [];
|
|
112
|
+
if (packageJson.type !== "module") {
|
|
113
|
+
packageJson.type = "module";
|
|
114
|
+
changes.push('Set type to "module"');
|
|
115
|
+
}
|
|
116
|
+
if (changes.length > 0) {
|
|
117
|
+
if (!dryRun) {
|
|
118
|
+
await writeFile(
|
|
119
|
+
packageJsonPath,
|
|
120
|
+
JSON.stringify(packageJson, null, 2) + "\n",
|
|
121
|
+
"utf-8"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
results.push({
|
|
125
|
+
file: packageJsonPath,
|
|
126
|
+
success: true,
|
|
127
|
+
message: `${changes.length} change(s) made`,
|
|
128
|
+
changes
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
results.push({
|
|
132
|
+
file: packageJsonPath,
|
|
133
|
+
success: true,
|
|
134
|
+
message: "Already configured correctly"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
results.push({
|
|
139
|
+
file: packageJsonPath,
|
|
140
|
+
success: false,
|
|
141
|
+
message: `Failed to update: ${error instanceof Error ? error.message : String(error)}`
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
async function updateImportExtensions(targetResolution, dryRun = false) {
|
|
147
|
+
const results = [];
|
|
148
|
+
const files = await getAllTsFiles(".");
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
try {
|
|
151
|
+
const content = await readFile(file, "utf-8");
|
|
152
|
+
const analysis = getFileImportsExports(content, {
|
|
153
|
+
kind: "import",
|
|
154
|
+
pathTypes: ["relative", "absolute"]
|
|
155
|
+
});
|
|
156
|
+
if (analysis.length === 0) continue;
|
|
157
|
+
let modified = content;
|
|
158
|
+
const changes = [];
|
|
159
|
+
let hasChanges = false;
|
|
160
|
+
for (const imp of analysis) {
|
|
161
|
+
if (!imp.source) continue;
|
|
162
|
+
const isRelativeOrAbsolute = imp.pathType === "relative" || imp.pathType === "absolute";
|
|
163
|
+
if (!isRelativeOrAbsolute) continue;
|
|
164
|
+
if (targetResolution === "nodenext") {
|
|
165
|
+
if (imp.source.endsWith(".ts") || imp.source.endsWith(".tsx")) {
|
|
166
|
+
const newSource = imp.source.replace(/\.tsx?$/, ".js");
|
|
167
|
+
modified = modified.replace(`"${imp.source}"`, `"${newSource}"`).replace(`'${imp.source}'`, `'${newSource}'`);
|
|
168
|
+
changes.push(`${imp.source} \u2192 ${newSource}`);
|
|
169
|
+
hasChanges = true;
|
|
170
|
+
} else if (!imp.source.includes(".") && !imp.source.endsWith("/")) {
|
|
171
|
+
const newSource = `${imp.source}.js`;
|
|
172
|
+
modified = modified.replace(`"${imp.source}"`, `"${newSource}"`).replace(`'${imp.source}'`, `'${newSource}'`);
|
|
173
|
+
changes.push(`${imp.source} \u2192 ${newSource}`);
|
|
174
|
+
hasChanges = true;
|
|
175
|
+
}
|
|
176
|
+
} else if (targetResolution === "bundler") {
|
|
177
|
+
if (imp.source.match(/\.(js|jsx|ts|tsx)$/)) {
|
|
178
|
+
const newSource = imp.source.replace(/\.(js|jsx|ts|tsx)$/, "");
|
|
179
|
+
modified = modified.replace(`"${imp.source}"`, `"${newSource}"`).replace(`'${imp.source}'`, `'${newSource}'`);
|
|
180
|
+
changes.push(`${imp.source} \u2192 ${newSource}`);
|
|
181
|
+
hasChanges = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (hasChanges) {
|
|
186
|
+
if (!dryRun) {
|
|
187
|
+
await writeFile(file, modified, "utf-8");
|
|
188
|
+
}
|
|
189
|
+
results.push({
|
|
190
|
+
file,
|
|
191
|
+
success: true,
|
|
192
|
+
message: `${changes.length} import(s) updated`,
|
|
193
|
+
changes
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
results.push({
|
|
198
|
+
file,
|
|
199
|
+
success: false,
|
|
200
|
+
message: `Failed to process: ${error instanceof Error ? error.message : String(error)}`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
export async function migrateToNodeNext(dryRun = false) {
|
|
207
|
+
const results = [];
|
|
208
|
+
const tsConfigResults = await updateTsConfig("nodenext", dryRun);
|
|
209
|
+
results.push(...tsConfigResults);
|
|
210
|
+
const packageResults = await updatePackageJson(dryRun);
|
|
211
|
+
results.push(...packageResults);
|
|
212
|
+
const importResults = await updateImportExtensions("nodenext", dryRun);
|
|
213
|
+
results.push(...importResults);
|
|
214
|
+
return results;
|
|
215
|
+
}
|
|
216
|
+
export async function migrateToBundler(dryRun = false) {
|
|
217
|
+
const results = [];
|
|
218
|
+
const tsConfigResults = await updateTsConfig("bundler", dryRun);
|
|
219
|
+
results.push(...tsConfigResults);
|
|
220
|
+
const packageResults = await updatePackageJson(dryRun);
|
|
221
|
+
results.push(...packageResults);
|
|
222
|
+
const importResults = await updateImportExtensions("bundler", dryRun);
|
|
223
|
+
results.push(...importResults);
|
|
224
|
+
return results;
|
|
225
|
+
}
|
|
226
|
+
export async function migrateModuleResolution(target, dryRun = false) {
|
|
227
|
+
if (target === "nodenext") {
|
|
228
|
+
return migrateToNodeNext(dryRun);
|
|
229
|
+
}
|
|
230
|
+
return migrateToBundler(dryRun);
|
|
231
|
+
}
|
|
@@ -64,7 +64,6 @@ async function prepareCLIFiles(revert = false, recursive = true, useDtsTxtForPre
|
|
|
64
64
|
const fileName = basename(file);
|
|
65
65
|
const baseName = basename(file, ext);
|
|
66
66
|
const dir = dirname(fullPath);
|
|
67
|
-
relinka("log", `Processing file: ${fullPath}`);
|
|
68
67
|
if (revert) {
|
|
69
68
|
if (file.endsWith(".json.json")) {
|
|
70
69
|
const originalName = join(dir, fileName.replace(".json.json", ".json"));
|
package/bin/init/info.js
CHANGED
|
@@ -8,3 +8,8 @@ export declare const copyFileExecutor: (spell: Spell, filePath: string) => Promi
|
|
|
8
8
|
export declare const moveFileExecutor: (spell: Spell, filePath: string) => Promise<SpellResult>;
|
|
9
9
|
export declare const transformContentExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
10
10
|
export declare const insertAtExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
11
|
+
export declare const replaceRangeExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
12
|
+
export declare const transformLineExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
13
|
+
export declare const insertBeforeExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
14
|
+
export declare const insertAfterExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
15
|
+
export declare const conditionalExecuteExecutor: (spell: Spell, filePath: string, content: string) => Promise<SpellResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "@reliverse/pathkit";
|
|
2
2
|
import * as fs from "./spell-filesystem.js";
|
|
3
|
+
import { executeSpell } from "./spell-mod.js";
|
|
3
4
|
export const replaceLineExecutor = async (spell, filePath, content) => {
|
|
4
5
|
if (!spell.lineNumber) {
|
|
5
6
|
return {
|
|
@@ -235,6 +236,38 @@ export const transformContentExecutor = async (spell, filePath, content) => {
|
|
|
235
236
|
}
|
|
236
237
|
);
|
|
237
238
|
break;
|
|
239
|
+
case "update-dependencies":
|
|
240
|
+
if (filePath.endsWith("package.json")) {
|
|
241
|
+
const pkg = JSON.parse(content);
|
|
242
|
+
let newDeps = { ...pkg.dependencies };
|
|
243
|
+
if (newDeps.pathe) {
|
|
244
|
+
const { ...rest } = newDeps;
|
|
245
|
+
newDeps = rest;
|
|
246
|
+
}
|
|
247
|
+
let newDevDeps = { ...pkg.devDependencies };
|
|
248
|
+
if (newDevDeps.pathe) {
|
|
249
|
+
const { ...rest } = newDevDeps;
|
|
250
|
+
newDevDeps = rest;
|
|
251
|
+
}
|
|
252
|
+
newDeps["@reliverse/pathkit"] = "latest";
|
|
253
|
+
const newPkg = {
|
|
254
|
+
...pkg,
|
|
255
|
+
dependencies: newDeps,
|
|
256
|
+
devDependencies: newDevDeps
|
|
257
|
+
};
|
|
258
|
+
newContent = JSON.stringify(newPkg, null, 2);
|
|
259
|
+
} else {
|
|
260
|
+
return {
|
|
261
|
+
spell,
|
|
262
|
+
file: filePath,
|
|
263
|
+
success: false,
|
|
264
|
+
message: "update-dependencies can only be used on package.json"
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case "update-imports":
|
|
269
|
+
newContent = content.replace(/from\s+['"]pathe['"]/g, 'from "@reliverse/pathkit"').replace(/from\s+['"]@unjs\/pathe['"]/g, 'from "@reliverse/pathkit"');
|
|
270
|
+
break;
|
|
238
271
|
default:
|
|
239
272
|
return {
|
|
240
273
|
spell,
|
|
@@ -305,3 +338,257 @@ export const insertAtExecutor = async (spell, filePath, content) => {
|
|
|
305
338
|
};
|
|
306
339
|
}
|
|
307
340
|
};
|
|
341
|
+
export const replaceRangeExecutor = async (spell, filePath, content) => {
|
|
342
|
+
if (!spell.lineNumber || !spell.params.startLine || !spell.params.endLine) {
|
|
343
|
+
return {
|
|
344
|
+
spell,
|
|
345
|
+
file: filePath,
|
|
346
|
+
success: false,
|
|
347
|
+
message: "Line numbers not provided"
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (!spell.value) {
|
|
351
|
+
return {
|
|
352
|
+
spell,
|
|
353
|
+
file: filePath,
|
|
354
|
+
success: false,
|
|
355
|
+
message: "Target file not specified"
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const includeFilePath = path.resolve(path.dirname(filePath), spell.value);
|
|
359
|
+
try {
|
|
360
|
+
const includeContent = await fs.readFile(includeFilePath);
|
|
361
|
+
const lines = content.split("\n");
|
|
362
|
+
const start = spell.params.startLine - 1;
|
|
363
|
+
const end = spell.params.endLine;
|
|
364
|
+
const originalLines = lines.slice(start, end);
|
|
365
|
+
lines.splice(start, end - start, ...includeContent.trim().split("\n"));
|
|
366
|
+
const newContent = lines.join("\n");
|
|
367
|
+
await fs.writeFile(filePath, newContent);
|
|
368
|
+
return {
|
|
369
|
+
spell,
|
|
370
|
+
file: filePath,
|
|
371
|
+
success: true,
|
|
372
|
+
message: `Lines ${spell.params.startLine}-${spell.params.endLine} replaced with content from ${includeFilePath}`,
|
|
373
|
+
changes: {
|
|
374
|
+
before: originalLines.join("\n"),
|
|
375
|
+
after: includeContent.trim()
|
|
376
|
+
},
|
|
377
|
+
details: {
|
|
378
|
+
linesAffected: Array.from(
|
|
379
|
+
{ length: end - start },
|
|
380
|
+
(_, i) => start + i + 1
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
spell,
|
|
387
|
+
file: filePath,
|
|
388
|
+
success: false,
|
|
389
|
+
message: `Failed to replace range: ${error}`
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
export const transformLineExecutor = async (spell, filePath, content) => {
|
|
394
|
+
if (!spell.lineNumber) {
|
|
395
|
+
return {
|
|
396
|
+
spell,
|
|
397
|
+
file: filePath,
|
|
398
|
+
success: false,
|
|
399
|
+
message: "Line number not provided"
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (!spell.value) {
|
|
403
|
+
return {
|
|
404
|
+
spell,
|
|
405
|
+
file: filePath,
|
|
406
|
+
success: false,
|
|
407
|
+
message: "Transformation not specified"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const lines = content.split("\n");
|
|
412
|
+
const originalLine = lines[spell.lineNumber - 1];
|
|
413
|
+
const transformCode = spell.value;
|
|
414
|
+
const transformFn = new Function("line", transformCode);
|
|
415
|
+
const transformedLine = transformFn(originalLine);
|
|
416
|
+
lines[spell.lineNumber - 1] = transformedLine;
|
|
417
|
+
const newContent = lines.join("\n");
|
|
418
|
+
await fs.writeFile(filePath, newContent);
|
|
419
|
+
return {
|
|
420
|
+
spell,
|
|
421
|
+
file: filePath,
|
|
422
|
+
success: true,
|
|
423
|
+
message: "Line transformed",
|
|
424
|
+
changes: {
|
|
425
|
+
before: originalLine ?? "",
|
|
426
|
+
after: transformedLine
|
|
427
|
+
},
|
|
428
|
+
details: {
|
|
429
|
+
linesAffected: [spell.lineNumber]
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
} catch (error) {
|
|
433
|
+
return {
|
|
434
|
+
spell,
|
|
435
|
+
file: filePath,
|
|
436
|
+
success: false,
|
|
437
|
+
message: `Failed to transform line: ${error}`
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
export const insertBeforeExecutor = async (spell, filePath, content) => {
|
|
442
|
+
if (!spell.lineNumber) {
|
|
443
|
+
return {
|
|
444
|
+
spell,
|
|
445
|
+
file: filePath,
|
|
446
|
+
success: false,
|
|
447
|
+
message: "Line number not provided"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (!spell.value) {
|
|
451
|
+
return {
|
|
452
|
+
spell,
|
|
453
|
+
file: filePath,
|
|
454
|
+
success: false,
|
|
455
|
+
message: "Content to insert not specified"
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const lines = content.split("\n");
|
|
460
|
+
const insertContent = spell.value.split("\n");
|
|
461
|
+
const lineNumber = spell.lineNumber;
|
|
462
|
+
lines.splice(lineNumber - 1, 0, ...insertContent);
|
|
463
|
+
const newContent = lines.join("\n");
|
|
464
|
+
await fs.writeFile(filePath, newContent);
|
|
465
|
+
return {
|
|
466
|
+
spell,
|
|
467
|
+
file: filePath,
|
|
468
|
+
success: true,
|
|
469
|
+
message: "Content inserted before line",
|
|
470
|
+
changes: {
|
|
471
|
+
before: "",
|
|
472
|
+
after: insertContent.join("\n")
|
|
473
|
+
},
|
|
474
|
+
details: {
|
|
475
|
+
linesAffected: Array.from(
|
|
476
|
+
{ length: insertContent.length },
|
|
477
|
+
(_, i) => lineNumber + i
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return {
|
|
483
|
+
spell,
|
|
484
|
+
file: filePath,
|
|
485
|
+
success: false,
|
|
486
|
+
message: `Failed to insert content: ${error}`
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
export const insertAfterExecutor = async (spell, filePath, content) => {
|
|
491
|
+
if (!spell.lineNumber) {
|
|
492
|
+
return {
|
|
493
|
+
spell,
|
|
494
|
+
file: filePath,
|
|
495
|
+
success: false,
|
|
496
|
+
message: "Line number not provided"
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (!spell.value) {
|
|
500
|
+
return {
|
|
501
|
+
spell,
|
|
502
|
+
file: filePath,
|
|
503
|
+
success: false,
|
|
504
|
+
message: "Content to insert not specified"
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
const lines = content.split("\n");
|
|
509
|
+
const insertContent = spell.value.split("\n");
|
|
510
|
+
const lineNumber = spell.lineNumber;
|
|
511
|
+
lines.splice(lineNumber, 0, ...insertContent);
|
|
512
|
+
const newContent = lines.join("\n");
|
|
513
|
+
await fs.writeFile(filePath, newContent);
|
|
514
|
+
return {
|
|
515
|
+
spell,
|
|
516
|
+
file: filePath,
|
|
517
|
+
success: true,
|
|
518
|
+
message: "Content inserted after line",
|
|
519
|
+
changes: {
|
|
520
|
+
before: "",
|
|
521
|
+
after: insertContent.join("\n")
|
|
522
|
+
},
|
|
523
|
+
details: {
|
|
524
|
+
linesAffected: Array.from(
|
|
525
|
+
{ length: insertContent.length },
|
|
526
|
+
(_, i) => lineNumber + i + 1
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
} catch (error) {
|
|
531
|
+
return {
|
|
532
|
+
spell,
|
|
533
|
+
file: filePath,
|
|
534
|
+
success: false,
|
|
535
|
+
message: `Failed to insert content: ${error}`
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
export const conditionalExecuteExecutor = async (spell, filePath, content) => {
|
|
540
|
+
if (!spell.params.condition) {
|
|
541
|
+
return {
|
|
542
|
+
spell,
|
|
543
|
+
file: filePath,
|
|
544
|
+
success: false,
|
|
545
|
+
message: "Condition not provided"
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
const conditionFn = new Function(
|
|
550
|
+
"content",
|
|
551
|
+
"filePath",
|
|
552
|
+
spell.params.condition
|
|
553
|
+
);
|
|
554
|
+
const shouldExecute = conditionFn(content, filePath);
|
|
555
|
+
if (!shouldExecute) {
|
|
556
|
+
return {
|
|
557
|
+
spell,
|
|
558
|
+
file: filePath,
|
|
559
|
+
success: true,
|
|
560
|
+
message: "Condition not met, skipping execution"
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (!spell.dependsOn?.length) {
|
|
564
|
+
return {
|
|
565
|
+
spell,
|
|
566
|
+
file: filePath,
|
|
567
|
+
success: false,
|
|
568
|
+
message: "No dependent spells specified"
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const results = [];
|
|
572
|
+
for (const dependentSpell of spell.dependsOn) {
|
|
573
|
+
const result = await executeSpell(dependentSpell, filePath, content);
|
|
574
|
+
results.push(result);
|
|
575
|
+
}
|
|
576
|
+
const allSuccessful = results.every((r) => r.success);
|
|
577
|
+
return {
|
|
578
|
+
spell,
|
|
579
|
+
file: filePath,
|
|
580
|
+
success: allSuccessful,
|
|
581
|
+
message: allSuccessful ? "All dependent spells executed successfully" : "Some dependent spells failed",
|
|
582
|
+
details: {
|
|
583
|
+
filesAffected: results.map((r) => r.file)
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
} catch (error) {
|
|
587
|
+
return {
|
|
588
|
+
spell,
|
|
589
|
+
file: filePath,
|
|
590
|
+
success: false,
|
|
591
|
+
message: `Failed to execute condition: ${error}`
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const SPELL_REGEX =
|
|
1
|
+
const SPELL_REGEX = /dler-([a-z-]+)-(?:"([^"]+)")?-?(?:{([^}]+)})?/;
|
|
2
2
|
export const parseParams = (paramsStr) => {
|
|
3
3
|
const defaultParams = {
|
|
4
|
-
hooked:
|
|
4
|
+
hooked: true
|
|
5
5
|
};
|
|
6
6
|
if (!paramsStr) return defaultParams;
|
|
7
7
|
const params = { ...defaultParams };
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
export type SpellType = "replace-line" | "rename-file" | "remove-comment" | "remove-line" | "remove-file" | "transform-content" | "copy-file" | "move-file" | "insert-at";
|
|
1
|
+
export type SpellType = "replace-line" | "replace-range" | "rename-file" | "remove-comment" | "remove-line" | "remove-file" | "transform-content" | "transform-line" | "copy-file" | "move-file" | "insert-at" | "insert-before" | "insert-after" | "conditional-execute";
|
|
2
2
|
export type SpellParams = {
|
|
3
|
+
/** Whether the spell should be executed manually (true) or automatically at postbuild (false) */
|
|
3
4
|
hooked: boolean;
|
|
5
|
+
/** Line number to start the operation (for range operations) */
|
|
6
|
+
startLine?: number;
|
|
7
|
+
/** Line number to end the operation (for range operations) */
|
|
8
|
+
endLine?: number;
|
|
9
|
+
/** Condition to check before executing the spell */
|
|
10
|
+
condition?: string;
|
|
11
|
+
/** Whether to skip the spell if the target file doesn't exist */
|
|
12
|
+
skipIfMissing?: boolean;
|
|
13
|
+
/** Whether to create the target directory if it doesn't exist */
|
|
14
|
+
createDir?: boolean;
|
|
15
|
+
/** Custom parameters for specific spell types */
|
|
4
16
|
[key: string]: any;
|
|
5
17
|
};
|
|
6
18
|
export type Spell = {
|
|
@@ -10,17 +22,25 @@ export type Spell = {
|
|
|
10
22
|
lineNumber?: number;
|
|
11
23
|
fullMatch?: string;
|
|
12
24
|
fileName?: string;
|
|
25
|
+
/** Optional array of spells that must be executed before this one */
|
|
26
|
+
dependsOn?: Spell[];
|
|
13
27
|
};
|
|
14
28
|
export type SpellExecutionOptions = {
|
|
15
29
|
spells?: (SpellType | "all")[];
|
|
16
30
|
files?: string[];
|
|
17
31
|
dryRun?: boolean;
|
|
32
|
+
/** Whether to show detailed changes in the output */
|
|
33
|
+
showChanges?: boolean;
|
|
34
|
+
/** Whether to validate spell parameters before execution */
|
|
35
|
+
validate?: boolean;
|
|
18
36
|
};
|
|
19
37
|
export type FileOperation = {
|
|
20
38
|
type: "read" | "write" | "rename" | "remove" | "copy" | "move";
|
|
21
39
|
sourcePath: string;
|
|
22
40
|
targetPath?: string;
|
|
23
41
|
content?: string;
|
|
42
|
+
/** Whether to create parent directories if they don't exist */
|
|
43
|
+
createDir?: boolean;
|
|
24
44
|
};
|
|
25
45
|
export type SpellResult = {
|
|
26
46
|
spell: Spell;
|
|
@@ -31,4 +51,10 @@ export type SpellResult = {
|
|
|
31
51
|
before: string;
|
|
32
52
|
after: string;
|
|
33
53
|
};
|
|
54
|
+
/** Detailed information about the changes made */
|
|
55
|
+
details?: {
|
|
56
|
+
linesAffected?: number[];
|
|
57
|
+
filesAffected?: string[];
|
|
58
|
+
validationErrors?: string[];
|
|
59
|
+
};
|
|
34
60
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@reliverse/bleump": "^1.1.4",
|
|
4
|
-
"@reliverse/pathkit": "^1.1.
|
|
4
|
+
"@reliverse/pathkit": "^1.1.6",
|
|
5
5
|
"@reliverse/reglob": "^1.0.0",
|
|
6
6
|
"@reliverse/relico": "^1.1.2",
|
|
7
7
|
"@reliverse/relifso": "^1.3.0",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"name": "@reliverse/dler",
|
|
43
43
|
"type": "module",
|
|
44
|
-
"version": "1.5.
|
|
44
|
+
"version": "1.5.6",
|
|
45
45
|
"keywords": [
|
|
46
46
|
"reliverse",
|
|
47
47
|
"cli",
|