@procedures/lib 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/cli-flags.d.ts +27 -0
  6. package/dist/cli-flags.d.ts.map +1 -0
  7. package/dist/cli-flags.js +33 -0
  8. package/dist/cli-flags.js.map +1 -0
  9. package/dist/dag/builder.d.ts +18 -0
  10. package/dist/dag/builder.d.ts.map +1 -0
  11. package/dist/dag/builder.js +35 -0
  12. package/dist/dag/builder.js.map +1 -0
  13. package/dist/dag/index.d.ts +12 -0
  14. package/dist/dag/index.d.ts.map +1 -0
  15. package/dist/dag/index.js +12 -0
  16. package/dist/dag/index.js.map +1 -0
  17. package/dist/git/index.d.ts +5 -0
  18. package/dist/git/index.d.ts.map +1 -0
  19. package/dist/git/index.js +5 -0
  20. package/dist/git/index.js.map +1 -0
  21. package/dist/git/ref-parser.d.ts +34 -0
  22. package/dist/git/ref-parser.d.ts.map +1 -0
  23. package/dist/git/ref-parser.js +70 -0
  24. package/dist/git/ref-parser.js.map +1 -0
  25. package/dist/index.d.ts +14 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +20 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/module.d.ts +16 -0
  30. package/dist/module.d.ts.map +1 -0
  31. package/dist/module.js +156 -0
  32. package/dist/module.js.map +1 -0
  33. package/dist/procedures/core/catch.d.ts +33 -0
  34. package/dist/procedures/core/catch.d.ts.map +1 -0
  35. package/dist/procedures/core/catch.js +118 -0
  36. package/dist/procedures/core/catch.js.map +1 -0
  37. package/dist/procedures/core/index.d.ts +9 -0
  38. package/dist/procedures/core/index.d.ts.map +1 -0
  39. package/dist/procedures/core/index.js +8 -0
  40. package/dist/procedures/core/index.js.map +1 -0
  41. package/dist/procedures/dag/index.d.ts +5 -0
  42. package/dist/procedures/dag/index.d.ts.map +1 -0
  43. package/dist/procedures/dag/index.js +5 -0
  44. package/dist/procedures/dag/index.js.map +1 -0
  45. package/dist/procedures/dag/traverse.d.ts +54 -0
  46. package/dist/procedures/dag/traverse.d.ts.map +1 -0
  47. package/dist/procedures/dag/traverse.js +252 -0
  48. package/dist/procedures/dag/traverse.js.map +1 -0
  49. package/dist/procedures/ecosystem/index.d.ts +7 -0
  50. package/dist/procedures/ecosystem/index.d.ts.map +1 -0
  51. package/dist/procedures/ecosystem/index.js +6 -0
  52. package/dist/procedures/ecosystem/index.js.map +1 -0
  53. package/dist/procedures/ecosystem/procedures.d.ts +63 -0
  54. package/dist/procedures/ecosystem/procedures.d.ts.map +1 -0
  55. package/dist/procedures/ecosystem/procedures.js +215 -0
  56. package/dist/procedures/ecosystem/procedures.js.map +1 -0
  57. package/dist/procedures/lib/audit.d.ts +29 -0
  58. package/dist/procedures/lib/audit.d.ts.map +1 -0
  59. package/dist/procedures/lib/audit.js +236 -0
  60. package/dist/procedures/lib/audit.js.map +1 -0
  61. package/dist/procedures/lib/index.d.ts +8 -0
  62. package/dist/procedures/lib/index.d.ts.map +1 -0
  63. package/dist/procedures/lib/index.js +8 -0
  64. package/dist/procedures/lib/index.js.map +1 -0
  65. package/dist/procedures/lib/new.d.ts +18 -0
  66. package/dist/procedures/lib/new.d.ts.map +1 -0
  67. package/dist/procedures/lib/new.js +170 -0
  68. package/dist/procedures/lib/new.js.map +1 -0
  69. package/dist/procedures/lib/rename.d.ts +28 -0
  70. package/dist/procedures/lib/rename.d.ts.map +1 -0
  71. package/dist/procedures/lib/rename.js +267 -0
  72. package/dist/procedures/lib/rename.js.map +1 -0
  73. package/dist/procedures/lib/scan.d.ts +14 -0
  74. package/dist/procedures/lib/scan.d.ts.map +1 -0
  75. package/dist/procedures/lib/scan.js +102 -0
  76. package/dist/procedures/lib/scan.js.map +1 -0
  77. package/dist/register.d.ts +10 -0
  78. package/dist/register.d.ts.map +1 -0
  79. package/dist/register.js +19 -0
  80. package/dist/register.js.map +1 -0
  81. package/dist/scoped-input.d.ts +17 -0
  82. package/dist/scoped-input.d.ts.map +1 -0
  83. package/dist/scoped-input.js +23 -0
  84. package/dist/scoped-input.js.map +1 -0
  85. package/dist/types.d.ts +248 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +231 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/workspace.d.ts +46 -0
  90. package/dist/workspace.d.ts.map +1 -0
  91. package/dist/workspace.js +98 -0
  92. package/dist/workspace.js.map +1 -0
  93. package/package.json +84 -3
  94. package/src/cli-flags.ts +49 -0
  95. package/src/dag/builder.ts +44 -0
  96. package/src/dag/index.ts +23 -0
  97. package/src/git/index.ts +11 -0
  98. package/src/git/ref-parser.ts +83 -0
  99. package/src/index.ts +57 -0
  100. package/src/module.ts +207 -0
  101. package/src/procedures/core/catch.ts +142 -0
  102. package/src/procedures/core/index.ts +9 -0
  103. package/src/procedures/dag/index.ts +5 -0
  104. package/src/procedures/dag/traverse.ts +281 -0
  105. package/src/procedures/ecosystem/index.ts +12 -0
  106. package/src/procedures/ecosystem/procedures.ts +317 -0
  107. package/src/procedures/lib/audit.ts +307 -0
  108. package/src/procedures/lib/index.ts +8 -0
  109. package/src/procedures/lib/new.ts +216 -0
  110. package/src/procedures/lib/rename.ts +355 -0
  111. package/src/procedures/lib/scan.ts +143 -0
  112. package/src/register.ts +23 -0
  113. package/src/types.ts +526 -0
  114. package/src/workspace.ts +110 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * lib.new procedure
