@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 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
- currently, only the first param is supported:
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
- more params coming soon...
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"; // <dler-replace-line-{hooked=false}>` — injects file contents at this line
322
- - `// @ts-expect-error <dler-remove-comment-{hooked=false}>` — removes just this comment
323
- - `// <dler-remove-line-{hooked=false}>` — removes this line
324
- - `// <dler-remove-file-{hooked=false}>` — deletes this file
325
- - `// <dler-rename-file-"tsconfig.json"-{hooked=false}>` — renames this file (runs at postbuild because `hooked=false`)
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=true`:**
338
+ **using `hooked=false`:**
328
339
 
329
- - `// <dler-rename-file-"tsconfig.json"-{hooked=true}>` — renames the file, but only when you trigger it yourself (hooked from your side)
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 dlers cli:
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
- // await dler.spell({}) // means all spells and all files
344
- // spell: ["all"] // means all spells
345
- // spell: [] // means all spells
346
- // files: [] // means all files
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)
@@ -0,0 +1,12 @@
1
+ declare const _default: import("@reliverse/rempts").Command<{
2
+ lib: {
3
+ type: "string";
4
+ description: string;
5
+ };
6
+ dryRun: {
7
+ type: "boolean";
8
+ description: string;
9
+ default: false;
10
+ };
11
+ }>;
12
+ export default _default;
@@ -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
@@ -1,5 +1,5 @@
1
1
  import { endPrompt, startPrompt } from "@reliverse/rempts";
2
- const version = "1.5.5";
2
+ const version = "1.5.6";
3
3
  export async function showStartPrompt(isDev) {
4
4
  await startPrompt({
5
5
  titleColor: "inverse",
@@ -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 = /<dler-([a-z-]+)-(?:"([^"]+)")?-?(?:{([^}]+)})?>/;
1
+ const SPELL_REGEX = /dler-([a-z-]+)-(?:"([^"]+)")?-?(?:{([^}]+)})?/;
2
2
  export const parseParams = (paramsStr) => {
3
3
  const defaultParams = {
4
- hooked: false
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.5",
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.5",
44
+ "version": "1.5.6",
45
45
  "keywords": [
46
46
  "reliverse",
47
47
  "cli",