3
+ *
4
+ * Creates a new client package in the workspace, at packages/<name>, with the
5
+ * structure the other client packages have: cue-config files, src/index.ts (which
6
+ * imports src/register.ts), src/register.ts, src/types.ts, the "./register" export and the
7
+ * "client.procedures" field. `mark procedure new` then adds procedures that compile.
8
+ * Uses fs.* and shell.* procedures via ctx.client.call() for all operations.
9
+ *
10
+ * After it, run `pnpm install` and `pnpm build` in the workspace root.
11
+ */
12
+
13
+ import { join } from "node:path";
14
+ import type { ProcedureContext } from "@procedures/client";
15
+ import type { LibNewInput, LibNewOutput } from "../../types.js";
16
+ import { packagesDir, resolveWorkspaceRoot } from "../../workspace.js";
17
+
18
+ interface ShellRunOutput {
19
+ exitCode: number;
20
+ stdout: string;
21
+ stderr: string;
22
+ success: boolean;
23
+ }
24
+
25
+ // Importing the package registers its procedures, as in the other client packages
26
+ const INDEX_TS = `// Entry point: importing the package registers its procedures
27
+ export * from "./register.js";
28
+ export * from "./types.js";
29
+ `;
30
+
31
+ // \`mark procedure new\` appends the types of each procedure, and adds the zod import then.
32
+ // An unused import here would fail noUnusedLocals (deep dive CLI-13).
33
+ const TYPES_TS = `/**
34
+ * Type definitions of the procedures of this package
35
+ *
36
+ * mark procedure new adds the types of each new procedure here.
37
+ */
38
+
39
+ export {};
40
+ `;
41
+
42
+ function registerTs(name: string): string {
43
+ return `/**
44
+ * Procedure registration for ${name}
45
+ *
46
+ * Add each procedure of this package to the registerProcedures() array.
47
+ * Scaffold a procedure with: mark procedure new <name> --path packages/${name}
48
+ */
49
+
50
+ import { registerProcedures } from "@procedures/client";
51
+
52
+ export function register(): void {
53
+ registerProcedures([]);
54
+ }
55
+
56
+ // Auto-register
57
+ register();
58
+ `;
59
+ }
60
+
61
+ /**
62
+ * Run the workspace's cue-config CLI (not npx: a new package has no node_modules,
63
+ * and npx would download an unrelated package from the registry).
64
+ */
65
+ async function runCueConfig(
66
+ cueCli: string,
67
+ args: string[],
68
+ cwd: string,
69
+ ctx: ProcedureContext
70
+ ): Promise<void> {
71
+ const result = await ctx.client.call<
72
+ { command: string; args: string[]; cwd: string },
73
+ ShellRunOutput
74
+ >(["shell", "run"], { command: process.execPath, args: [cueCli, ...args], cwd });
75
+ if (!result.success) {
76
+ throw new Error(`cue-config ${args.join(" ")} failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Create a new client package in the workspace
82
+ */
83
+ export async function libNew(input: LibNewInput, ctx: ProcedureContext): Promise<LibNewOutput> {
84
+ const operations: string[] = [];
85
+ const created: string[] = [];
86
+ const errors: string[] = [];
87
+ const packageName = `@procedures/${input.name}`;
88
+
89
+ let rootPath: string;
90
+ try {
91
+ rootPath = resolveWorkspaceRoot(input.rootPath);
92
+ } catch (error) {
93
+ return {
94
+ success: false,
95
+ packageName,
96
+ packagePath: "",
97
+ created: [],
98
+ operations: [],
99
+ errors: [error instanceof Error ? error.message : String(error)],
100
+ };
101
+ }
102
+
103
+ const packagePath = join(packagesDir(rootPath), input.name);
104
+ const cueCli = join(rootPath, "node_modules", "@mark1russell7", "cue", "dist", "cli.js");
105
+
106
+ // Check if package already exists
107
+ const existsResult = await ctx.client.call<{ path: string }, { exists: boolean; path: string }>(
108
+ ["fs", "exists"],
109
+ { path: packagePath }
110
+ );
111
+ if (existsResult.exists) {
112
+ return {
113
+ success: false,
114
+ packageName,
115
+ packagePath,
116
+ created: [],
117
+ operations: [],
118
+ errors: [`Package directory already exists: ${packagePath}`],
119
+ };
120
+ }
121
+
122
+ const files = [
123
+ join(packagePath, "src", "index.ts"),
124
+ join(packagePath, "src", "register.ts"),
125
+ join(packagePath, "src", "types.ts"),
126
+ join(packagePath, "dependencies.json"),
127
+ join(packagePath, "package.json"),
128
+ join(packagePath, "tsconfig.json"),
129
+ join(packagePath, ".gitignore"),
130
+ ];
131
+
132
+ if (input.dryRun) {
133
+ return {
134
+ success: true,
135
+ packageName,
136
+ packagePath,
137
+ created: [`${packagePath}/`, `${join(packagePath, "src")}/`, ...files],
138
+ operations: [
139
+ "Would create packages/" + input.name + " with src/index.ts, src/register.ts and src/types.ts",
140
+ `Would run cue-config init --preset ${input.preset}`,
141
+ "Would run cue-config generate",
142
+ `Would set the package name to ${packageName}, the exports, client.procedures and the client dependency`,
143
+ "Then run pnpm install and pnpm build in the workspace root",
144
+ ],
145
+ errors: [],
146
+ };
147
+ }
148
+
149
+ try {
150
+ // Step 1: Create the folders and the source files
151
+ operations.push("Creating directory structure");
152
+ await ctx.client.call<{ path: string; recursive?: boolean }, { path: string; created: boolean }>(
153
+ ["fs", "mkdir"],
154
+ { path: join(packagePath, "src"), recursive: true }
155
+ );
156
+ created.push(`${packagePath}/`, `${join(packagePath, "src")}/`);
157
+
158
+ for (const [file, content] of [
159
+ [join(packagePath, "src", "index.ts"), INDEX_TS],
160
+ [join(packagePath, "src", "register.ts"), registerTs(input.name)],
161
+ [join(packagePath, "src", "types.ts"), TYPES_TS],
162
+ ] as const) {
163
+ await ctx.client.call<{ path: string; content: string }, { path: string; bytesWritten: number }>(
164
+ ["fs", "write"],
165
+ { path: file, content }
166
+ );
167
+ created.push(file);
168
+ }
169
+
170
+ // Step 2: cue-config init and generate (dependencies.json, package.json, tsconfig.json, .gitignore)
171
+ operations.push(`Running cue-config init --preset ${input.preset}`);
172
+ await runCueConfig(cueCli, ["init", "--preset", input.preset, "--force"], packagePath, ctx);
173
+ created.push(join(packagePath, "dependencies.json"));
174
+
175
+ operations.push("Running cue-config generate");
176
+ await runCueConfig(cueCli, ["generate"], packagePath, ctx);
177
+ created.push(join(packagePath, "package.json"), join(packagePath, "tsconfig.json"), join(packagePath, ".gitignore"));
178
+
179
+ // Step 3: cue-config writes the CUE default name ("unnamed") and no client fields.
180
+ // Set what a client package needs (see documentation/BUGS-2026-07.md, C5).
181
+ operations.push("Setting the package fields");
182
+ const pkgJsonPath = join(packagePath, "package.json");
183
+ const pkgReadResult = await ctx.client.call<{ path: string }, { path: string; data: unknown }>(
184
+ ["fs", "read.json"],
185
+ { path: pkgJsonPath }
186
+ );
187
+ const pkgJson = (pkgReadResult.data ?? {}) as Record<string, unknown>;
188
+ pkgJson["name"] = packageName;
189
+ pkgJson["exports"] = {
190
+ ".": { types: "./dist/index.d.ts", import: "./dist/index.js" },
191
+ "./register": { types: "./dist/register.d.ts", import: "./dist/register.js" },
192
+ };
193
+ pkgJson["client"] = { procedures: "./dist/register.js" };
194
+ pkgJson["dependencies"] = {
195
+ ...((pkgJson["dependencies"] as Record<string, string> | undefined) ?? {}),
196
+ "@procedures/client": "workspace:*",
197
+ zod: "^4.6.5",
198
+ };
199
+ await ctx.client.call<{ path: string; content: string }, { path: string; bytesWritten: number }>(
200
+ ["fs", "write"],
201
+ { path: pkgJsonPath, content: JSON.stringify(pkgJson, null, 2) + "\n" }
202
+ );
203
+ operations.push(`Created ${packageName}. Next: run pnpm install and pnpm build in ${rootPath}`);
204
+ } catch (error) {
205
+ errors.push(error instanceof Error ? error.message : String(error));
206
+ }
207
+
208
+ return {
209
+ success: errors.length === 0,
210
+ packageName,
211
+ packagePath,
212
+ created,
213
+ operations,
214
+ errors,
215
+ };
216
+ }
@@ -0,0 +1,355 @@
1
+ /**
2
+ * lib.rename Procedure
3
+ *
4
+ * Renames a package across the entire codebase using ts-morph for AST-based
5
+ * import updates and direct file manipulation for package.json updates.
6
+ *
7
+ * Uses:
8
+ * - ts-morph: AST manipulation for TypeScript imports
9
+ * - File system operations for package.json updates
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * await client.call(["lib", "rename"], {
14
+ * oldName: "client",
15
+ * newName: "@procedures/client",
16
+ * dryRun: true, // Preview changes without applying
17
+ * });
18
+ * ```
19
+ *
20
+ * The search root is `rootPath`, or else the workspace that contains client-lib.
21
+ */
22
+
23
+ import { Project, SyntaxKind } from "ts-morph";
24
+ import type { ProcedureContext } from "@procedures/client";
25
+ import type { LibRenameInput, LibRenameOutput, RenameChange } from "../../types.js";
26
+ import { resolveWorkspaceRoot } from "../../workspace.js";
27
+
28
+ interface FsGlobOutput { pattern: string; matches: string[]; }
29
+ interface FsGlobInput { pattern: string; cwd: string; absolute: boolean; ignore: string[]; }
30
+ interface FsExistsOutput { exists: boolean; path: string; }
31
+ interface FsReadJsonOutput { path: string; data: unknown; }
32
+ interface FsWriteOutput { path: string; bytesWritten: number; }
33
+
34
+ // The folders that hold no source of the workspace
35
+ const IGNORE = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
36
+
37
+ /**
38
+ * Find all package.json files under a root path (excluding node_modules)
39
+ */
40
+ async function findPackageJsonFiles(rootPath: string, ctx: ProcedureContext): Promise<string[]> {
41
+ const result = await ctx.client.call<FsGlobInput, FsGlobOutput>(
42
+ ["fs", "glob"],
43
+ { pattern: "**/package.json", cwd: rootPath, absolute: true, ignore: IGNORE }
44
+ );
45
+ // fs.glob returns `matches`. (Before, this read `files`, which does not exist: deep dive DATA-7.)
46
+ return result.matches;
47
+ }
48
+
49
+ /**
50
+ * Find all TypeScript files under a root path (excluding node_modules, dist)
51
+ */
52
+ async function findTypeScriptFiles(rootPath: string, ctx: ProcedureContext): Promise<string[]> {
53
+ const result = await ctx.client.call<FsGlobInput, FsGlobOutput>(
54
+ ["fs", "glob"],
55
+ { pattern: "**/*.{ts,tsx}", cwd: rootPath, absolute: true, ignore: IGNORE }
56
+ );
57
+ return result.matches;
58
+ }
59
+
60
+ /**
61
+ * Update package.json name field
62
+ */
63
+ async function updatePackageJsonName(
64
+ pkgPath: string,
65
+ oldName: string,
66
+ newName: string,
67
+ dryRun: boolean,
68
+ ctx: ProcedureContext
69
+ ): Promise<RenameChange | null> {
70
+ try {
71
+ const result = await ctx.client.call<{ path: string }, FsReadJsonOutput>(
72
+ ["fs", "read.json"],
73
+ { path: pkgPath }
74
+ );
75
+ const pkg = result.data as { name?: string };
76
+
77
+ if (pkg.name === oldName) {
78
+ if (!dryRun) {
79
+ pkg.name = newName;
80
+ await ctx.client.call<{ path: string; content: string }, FsWriteOutput>(
81
+ ["fs", "write"],
82
+ { path: pkgPath, content: JSON.stringify(pkg, null, 2) + "\n" }
83
+ );
84
+ }
85
+
86
+ return {
87
+ type: "package-name",
88
+ file: pkgPath,
89
+ oldValue: oldName,
90
+ newValue: newName,
91
+ };
92
+ }
93
+ } catch {
94
+ // Skip files we can't parse
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Update package.json dependencies
102
+ */
103
+ async function updatePackageJsonDependencies(
104
+ pkgPath: string,
105
+ oldName: string,
106
+ newName: string,
107
+ dryRun: boolean,
108
+ ctx: ProcedureContext
109
+ ): Promise<RenameChange[]> {
110
+ const changes: RenameChange[] = [];
111
+
112
+ try {
113
+ const result = await ctx.client.call<{ path: string }, FsReadJsonOutput>(
114
+ ["fs", "read.json"],
115
+ { path: pkgPath }
116
+ );
117
+ const pkg = result.data as Record<string, unknown>;
118
+ let modified = false;
119
+
120
+ const depFields = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
121
+
122
+ for (const field of depFields) {
123
+ const deps = pkg[field] as Record<string, string> | undefined;
124
+ if (deps && oldName in deps) {
125
+ const oldValue = deps[oldName]!;
126
+ let newValue = oldValue;
127
+ // The repository of a git dependency has the name of the package without its scope
128
+ const bare = (name: string): string => name.replace(/^@[^/]+\//, "");
129
+ if (oldValue.includes(`github:mark1russell7/${bare(oldName)}`)) {
130
+ newValue = oldValue.replace(`github:mark1russell7/${bare(oldName)}`, `github:mark1russell7/${bare(newName)}`);
131
+ }
132
+
133
+ if (!dryRun) {
134
+ delete deps[oldName];
135
+ deps[newName] = newValue;
136
+ }
137
+
138
+ changes.push({
139
+ type: "dependency",
140
+ file: pkgPath,
141
+ field,
142
+ oldValue: `"${oldName}": "${oldValue}"`,
143
+ newValue: `"${newName}": "${newValue}"`,
144
+ });
145
+ modified = true;
146
+ }
147
+ }
148
+
149
+ if (modified && !dryRun) {
150
+ await ctx.client.call<{ path: string; content: string }, FsWriteOutput>(
151
+ ["fs", "write"],
152
+ { path: pkgPath, content: JSON.stringify(pkg, null, 2) + "\n" }
153
+ );
154
+ }
155
+ } catch {
156
+ // Skip files we can't parse
157
+ }
158
+
159
+ return changes;
160
+ }
161
+
162
+ /**
163
+ * Update TypeScript imports using ts-morph
164
+ */
165
+ function updateTypeScriptImports(
166
+ tsFiles: string[],
167
+ oldName: string,
168
+ newName: string,
169
+ dryRun: boolean
170
+ ): RenameChange[] {
171
+ const changes: RenameChange[] = [];
172
+
173
+ // Create ts-morph project
174
+ const project = new Project({
175
+ skipAddingFilesFromTsConfig: true,
176
+ skipFileDependencyResolution: true,
177
+ });
178
+
179
+ // Add files to project
180
+ for (const filePath of tsFiles) {
181
+ try {
182
+ project.addSourceFileAtPath(filePath);
183
+ } catch {
184
+ // Skip files we can't add
185
+ }
186
+ }
187
+
188
+ // Process each source file
189
+ for (const sourceFile of project.getSourceFiles()) {
190
+ let fileModified = false;
191
+
192
+ // Find all import declarations
193
+ const importDecls = sourceFile.getImportDeclarations();
194
+
195
+ for (const importDecl of importDecls) {
196
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
197
+
198
+ // Check if this import matches the old package name
199
+ // Match exact name or subpath imports (e.g., "client" or "client/something")
200
+ if (moduleSpecifier === oldName || moduleSpecifier.startsWith(`${oldName}/`)) {
201
+ const newSpecifier = moduleSpecifier.replace(oldName, newName);
202
+
203
+ changes.push({
204
+ type: "import",
205
+ file: sourceFile.getFilePath(),
206
+ line: importDecl.getStartLineNumber(),
207
+ oldValue: `from "${moduleSpecifier}"`,
208
+ newValue: `from "${newSpecifier}"`,
209
+ });
210
+
211
+ if (!dryRun) {
212
+ importDecl.setModuleSpecifier(newSpecifier);
213
+ fileModified = true;
214
+ }
215
+ }
216
+ }
217
+
218
+ // Also check dynamic imports (import())
219
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
220
+
221
+ for (const call of callExpressions) {
222
+ const expression = call.getExpression();
223
+ if (expression.getKind() === SyntaxKind.ImportKeyword) {
224
+ const args = call.getArguments();
225
+ if (args.length > 0) {
226
+ const arg = args[0]!;
227
+ if (arg.getKind() === SyntaxKind.StringLiteral) {
228
+ const value = arg.getText().slice(1, -1); // Remove quotes
229
+ if (value === oldName || value.startsWith(`${oldName}/`)) {
230
+ const newValue = value.replace(oldName, newName);
231
+
232
+ changes.push({
233
+ type: "dynamic-import",
234
+ file: sourceFile.getFilePath(),
235
+ line: call.getStartLineNumber(),
236
+ oldValue: `import("${value}")`,
237
+ newValue: `import("${newValue}")`,
238
+ });
239
+
240
+ if (!dryRun) {
241
+ arg.replaceWithText(`"${newValue}"`);
242
+ fileModified = true;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ // Save if modified
251
+ if (fileModified && !dryRun) {
252
+ sourceFile.saveSync();
253
+ }
254
+ }
255
+
256
+ return changes;
257
+ }
258
+
259
+ /**
260
+ * Execute the lib.rename procedure
261
+ */
262
+ export async function libRename(input: LibRenameInput, ctx: ProcedureContext): Promise<LibRenameOutput> {
263
+ const { oldName, newName, dryRun = false } = input;
264
+
265
+ const changes: RenameChange[] = [];
266
+ const errors: string[] = [];
267
+
268
+ // Resolve path: the given root, else the workspace that contains client-lib
269
+ let resolvedRoot: string;
270
+ try {
271
+ resolvedRoot = resolveWorkspaceRoot(input.rootPath);
272
+ } catch (error) {
273
+ return {
274
+ success: false,
275
+ changes: [],
276
+ errors: [error instanceof Error ? error.message : String(error)],
277
+ summary: { packageNames: 0, dependencies: 0, imports: 0, total: 0 },
278
+ };
279
+ }
280
+
281
+ const existsResult = await ctx.client.call<{ path: string }, FsExistsOutput>(
282
+ ["fs", "exists"],
283
+ { path: resolvedRoot }
284
+ );
285
+ if (!existsResult.exists) {
286
+ return {
287
+ success: false,
288
+ changes: [],
289
+ errors: [`Root path does not exist: ${resolvedRoot}`],
290
+ summary: { packageNames: 0, dependencies: 0, imports: 0, total: 0 },
291
+ };
292
+ }
293
+
294
+ console.error(`[lib.rename] ${dryRun ? "[DRY RUN] " : ""}Renaming "${oldName}" → "${newName}"`);
295
+ console.error(`[lib.rename] Scanning ${resolvedRoot}...`);
296
+
297
+ // Find all package.json files
298
+ const packageJsonFiles = await findPackageJsonFiles(resolvedRoot, ctx);
299
+ console.error(`[lib.rename] Found ${packageJsonFiles.length} package.json files`);
300
+
301
+ // Find all TypeScript files
302
+ const tsFiles = await findTypeScriptFiles(resolvedRoot, ctx);
303
+ console.error(`[lib.rename] Found ${tsFiles.length} TypeScript files`);
304
+
305
+ // 1. Update package.json name field (find the package being renamed)
306
+ for (const pkgPath of packageJsonFiles) {
307
+ const change = await updatePackageJsonName(pkgPath, oldName, newName, dryRun, ctx);
308
+ if (change) {
309
+ changes.push(change);
310
+ console.error(`[lib.rename] ${dryRun ? "Would update" : "Updated"} package name: ${pkgPath}`);
311
+ }
312
+ }
313
+
314
+ // 2. Update package.json dependencies
315
+ for (const pkgPath of packageJsonFiles) {
316
+ const depChanges = await updatePackageJsonDependencies(pkgPath, oldName, newName, dryRun, ctx);
317
+ changes.push(...depChanges);
318
+ if (depChanges.length > 0) {
319
+ console.error(
320
+ `[lib.rename] ${dryRun ? "Would update" : "Updated"} ${depChanges.length} dependencies in: ${pkgPath}`
321
+ );
322
+ }
323
+ }
324
+
325
+ // 3. Update TypeScript imports using ts-morph
326
+ try {
327
+ const importChanges = updateTypeScriptImports(tsFiles, oldName, newName, dryRun);
328
+ changes.push(...importChanges);
329
+ if (importChanges.length > 0) {
330
+ console.error(
331
+ `[lib.rename] ${dryRun ? "Would update" : "Updated"} ${importChanges.length} TypeScript imports`
332
+ );
333
+ }
334
+ } catch (error) {
335
+ const errorMessage = error instanceof Error ? error.message : String(error);
336
+ errors.push(`Error updating TypeScript imports: ${errorMessage}`);
337
+ }
338
+
339
+ // Build summary
340
+ const summary = {
341
+ packageNames: changes.filter((c) => c.type === "package-name").length,
342
+ dependencies: changes.filter((c) => c.type === "dependency").length,
343
+ imports: changes.filter((c) => c.type === "import" || c.type === "dynamic-import").length,
344
+ total: changes.length,
345
+ };
346
+
347
+ console.error(`[lib.rename] Summary: ${summary.packageNames} package names, ${summary.dependencies} dependencies, ${summary.imports} imports`);
348
+
349
+ return {
350
+ success: errors.length === 0,
351
+ changes,
352
+ errors,
353
+ summary,
354
+ };
355
+ }