@kb-labs/devlink-core 1.5.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.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # @kb-labs/plugin-template-cli
2
+
3
+ Reference CLI/REST/Studio plugin package for KB Labs Plugin Template.
4
+
5
+ ## Vision & Purpose
6
+
7
+ **@kb-labs/plugin-template-cli** is the canonical example plugin package used by `@kb-labs/plugin-template`.
8
+ It shows how to implement a plugin that exposes:
9
+
10
+ - a **CLI command** (Hello),
11
+ - a **REST handler**, and
12
+ - a **Studio widget**,
13
+
14
+ all driven by a single manifest and contracts package.
15
+
16
+ ## Package Status
17
+
18
+ - **Version**: 0.1.0
19
+ - **Stage**: Stable (template)
20
+ - **Status**: Reference Implementation ✅
21
+
22
+ ## Architecture
23
+
24
+ ### High-Level Overview
25
+
26
+ ```
27
+ plugin-cli
28
+
29
+ ├──► contracts (from @kb-labs/plugin-template-contracts)
30
+ ├──► shared (constants/helpers)
31
+ ├──► domain (Greeting entity and invariants)
32
+ ├──► application (use-cases: create greeting, etc.)
33
+ ├──► cli (Hello command wiring)
34
+ ├──► rest (Hello REST handler + schema)
35
+ └──► studio (Hello Studio widget)
36
+ ```
37
+
38
+ ### Key Components
39
+
40
+ - `src/domain/`: `Greeting` entity and domain rules
41
+ - `src/application/`: use-cases that orchestrate domain logic
42
+ - `src/cli/commands/hello/*`: CLI command implementation
43
+ - `src/rest/handlers/hello-handler.ts`: REST handler bound to manifest
44
+ - `src/studio/widgets/hello-widget.tsx`: Studio widget implementation
45
+ - `src/manifest.v2.ts`: Plugin manifest v2 (CLI/REST/Studio wiring)
46
+
47
+ ## Features
48
+
49
+ - **Single-source manifest** for CLI/REST/Studio surfaces
50
+ - **Layered architecture** (shared → domain → application → interface)
51
+ - **Type-safe contracts** via `@kb-labs/plugin-template-contracts`
52
+ - **Hello-world flow** demonstrating end-to-end plugin wiring
53
+
54
+ ## Exports
55
+
56
+ From `src/index.ts`:
57
+
58
+ - `manifest`: Plugin Manifest V2
59
+ - All public surfaces:
60
+ - CLI command exports
61
+ - domain/application/shared re-exports
62
+
63
+ ## Dependencies
64
+
65
+ ### Runtime
66
+
67
+ - `@kb-labs/setup-operations`: reusable setup operations
68
+ - `@kb-labs/plugin-manifest`: manifest types and helpers
69
+ - `@kb-labs/plugin-template-contracts`: public contracts for this template plugin
70
+ - `@kb-labs/shared-cli-ui`: shared CLI UI helpers
71
+ - `react`, `react-dom`, `zod`
72
+
73
+ ### Development
74
+
75
+ - `@kb-labs/devkit`: shared TS/ESLint/Vitest/TSUP presets
76
+ - `typescript`, `tsup`, `vitest`, `rimraf`
77
+
78
+ ## Scripts
79
+
80
+ From `kb-labs-plugin-template` repo root:
81
+
82
+ ```bash
83
+ pnpm install
84
+ pnpm --filter @kb-labs/plugin-template-cli build
85
+ pnpm --filter @kb-labs/plugin-template-cli test
86
+ ```
87
+
88
+ To run sandboxes, see the root `README.md` (`pnpm sandbox:cli`, `sandbox:rest`, `sandbox:studio`).
89
+
90
+ ## Command Implementation
91
+
92
+ This template demonstrates **three different approaches** to implementing CLI commands:
93
+
94
+ 1. **High-level wrapper (`defineCommand`)** - Recommended for most cases
95
+ 2. **Low-level atomic tools** - For maximum control
96
+ 3. **Hybrid approach** - Combining both
97
+
98
+ See [`COMMAND_IMPLEMENTATION_GUIDE.md`](./COMMAND_IMPLEMENTATION_GUIDE.md) for detailed explanations and examples.
99
+
100
+ ### Quick Start
101
+
102
+ The `template:hello` command in `src/cli/commands/hello/run.ts` shows all three approaches with working code examples. The default implementation uses Approach 1 (`defineCommand`), which provides:
103
+
104
+ - ✅ Zero-boilerplate flag validation
105
+ - ✅ Automatic analytics integration
106
+ - ✅ Structured logging
107
+ - ✅ Error handling
108
+ - ✅ Timing tracking
109
+ - ✅ JSON output mode
110
+
111
+ ## Customising for Your Plugin
112
+
113
+ When using this as a starting point:
114
+
115
+ - Rename the package in `package.json` (e.g. `@kb-labs/my-plugin-cli`)
116
+ - Update manifest IDs and the contracts package
117
+ - Replace the Hello flow with your own domain, use-cases, and surfaces
118
+ - Choose the command implementation approach that fits your needs (see `COMMAND_IMPLEMENTATION_GUIDE.md`)
119
+
120
+
@@ -0,0 +1,166 @@
1
+ import { PackageMap, DevlinkMode, DevlinkPlan, DevlinkPlanItem, DevlinkState, DevlinkBackup, DiagnosticIssue } from '@kb-labs/devlink-contracts';
2
+
3
+ /** Monorepo info discovered on disk */
4
+ interface MonorepoInfo {
5
+ /** Dir name e.g. kb-labs-core */
6
+ name: string;
7
+ /** Absolute path to the monorepo root */
8
+ rootPath: string;
9
+ /** All package.json paths within the monorepo */
10
+ packagePaths: string[];
11
+ /** pnpm-workspace.yaml content */
12
+ workspacePackages: string[];
13
+ }
14
+ /**
15
+ * Discovers all submodule repos via .gitmodules (layout-agnostic).
16
+ * Includes both monorepos (with pnpm-workspace.yaml) and standalone packages.
17
+ */
18
+ declare function discoverMonorepos(rootDir: string): MonorepoInfo[];
19
+ /**
20
+ * Builds a PackageMap: packageName → { linkPath, npmVersion, monorepo }.
21
+ * Scans all packages in all monorepos and collects their name/version.
22
+ *
23
+ * linkPath is relative to the root kb-labs/ dir (so it can be used as link:../path).
24
+ */
25
+ declare function buildPackageMap(monorepos: MonorepoInfo[], rootDir: string): PackageMap;
26
+ /**
27
+ * Async version of buildPackageMap that verifies each package exists on npm.
28
+ * For 'local' mode skips the npm check and returns all packages found on disk.
29
+ * For 'npm'/'auto'/undefined filters out packages not published to the registry.
30
+ */
31
+ declare function buildPackageMapFiltered(monorepos: MonorepoInfo[], rootDir: string, ttlMs?: number, mode?: DevlinkMode): Promise<PackageMap>;
32
+ /**
33
+ * Determines the current linking mode of cross-repo dependencies in a package.json.
34
+ * Returns counts of link:, npm, workspace: references for @kb-labs/* deps.
35
+ */
36
+ declare function analyzePackageDeps(pkgPath: string, packageMap: PackageMap): {
37
+ linkCount: number;
38
+ npmCount: number;
39
+ workspaceCount: number;
40
+ unknownCount: number;
41
+ };
42
+ /**
43
+ * Determines which MonorepoInfo a package.json belongs to.
44
+ * Uses rootPath prefix matching.
45
+ */
46
+ declare function resolvePackageMonorepo(pkgPath: string, monorepos: MonorepoInfo[]): MonorepoInfo | null;
47
+
48
+ /**
49
+ * Checks if a package exists on the npm registry.
50
+ * Results are cached via useCache() with the given TTL.
51
+ */
52
+ declare function isPublishedOnNpm(packageName: string, ttlMs?: number): Promise<boolean>;
53
+ /**
54
+ * Filters a list of package names to only those published on npm.
55
+ * Runs checks concurrently.
56
+ */
57
+ declare function filterPublishedPackages(packageNames: string[], ttlMs?: number): Promise<Set<string>>;
58
+
59
+ /**
60
+ * Builds a DevlinkPlan — all package.json changes needed for the target mode.
61
+ */
62
+ declare function buildPlan(mode: DevlinkMode, packageMap: PackageMap, monorepos: MonorepoInfo[], rootDir: string, options?: {
63
+ scopedRepos?: string[];
64
+ }): DevlinkPlan;
65
+ /**
66
+ * Returns a human-readable description of a plan item change.
67
+ */
68
+ declare function describeChange(item: DevlinkPlanItem): string;
69
+ /**
70
+ * Groups plan items by monorepo for display purposes.
71
+ */
72
+ declare function groupByMonorepo(items: DevlinkPlanItem[]): Map<string, DevlinkPlanItem[]>;
73
+
74
+ interface ApplyOptions {
75
+ dryRun?: boolean;
76
+ }
77
+ interface ApplyResult {
78
+ applied: number;
79
+ skipped: number;
80
+ errors: Array<{
81
+ file: string;
82
+ error: string;
83
+ }>;
84
+ }
85
+ /**
86
+ * Applies a DevlinkPlan to package.json files on disk.
87
+ * Groups changes by file to minimize I/O operations.
88
+ */
89
+ declare function applyPlan(plan: DevlinkPlan, options?: ApplyOptions): Promise<ApplyResult>;
90
+ /**
91
+ * Checks if the given directory has uncommitted git changes.
92
+ * Returns a list of modified files, or empty array if clean.
93
+ */
94
+ declare function checkGitDirty(repoPath: string): string[];
95
+
96
+ declare function loadState(rootDir: string): DevlinkState;
97
+ declare function saveState(rootDir: string, state: DevlinkState): void;
98
+ interface LockFile {
99
+ frozenAt: string;
100
+ plan: DevlinkPlan;
101
+ }
102
+ declare function freeze(rootDir: string, currentPlan: DevlinkPlan): LockFile;
103
+ declare function loadLock(rootDir: string): LockFile | null;
104
+
105
+ /**
106
+ * Creates a backup of given package.json files before a mutation.
107
+ * Returns the backup metadata.
108
+ */
109
+ declare function createBackup(rootDir: string, filePaths: string[], description: string, currentMode: DevlinkMode | null): DevlinkBackup;
110
+ /**
111
+ * Lists all backups, sorted newest first.
112
+ */
113
+ declare function listBackups(rootDir: string): DevlinkBackup[];
114
+ /**
115
+ * Returns the most recent backup, or null if none exist.
116
+ */
117
+ declare function getLastBackup(rootDir: string): DevlinkBackup | null;
118
+ /**
119
+ * Restores package.json files from a specific backup.
120
+ */
121
+ declare function restoreBackup(rootDir: string, backupId: string): {
122
+ restored: number;
123
+ errors: string[];
124
+ };
125
+ /**
126
+ * Remove oldest backups beyond the retention limit.
127
+ */
128
+ declare function pruneBackups(rootDir: string, maxBackups?: number): number;
129
+
130
+ /**
131
+ * Workspace YAML Manager
132
+ *
133
+ * Generates/updates pnpm-workspace.yaml in sub-repos to include
134
+ * cross-repo paths needed for autonomous `cd sub-repo && pnpm install`.
135
+ */
136
+
137
+ interface WorkspaceYamlUpdate {
138
+ repoName: string;
139
+ repoPath: string;
140
+ added: string[];
141
+ removed: string[];
142
+ kept: string[];
143
+ }
144
+ /**
145
+ * Update pnpm-workspace.yaml in all sub-repos to include correct cross-repo paths.
146
+ *
147
+ * For each sub-repo:
148
+ * 1. Keep intra-repo patterns (packages/*, apps/*, etc.)
149
+ * 2. Analyze which cross-repo packages are needed (from deps in all package.json)
150
+ * 3. Compute correct relative paths to those packages
151
+ * 4. Write updated workspace.yaml
152
+ */
153
+ declare function updateWorkspaceYamls(monorepos: MonorepoInfo[], packageMap: PackageMap, rootDir: string, options?: {
154
+ dryRun?: boolean;
155
+ }): WorkspaceYamlUpdate[];
156
+
157
+ /**
158
+ * Diagnostics — detect broken deps, stale lockfiles, cross-repo workspace:* issues
159
+ */
160
+
161
+ /**
162
+ * Run all diagnostic checks across the monorepo.
163
+ */
164
+ declare function diagnose(monorepos: MonorepoInfo[], packageMap: PackageMap, rootDir: string): DiagnosticIssue[];
165
+
166
+ export { type ApplyOptions, type ApplyResult, type LockFile, type MonorepoInfo, type WorkspaceYamlUpdate, analyzePackageDeps, applyPlan, buildPackageMap, buildPackageMapFiltered, buildPlan, checkGitDirty, createBackup, describeChange, diagnose, discoverMonorepos, filterPublishedPackages, freeze, getLastBackup, groupByMonorepo, isPublishedOnNpm, listBackups, loadLock, loadState, pruneBackups, resolvePackageMonorepo, restoreBackup, saveState, updateWorkspaceYamls };
package/dist/index.js ADDED
@@ -0,0 +1,664 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, rmSync, statSync } from 'fs';
2
+ import { join, dirname, relative, resolve } from 'path';
3
+ import yaml2 from 'js-yaml';
4
+ import { useCache, discoverSubRepoPaths } from '@kb-labs/sdk';
5
+ import { execSync } from 'child_process';
6
+
7
+ // src/discovery/index.ts
8
+ var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
9
+ async function isPublishedOnNpm(packageName, ttlMs = DEFAULT_TTL_MS) {
10
+ const cacheKey = `devlink:npm-exists:${packageName}`;
11
+ const cache = useCache();
12
+ if (cache) {
13
+ const cached = await cache.get(cacheKey);
14
+ if (cached !== void 0 && cached !== null) {
15
+ return cached;
16
+ }
17
+ }
18
+ let exists;
19
+ try {
20
+ execSync(`npm view ${packageName} version --json`, {
21
+ stdio: "pipe",
22
+ timeout: 1e4
23
+ });
24
+ exists = true;
25
+ } catch {
26
+ exists = false;
27
+ }
28
+ if (cache) {
29
+ await cache.set(cacheKey, exists, ttlMs);
30
+ }
31
+ return exists;
32
+ }
33
+ async function filterPublishedPackages(packageNames, ttlMs = DEFAULT_TTL_MS) {
34
+ const results = await Promise.all(
35
+ packageNames.map(async (name) => ({
36
+ name,
37
+ published: await isPublishedOnNpm(name, ttlMs)
38
+ }))
39
+ );
40
+ return new Set(results.filter((r) => r.published).map((r) => r.name));
41
+ }
42
+
43
+ // src/discovery/index.ts
44
+ function discoverMonorepos(rootDir) {
45
+ const subRepoPaths = discoverSubRepoPaths(rootDir);
46
+ const monorepos = [];
47
+ for (const repoPath of subRepoPaths) {
48
+ const repoName = repoPath.split("/").pop() ?? repoPath;
49
+ const hasWorkspace = existsSync(join(repoPath, "pnpm-workspace.yaml"));
50
+ if (hasWorkspace) {
51
+ const packagePaths = findPackageJsonFiles(repoPath);
52
+ const repoWorkspace = yaml2.load(
53
+ readFileSync(join(repoPath, "pnpm-workspace.yaml"), "utf-8")
54
+ );
55
+ monorepos.push({
56
+ name: repoName,
57
+ rootPath: repoPath,
58
+ packagePaths,
59
+ workspacePackages: repoWorkspace.packages ?? []
60
+ });
61
+ } else {
62
+ const rootPkgPath = join(repoPath, "package.json");
63
+ if (existsSync(rootPkgPath)) {
64
+ monorepos.push({
65
+ name: repoName,
66
+ rootPath: repoPath,
67
+ packagePaths: [rootPkgPath],
68
+ workspacePackages: []
69
+ });
70
+ }
71
+ }
72
+ }
73
+ return monorepos;
74
+ }
75
+ function buildPackageMap(monorepos, rootDir) {
76
+ const map = {};
77
+ for (const monorepo of monorepos) {
78
+ for (const pkgPath of monorepo.packagePaths) {
79
+ if (!existsSync(pkgPath)) {
80
+ continue;
81
+ }
82
+ let pkg;
83
+ try {
84
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
85
+ } catch {
86
+ continue;
87
+ }
88
+ if (!pkg.name || !pkg.version) {
89
+ continue;
90
+ }
91
+ if (!pkg.name.startsWith("@kb-labs/") && !pkg.name.startsWith("kb-labs-")) {
92
+ continue;
93
+ }
94
+ const pkgDir = dirname(pkgPath);
95
+ const linkPath = relative(rootDir, pkgDir);
96
+ const entry = {
97
+ name: pkg.name,
98
+ linkPath,
99
+ npmVersion: `^${pkg.version}`,
100
+ monorepo: monorepo.name,
101
+ private: pkg.private ?? false
102
+ };
103
+ map[pkg.name] = entry;
104
+ }
105
+ }
106
+ return map;
107
+ }
108
+ async function buildPackageMapFiltered(monorepos, rootDir, ttlMs, mode) {
109
+ const rawMap = buildPackageMap(monorepos, rootDir);
110
+ if (mode === "local") {
111
+ return rawMap;
112
+ }
113
+ const packageNames = Object.keys(rawMap);
114
+ const published = await filterPublishedPackages(packageNames, ttlMs);
115
+ const filtered = {};
116
+ for (const name of packageNames) {
117
+ if (published.has(name)) {
118
+ filtered[name] = rawMap[name];
119
+ }
120
+ }
121
+ return filtered;
122
+ }
123
+ function analyzePackageDeps(pkgPath, packageMap) {
124
+ let linkCount = 0;
125
+ let npmCount = 0;
126
+ let workspaceCount = 0;
127
+ let unknownCount = 0;
128
+ if (!existsSync(pkgPath)) {
129
+ return { linkCount, npmCount, workspaceCount, unknownCount };
130
+ }
131
+ let pkg;
132
+ try {
133
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
134
+ } catch {
135
+ return { linkCount, npmCount, workspaceCount, unknownCount };
136
+ }
137
+ const sections = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
138
+ for (const section of sections) {
139
+ if (!section) {
140
+ continue;
141
+ }
142
+ for (const [depName, depValue] of Object.entries(section)) {
143
+ if (!packageMap[depName]) {
144
+ continue;
145
+ }
146
+ if (depValue.startsWith("link:")) {
147
+ linkCount++;
148
+ } else if (depValue.startsWith("workspace:")) {
149
+ workspaceCount++;
150
+ } else if (depValue.startsWith("^") || depValue.startsWith("~") || /^\d/.test(depValue)) {
151
+ npmCount++;
152
+ } else {
153
+ unknownCount++;
154
+ }
155
+ }
156
+ }
157
+ return { linkCount, npmCount, workspaceCount, unknownCount };
158
+ }
159
+ function findPackageJsonFiles(repoRoot) {
160
+ const results = [];
161
+ function walk(dir, depth = 0) {
162
+ if (depth > 4) {
163
+ return;
164
+ }
165
+ let entries;
166
+ try {
167
+ entries = readdirSync(dir, { withFileTypes: true, encoding: "utf-8" });
168
+ } catch {
169
+ return;
170
+ }
171
+ for (const entry of entries) {
172
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") {
173
+ continue;
174
+ }
175
+ const full = join(dir, entry.name);
176
+ if (entry.isDirectory()) {
177
+ walk(full, depth + 1);
178
+ } else if (entry.name === "package.json") {
179
+ results.push(full);
180
+ }
181
+ }
182
+ }
183
+ walk(repoRoot);
184
+ return results;
185
+ }
186
+ function resolvePackageMonorepo(pkgPath, monorepos) {
187
+ for (const mono of monorepos) {
188
+ if (pkgPath.startsWith(mono.rootPath + "/") || pkgPath === join(mono.rootPath, "package.json")) {
189
+ return mono;
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function buildPlan(mode, packageMap, monorepos, rootDir, options = {}) {
195
+ const items = [];
196
+ const filteredMonorepos = options.scopedRepos?.length ? monorepos.filter((m) => options.scopedRepos.includes(m.name)) : monorepos;
197
+ for (const monorepo of filteredMonorepos) {
198
+ for (const pkgPath of monorepo.packagePaths) {
199
+ if (!existsSync(pkgPath)) {
200
+ continue;
201
+ }
202
+ let pkg;
203
+ try {
204
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
205
+ } catch {
206
+ continue;
207
+ }
208
+ const sections = ["dependencies", "devDependencies", "peerDependencies"];
209
+ for (const section of sections) {
210
+ const deps = pkg[section];
211
+ if (!deps) {
212
+ continue;
213
+ }
214
+ for (const [depName, currentValue] of Object.entries(deps)) {
215
+ const entry = packageMap[depName];
216
+ if (!entry) {
217
+ continue;
218
+ }
219
+ if (currentValue === "*") {
220
+ continue;
221
+ }
222
+ if (currentValue.startsWith("workspace:")) {
223
+ const consumerMono = resolvePackageMonorepo(pkgPath, monorepos);
224
+ if (consumerMono && consumerMono.name === entry.monorepo) {
225
+ continue;
226
+ }
227
+ }
228
+ const targetValue = getTargetValue(mode, entry, pkgPath, rootDir);
229
+ if (targetValue === currentValue) {
230
+ continue;
231
+ }
232
+ items.push({
233
+ packageJsonPath: pkgPath,
234
+ packageJsonRel: relative(rootDir, pkgPath),
235
+ monorepo: monorepo.name,
236
+ depName,
237
+ from: currentValue,
238
+ to: targetValue,
239
+ section
240
+ });
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return {
246
+ mode,
247
+ items,
248
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
249
+ ...options.scopedRepos?.length ? { scopedRepos: options.scopedRepos } : {}
250
+ };
251
+ }
252
+ function getTargetValue(mode, entry, fromPackageJson, rootDir) {
253
+ if (mode === "npm" && !entry.private) {
254
+ return entry.npmVersion;
255
+ }
256
+ const fromDir = dirname(fromPackageJson);
257
+ const targetDir = resolve(rootDir, entry.linkPath);
258
+ const relPath = relative(fromDir, targetDir);
259
+ const normalized = relPath.startsWith(".") ? relPath : `./${relPath}`;
260
+ return `link:${normalized}`;
261
+ }
262
+ function describeChange(item) {
263
+ return `${item.depName}: ${item.from} \u2192 ${item.to}`;
264
+ }
265
+ function groupByMonorepo(items) {
266
+ const groups = /* @__PURE__ */ new Map();
267
+ for (const item of items) {
268
+ if (!groups.has(item.monorepo)) {
269
+ groups.set(item.monorepo, []);
270
+ }
271
+ groups.get(item.monorepo).push(item);
272
+ }
273
+ return groups;
274
+ }
275
+ async function applyPlan(plan, options = {}) {
276
+ const { dryRun = false } = options;
277
+ const byFile = /* @__PURE__ */ new Map();
278
+ for (const item of plan.items) {
279
+ if (!byFile.has(item.packageJsonPath)) {
280
+ byFile.set(item.packageJsonPath, []);
281
+ }
282
+ byFile.get(item.packageJsonPath).push(item);
283
+ }
284
+ let applied = 0;
285
+ let skipped = 0;
286
+ const errors = [];
287
+ for (const [filePath, items] of byFile.entries()) {
288
+ if (!existsSync(filePath)) {
289
+ skipped++;
290
+ continue;
291
+ }
292
+ if (dryRun) {
293
+ applied += items.length;
294
+ continue;
295
+ }
296
+ try {
297
+ const raw = readFileSync(filePath, "utf-8");
298
+ const pkg = JSON.parse(raw);
299
+ for (const item of items) {
300
+ const section = pkg[item.section];
301
+ if (section && item.depName in section) {
302
+ section[item.depName] = item.to;
303
+ applied++;
304
+ } else {
305
+ skipped++;
306
+ }
307
+ }
308
+ const trailingNewline = raw.endsWith("\n") ? "\n" : "";
309
+ writeFileSync(filePath, JSON.stringify(pkg, null, 2) + trailingNewline, "utf-8");
310
+ } catch (err) {
311
+ errors.push({ file: filePath, error: String(err) });
312
+ }
313
+ }
314
+ return { applied, skipped, errors };
315
+ }
316
+ function checkGitDirty(repoPath) {
317
+ try {
318
+ const output = execSync("git status --porcelain", {
319
+ cwd: repoPath,
320
+ encoding: "utf-8",
321
+ stdio: ["pipe", "pipe", "pipe"]
322
+ });
323
+ return output.split("\n").filter(Boolean).map((line) => line.slice(3).trim());
324
+ } catch {
325
+ return [];
326
+ }
327
+ }
328
+ var DEFAULT_STATE = {
329
+ currentMode: null,
330
+ lastApplied: null,
331
+ frozenAt: null
332
+ };
333
+ function getStatePath(rootDir) {
334
+ return join(rootDir, ".kb", "devlink", "state.json");
335
+ }
336
+ function getLockPath(rootDir) {
337
+ return join(rootDir, ".kb", "devlink", "lock.json");
338
+ }
339
+ function ensureDir(filePath) {
340
+ mkdirSync(dirname(filePath), { recursive: true });
341
+ }
342
+ function loadState(rootDir) {
343
+ const statePath = getStatePath(rootDir);
344
+ if (!existsSync(statePath)) {
345
+ return { ...DEFAULT_STATE };
346
+ }
347
+ try {
348
+ return JSON.parse(readFileSync(statePath, "utf-8"));
349
+ } catch {
350
+ return { ...DEFAULT_STATE };
351
+ }
352
+ }
353
+ function saveState(rootDir, state) {
354
+ const statePath = getStatePath(rootDir);
355
+ ensureDir(statePath);
356
+ writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
357
+ }
358
+ function freeze(rootDir, currentPlan) {
359
+ const lock = {
360
+ frozenAt: (/* @__PURE__ */ new Date()).toISOString(),
361
+ plan: currentPlan
362
+ };
363
+ const lockPath = getLockPath(rootDir);
364
+ ensureDir(lockPath);
365
+ writeFileSync(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf-8");
366
+ const state = loadState(rootDir);
367
+ saveState(rootDir, { ...state, frozenAt: lock.frozenAt });
368
+ return lock;
369
+ }
370
+ function loadLock(rootDir) {
371
+ const lockPath = getLockPath(rootDir);
372
+ if (!existsSync(lockPath)) {
373
+ return null;
374
+ }
375
+ try {
376
+ return JSON.parse(readFileSync(lockPath, "utf-8"));
377
+ } catch {
378
+ return null;
379
+ }
380
+ }
381
+ function getBackupsDir(rootDir) {
382
+ return join(rootDir, ".kb", "devlink", "backups");
383
+ }
384
+ function getMetaPath(backupDir) {
385
+ return join(backupDir, "meta.json");
386
+ }
387
+ function createBackup(rootDir, filePaths, description, currentMode) {
388
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
389
+ const backupDir = join(getBackupsDir(rootDir), id);
390
+ mkdirSync(backupDir, { recursive: true });
391
+ const backedUpFiles = [];
392
+ for (const filePath of filePaths) {
393
+ if (!existsSync(filePath)) {
394
+ continue;
395
+ }
396
+ const safeName = filePath.replace(/\//g, "__").replace(/:/g, "_");
397
+ const destPath = join(backupDir, safeName);
398
+ copyFileSync(filePath, destPath);
399
+ backedUpFiles.push(filePath);
400
+ }
401
+ const meta = {
402
+ id,
403
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
404
+ description,
405
+ files: backedUpFiles,
406
+ modeAtBackup: currentMode
407
+ };
408
+ writeFileSync(getMetaPath(backupDir), JSON.stringify(meta, null, 2) + "\n", "utf-8");
409
+ pruneBackups(rootDir, 10);
410
+ return meta;
411
+ }
412
+ function listBackups(rootDir) {
413
+ const backupsDir = getBackupsDir(rootDir);
414
+ if (!existsSync(backupsDir)) {
415
+ return [];
416
+ }
417
+ const entries = readdirSync(backupsDir, { withFileTypes: true });
418
+ const backups = [];
419
+ for (const entry of entries) {
420
+ if (!entry.isDirectory()) {
421
+ continue;
422
+ }
423
+ const metaPath = getMetaPath(join(backupsDir, entry.name));
424
+ if (!existsSync(metaPath)) {
425
+ continue;
426
+ }
427
+ try {
428
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
429
+ backups.push(meta);
430
+ } catch {
431
+ }
432
+ }
433
+ return backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
434
+ }
435
+ function getLastBackup(rootDir) {
436
+ const backups = listBackups(rootDir);
437
+ return backups[0] ?? null;
438
+ }
439
+ function restoreBackup(rootDir, backupId) {
440
+ const backupDir = join(getBackupsDir(rootDir), backupId);
441
+ const metaPath = getMetaPath(backupDir);
442
+ if (!existsSync(metaPath)) {
443
+ throw new Error(`Backup ${backupId} not found`);
444
+ }
445
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
446
+ let restored = 0;
447
+ const errors = [];
448
+ for (const originalPath of meta.files) {
449
+ const safeName = originalPath.replace(/\//g, "__").replace(/:/g, "_");
450
+ const srcPath = join(backupDir, safeName);
451
+ if (!existsSync(srcPath)) {
452
+ errors.push(`Backup file missing: ${safeName}`);
453
+ continue;
454
+ }
455
+ try {
456
+ mkdirSync(dirname(originalPath), { recursive: true });
457
+ copyFileSync(srcPath, originalPath);
458
+ restored++;
459
+ } catch (err) {
460
+ errors.push(`Failed to restore ${originalPath}: ${String(err)}`);
461
+ }
462
+ }
463
+ return { restored, errors };
464
+ }
465
+ function pruneBackups(rootDir, maxBackups = 10) {
466
+ const backups = listBackups(rootDir);
467
+ if (backups.length <= maxBackups) {
468
+ return 0;
469
+ }
470
+ const backupsDir = getBackupsDir(rootDir);
471
+ let pruned = 0;
472
+ for (const old of backups.slice(maxBackups)) {
473
+ const dir = join(backupsDir, old.id);
474
+ try {
475
+ rmSync(dir, { recursive: true, force: true });
476
+ pruned++;
477
+ } catch {
478
+ }
479
+ }
480
+ return pruned;
481
+ }
482
+ function updateWorkspaceYamls(monorepos, packageMap, rootDir, options = {}) {
483
+ const updates = [];
484
+ for (const mono of monorepos) {
485
+ const wsPath = join(mono.rootPath, "pnpm-workspace.yaml");
486
+ if (!existsSync(wsPath)) {
487
+ continue;
488
+ }
489
+ const update = updateOneWorkspaceYaml(mono, monorepos, packageMap, rootDir, options);
490
+ if (update) {
491
+ updates.push(update);
492
+ }
493
+ }
494
+ return updates;
495
+ }
496
+ function updateOneWorkspaceYaml(mono, allMonorepos, packageMap, rootDir, options) {
497
+ const wsPath = join(mono.rootPath, "pnpm-workspace.yaml");
498
+ let workspace;
499
+ try {
500
+ workspace = yaml2.load(readFileSync(wsPath, "utf-8"));
501
+ } catch {
502
+ return null;
503
+ }
504
+ const currentPatterns = workspace.packages ?? [];
505
+ const intraPatterns = [];
506
+ for (const pattern of currentPatterns) {
507
+ if (!pattern.startsWith("../") && !pattern.startsWith("..\\")) {
508
+ intraPatterns.push(pattern);
509
+ }
510
+ }
511
+ const neededRepos = /* @__PURE__ */ new Set();
512
+ for (const pkgPath of mono.packagePaths) {
513
+ if (!existsSync(pkgPath)) {
514
+ continue;
515
+ }
516
+ let pkg;
517
+ try {
518
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
519
+ } catch {
520
+ continue;
521
+ }
522
+ const allDeps = {
523
+ ...pkg.dependencies,
524
+ ...pkg.devDependencies,
525
+ ...pkg.peerDependencies
526
+ };
527
+ for (const depName of Object.keys(allDeps)) {
528
+ const entry = packageMap[depName];
529
+ if (!entry) {
530
+ continue;
531
+ }
532
+ if (entry.monorepo !== mono.name) {
533
+ neededRepos.add(entry.monorepo);
534
+ }
535
+ }
536
+ }
537
+ const crossPatterns = [];
538
+ for (const repoName of neededRepos) {
539
+ const targetMono = allMonorepos.find((m) => m.name === repoName);
540
+ if (!targetMono) {
541
+ continue;
542
+ }
543
+ const relPath = relative(mono.rootPath, targetMono.rootPath);
544
+ if (targetMono.workspacePackages.length > 0) {
545
+ for (const pattern of targetMono.workspacePackages) {
546
+ crossPatterns.push(`${relPath}/${pattern}`);
547
+ }
548
+ } else {
549
+ crossPatterns.push(relPath);
550
+ }
551
+ }
552
+ crossPatterns.sort();
553
+ const newPatterns = [...intraPatterns, ...crossPatterns];
554
+ const oldSet = new Set(currentPatterns);
555
+ const newSet = new Set(newPatterns);
556
+ const added = crossPatterns.filter((p) => !oldSet.has(p));
557
+ const removed = currentPatterns.filter((p) => p.startsWith("../") && !newSet.has(p));
558
+ const kept = intraPatterns;
559
+ if (added.length === 0 && removed.length === 0) {
560
+ return null;
561
+ }
562
+ if (!options.dryRun) {
563
+ const output = { packages: newPatterns };
564
+ writeFileSync(wsPath, yaml2.dump(output, { lineWidth: -1, quotingType: '"' }), "utf-8");
565
+ }
566
+ return {
567
+ repoName: mono.name,
568
+ repoPath: mono.rootPath,
569
+ added,
570
+ removed,
571
+ kept
572
+ };
573
+ }
574
+ function diagnose(monorepos, packageMap, rootDir) {
575
+ const issues = [];
576
+ for (const mono of monorepos) {
577
+ for (const pkgPath of mono.packagePaths) {
578
+ if (!existsSync(pkgPath)) {
579
+ continue;
580
+ }
581
+ let pkg;
582
+ try {
583
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
584
+ } catch {
585
+ continue;
586
+ }
587
+ const sections = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
588
+ for (const section of sections) {
589
+ if (!section) {
590
+ continue;
591
+ }
592
+ for (const [depName, depValue] of Object.entries(section)) {
593
+ if (depValue.startsWith("link:")) {
594
+ const targetPath = resolve(dirname(pkgPath), depValue.slice(5));
595
+ if (!existsSync(targetPath)) {
596
+ issues.push({
597
+ type: "broken-link",
598
+ severity: "error",
599
+ file: pkgPath,
600
+ dep: depName,
601
+ message: `${depName}: link:${depValue.slice(5)} \u2192 target does not exist`,
602
+ fix: "Run devlink switch --mode=local to recalculate paths"
603
+ });
604
+ }
605
+ }
606
+ if (depValue.startsWith("workspace:") && packageMap[depName]) {
607
+ const consumerMono = resolvePackageMonorepo(pkgPath, monorepos);
608
+ const depMonorepo = packageMap[depName].monorepo;
609
+ if (consumerMono && consumerMono.name !== depMonorepo) {
610
+ issues.push({
611
+ type: "cross-repo-workspace",
612
+ severity: "warning",
613
+ file: pkgPath,
614
+ dep: depName,
615
+ message: `${depName}: workspace:* crosses sub-repo boundary (${consumerMono.name} \u2192 ${depMonorepo})`,
616
+ fix: "Run devlink switch --mode=local to convert to link:"
617
+ });
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
623
+ checkStaleLockfile(mono, issues);
624
+ }
625
+ issues.sort((a, b) => {
626
+ if (a.severity !== b.severity) {
627
+ return a.severity === "error" ? -1 : 1;
628
+ }
629
+ return a.type.localeCompare(b.type);
630
+ });
631
+ return issues;
632
+ }
633
+ function checkStaleLockfile(mono, issues) {
634
+ const lockPath = join(mono.rootPath, "pnpm-lock.yaml");
635
+ if (!existsSync(lockPath)) {
636
+ return;
637
+ }
638
+ let lockMtime;
639
+ try {
640
+ lockMtime = statSync(lockPath).mtimeMs;
641
+ } catch {
642
+ return;
643
+ }
644
+ for (const pkgPath of mono.packagePaths) {
645
+ try {
646
+ const pkgMtime = statSync(pkgPath).mtimeMs;
647
+ if (pkgMtime > lockMtime) {
648
+ issues.push({
649
+ type: "stale-lockfile",
650
+ severity: "warning",
651
+ file: lockPath,
652
+ message: `${mono.name}: pnpm-lock.yaml is older than ${pkgPath}`,
653
+ fix: "Delete lockfile and run pnpm install, or use devlink switch --install"
654
+ });
655
+ return;
656
+ }
657
+ } catch {
658
+ }
659
+ }
660
+ }
661
+
662
+ export { analyzePackageDeps, applyPlan, buildPackageMap, buildPackageMapFiltered, buildPlan, checkGitDirty, createBackup, describeChange, diagnose, discoverMonorepos, filterPublishedPackages, freeze, getLastBackup, groupByMonorepo, isPublishedOnNpm, listBackups, loadLock, loadState, pruneBackups, resolvePackageMonorepo, restoreBackup, saveState, updateWorkspaceYamls };
663
+ //# sourceMappingURL=index.js.map
664
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/npm/index.ts","../src/discovery/index.ts","../src/plan/index.ts","../src/apply/index.ts","../src/state/index.ts","../src/backup/index.ts","../src/workspace-yaml/index.ts","../src/diagnostics/index.ts"],"names":["yaml","existsSync","readFileSync","relative","dirname","execSync","join","writeFileSync","mkdirSync","readdirSync","resolve"],"mappings":"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","file":"index.js","sourcesContent":["import { execSync } from 'child_process';\nimport { useCache } from '@kb-labs/sdk';\n\n/** Default TTL: 24 hours */\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Checks if a package exists on the npm registry.\n * Results are cached via useCache() with the given TTL.\n */\nexport async function isPublishedOnNpm(\n packageName: string,\n ttlMs = DEFAULT_TTL_MS\n): Promise<boolean> {\n const cacheKey = `devlink:npm-exists:${packageName}`;\n const cache = useCache();\n\n if (cache) {\n const cached = await cache.get<boolean>(cacheKey);\n if (cached !== undefined && cached !== null) {return cached;}\n }\n\n let exists: boolean;\n try {\n execSync(`npm view ${packageName} version --json`, {\n stdio: 'pipe',\n timeout: 10_000,\n });\n exists = true;\n } catch {\n exists = false;\n }\n\n if (cache) {\n await cache.set(cacheKey, exists, ttlMs);\n }\n\n return exists;\n}\n\n/**\n * Filters a list of package names to only those published on npm.\n * Runs checks concurrently.\n */\nexport async function filterPublishedPackages(\n packageNames: string[],\n ttlMs = DEFAULT_TTL_MS\n): Promise<Set<string>> {\n const results = await Promise.all(\n packageNames.map(async name => ({\n name,\n published: await isPublishedOnNpm(name, ttlMs),\n }))\n );\n\n return new Set(results.filter(r => r.published).map(r => r.name));\n}\n","import { readFileSync, existsSync, readdirSync } from 'fs';\nimport { join, relative, dirname } from 'path';\nimport yaml from 'js-yaml';\nimport { discoverSubRepoPaths } from '@kb-labs/sdk';\nimport type { PackageMap, PackageEntry, DevlinkMode } from '@kb-labs/devlink-contracts';\nimport { filterPublishedPackages } from '../npm/index.js';\n\ninterface PnpmWorkspace {\n packages?: string[];\n}\n\ninterface PackageJson {\n name?: string;\n version?: string;\n private?: boolean;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\n/** Monorepo info discovered on disk */\nexport interface MonorepoInfo {\n /** Dir name e.g. kb-labs-core */\n name: string;\n /** Absolute path to the monorepo root */\n rootPath: string;\n /** All package.json paths within the monorepo */\n packagePaths: string[];\n /** pnpm-workspace.yaml content */\n workspacePackages: string[];\n}\n\n/**\n * Discovers all submodule repos via .gitmodules (layout-agnostic).\n * Includes both monorepos (with pnpm-workspace.yaml) and standalone packages.\n */\nexport function discoverMonorepos(rootDir: string): MonorepoInfo[] {\n const subRepoPaths = discoverSubRepoPaths(rootDir);\n const monorepos: MonorepoInfo[] = [];\n\n for (const repoPath of subRepoPaths) {\n const repoName = repoPath.split('/').pop() ?? repoPath;\n const hasWorkspace = existsSync(join(repoPath, 'pnpm-workspace.yaml'));\n\n if (hasWorkspace) {\n // Monorepo: scan all package.json files\n const packagePaths = findPackageJsonFiles(repoPath);\n const repoWorkspace = yaml.load(\n readFileSync(join(repoPath, 'pnpm-workspace.yaml'), 'utf-8')\n ) as PnpmWorkspace;\n\n monorepos.push({\n name: repoName,\n rootPath: repoPath,\n packagePaths,\n workspacePackages: repoWorkspace.packages ?? [],\n });\n } else {\n // Standalone package (e.g. devkit): treat root package.json as sole package\n const rootPkgPath = join(repoPath, 'package.json');\n if (existsSync(rootPkgPath)) {\n monorepos.push({\n name: repoName,\n rootPath: repoPath,\n packagePaths: [rootPkgPath],\n workspacePackages: [],\n });\n }\n }\n }\n\n return monorepos;\n}\n\n/**\n * Builds a PackageMap: packageName → { linkPath, npmVersion, monorepo }.\n * Scans all packages in all monorepos and collects their name/version.\n *\n * linkPath is relative to the root kb-labs/ dir (so it can be used as link:../path).\n */\nexport function buildPackageMap(monorepos: MonorepoInfo[], rootDir: string): PackageMap {\n const map: PackageMap = {};\n\n for (const monorepo of monorepos) {\n for (const pkgPath of monorepo.packagePaths) {\n if (!existsSync(pkgPath)) {continue;}\n\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {\n continue;\n }\n\n if (!pkg.name || !pkg.version) {continue;}\n // Only include @kb-labs/* packages (or non-scoped kb-labs-* packages)\n if (!pkg.name.startsWith('@kb-labs/') && !pkg.name.startsWith('kb-labs-')) {continue;}\n\n const pkgDir = dirname(pkgPath);\n const linkPath = relative(rootDir, pkgDir);\n\n const entry: PackageEntry = {\n name: pkg.name,\n linkPath,\n npmVersion: `^${pkg.version}`,\n monorepo: monorepo.name,\n private: pkg.private ?? false,\n };\n\n map[pkg.name] = entry;\n }\n }\n\n return map;\n}\n\n/**\n * Async version of buildPackageMap that verifies each package exists on npm.\n * For 'local' mode skips the npm check and returns all packages found on disk.\n * For 'npm'/'auto'/undefined filters out packages not published to the registry.\n */\nexport async function buildPackageMapFiltered(\n monorepos: MonorepoInfo[],\n rootDir: string,\n ttlMs?: number,\n mode?: DevlinkMode\n): Promise<PackageMap> {\n const rawMap = buildPackageMap(monorepos, rootDir);\n // local mode: disk has priority, no npm check needed\n if (mode === 'local') {return rawMap;}\n const packageNames = Object.keys(rawMap);\n const published = await filterPublishedPackages(packageNames, ttlMs);\n const filtered: PackageMap = {};\n for (const name of packageNames) {\n if (published.has(name)) {filtered[name] = rawMap[name]!;}\n }\n return filtered;\n}\n\n/**\n * Determines the current linking mode of cross-repo dependencies in a package.json.\n * Returns counts of link:, npm, workspace: references for @kb-labs/* deps.\n */\nexport function analyzePackageDeps(\n pkgPath: string,\n packageMap: PackageMap\n): { linkCount: number; npmCount: number; workspaceCount: number; unknownCount: number } {\n let linkCount = 0;\n let npmCount = 0;\n let workspaceCount = 0;\n let unknownCount = 0;\n\n if (!existsSync(pkgPath)) {return { linkCount, npmCount, workspaceCount, unknownCount };}\n\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {\n return { linkCount, npmCount, workspaceCount, unknownCount };\n }\n\n const sections = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];\n for (const section of sections) {\n if (!section) {continue;}\n for (const [depName, depValue] of Object.entries(section)) {\n if (!packageMap[depName]) {continue;} // Not a cross-repo dep\n if (depValue.startsWith('link:')) {\n linkCount++;\n } else if (depValue.startsWith('workspace:')) {\n workspaceCount++;\n } else if (depValue.startsWith('^') || depValue.startsWith('~') || /^\\d/.test(depValue)) {\n npmCount++;\n } else {\n unknownCount++;\n }\n }\n }\n\n return { linkCount, npmCount, workspaceCount, unknownCount };\n}\n\n/**\n * Finds all package.json files within a monorepo (excludes node_modules, dist).\n */\nfunction findPackageJsonFiles(repoRoot: string): string[] {\n const results: string[] = [];\n\n function walk(dir: string, depth = 0) {\n if (depth > 4) {return;}\n\n let entries: import('fs').Dirent<string>[];\n try {\n entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf-8' });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') {continue;}\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full, depth + 1);\n } else if (entry.name === 'package.json') {\n results.push(full);\n }\n }\n }\n\n walk(repoRoot);\n return results;\n}\n\n/**\n * Determines which MonorepoInfo a package.json belongs to.\n * Uses rootPath prefix matching.\n */\nexport function resolvePackageMonorepo(\n pkgPath: string,\n monorepos: MonorepoInfo[]\n): MonorepoInfo | null {\n for (const mono of monorepos) {\n if (pkgPath.startsWith(mono.rootPath + '/') || pkgPath === join(mono.rootPath, 'package.json')) {\n return mono;\n }\n }\n return null;\n}\n","import { readFileSync, existsSync } from 'fs';\nimport { relative, dirname, resolve } from 'path';\nimport type {\n DevlinkMode,\n DevlinkPlan,\n DevlinkPlanItem,\n PackageEntry,\n PackageMap,\n} from '@kb-labs/devlink-contracts';\nimport type { MonorepoInfo } from '../discovery/index.js';\nimport { resolvePackageMonorepo } from '../discovery/index.js';\n\ntype DepSection = 'dependencies' | 'devDependencies' | 'peerDependencies';\n\ninterface PackageJson {\n name?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\n/**\n * Builds a DevlinkPlan — all package.json changes needed for the target mode.\n */\nexport function buildPlan(\n mode: DevlinkMode,\n packageMap: PackageMap,\n monorepos: MonorepoInfo[],\n rootDir: string,\n options: { scopedRepos?: string[] } = {}\n): DevlinkPlan {\n const items: DevlinkPlanItem[] = [];\n const filteredMonorepos = options.scopedRepos?.length\n ? monorepos.filter(m => options.scopedRepos!.includes(m.name))\n : monorepos;\n\n for (const monorepo of filteredMonorepos) {\n for (const pkgPath of monorepo.packagePaths) {\n if (!existsSync(pkgPath)) {continue;}\n\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {\n continue;\n }\n\n const sections: DepSection[] = ['dependencies', 'devDependencies', 'peerDependencies'];\n for (const section of sections) {\n const deps = pkg[section];\n if (!deps) {continue;}\n\n for (const [depName, currentValue] of Object.entries(deps)) {\n const entry = packageMap[depName];\n if (!entry) {continue;} // Not a cross-repo dep\n\n // Skip bare * — ambiguous wildcard, not a versioned dep we manage\n if (currentValue === '*') {continue;}\n\n // workspace:* — only skip if intra-repo (same monorepo handles it via pnpm)\n // Cross-repo workspace:* must be converted to link: or ^version\n if (currentValue.startsWith('workspace:')) {\n const consumerMono = resolvePackageMonorepo(pkgPath, monorepos);\n if (consumerMono && consumerMono.name === entry.monorepo) {continue;}\n }\n\n const targetValue = getTargetValue(mode, entry, pkgPath, rootDir);\n if (targetValue === currentValue) {continue;} // Already correct\n\n items.push({\n packageJsonPath: pkgPath,\n packageJsonRel: relative(rootDir, pkgPath),\n monorepo: monorepo.name,\n depName,\n from: currentValue,\n to: targetValue,\n section,\n });\n }\n }\n }\n }\n\n return {\n mode,\n items,\n timestamp: new Date().toISOString(),\n ...(options.scopedRepos?.length ? { scopedRepos: options.scopedRepos } : {}),\n };\n}\n\n/**\n * Computes the target value for a dependency based on mode.\n * Private packages always stay as link: (not published to npm).\n */\nfunction getTargetValue(\n mode: DevlinkMode,\n entry: PackageEntry,\n fromPackageJson: string,\n rootDir: string\n): string {\n // npm mode: use ^version, but private packages stay as link: (not on npm)\n if (mode === 'npm' && !entry.private) {\n return entry.npmVersion;\n }\n\n // local, auto, or private in npm mode: use link: path\n const fromDir = dirname(fromPackageJson);\n const targetDir = resolve(rootDir, entry.linkPath);\n const relPath = relative(fromDir, targetDir);\n const normalized = relPath.startsWith('.') ? relPath : `./${relPath}`;\n return `link:${normalized}`;\n}\n\n/**\n * Returns a human-readable description of a plan item change.\n */\nexport function describeChange(item: DevlinkPlanItem): string {\n return `${item.depName}: ${item.from} → ${item.to}`;\n}\n\n/**\n * Groups plan items by monorepo for display purposes.\n */\nexport function groupByMonorepo(items: DevlinkPlanItem[]): Map<string, DevlinkPlanItem[]> {\n const groups = new Map<string, DevlinkPlanItem[]>();\n for (const item of items) {\n if (!groups.has(item.monorepo)) {groups.set(item.monorepo, []);}\n groups.get(item.monorepo)!.push(item);\n }\n return groups;\n}\n","import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { execSync } from 'child_process';\nimport type { DevlinkPlan } from '@kb-labs/devlink-contracts';\n\ntype DepSection = 'dependencies' | 'devDependencies' | 'peerDependencies';\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n [key: string]: unknown;\n}\n\nexport interface ApplyOptions {\n dryRun?: boolean;\n}\n\nexport interface ApplyResult {\n applied: number;\n skipped: number;\n errors: Array<{ file: string; error: string }>;\n}\n\n/**\n * Applies a DevlinkPlan to package.json files on disk.\n * Groups changes by file to minimize I/O operations.\n */\nexport async function applyPlan(plan: DevlinkPlan, options: ApplyOptions = {}): Promise<ApplyResult> {\n const { dryRun = false } = options;\n\n // Group items by packageJsonPath\n const byFile = new Map<string, typeof plan.items>();\n for (const item of plan.items) {\n if (!byFile.has(item.packageJsonPath)) {byFile.set(item.packageJsonPath, []);}\n byFile.get(item.packageJsonPath)!.push(item);\n }\n\n let applied = 0;\n let skipped = 0;\n const errors: ApplyResult['errors'] = [];\n\n for (const [filePath, items] of byFile.entries()) {\n if (!existsSync(filePath)) {\n skipped++;\n continue;\n }\n\n if (dryRun) {\n applied += items.length;\n continue;\n }\n\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const pkg = JSON.parse(raw) as PackageJson;\n\n for (const item of items) {\n const section = pkg[item.section as DepSection];\n if (section && item.depName in section) {\n section[item.depName] = item.to;\n applied++;\n } else {\n skipped++;\n }\n }\n\n // Write back with same trailing newline and 2-space indent\n const trailingNewline = raw.endsWith('\\n') ? '\\n' : '';\n writeFileSync(filePath, JSON.stringify(pkg, null, 2) + trailingNewline, 'utf-8');\n } catch (err) {\n errors.push({ file: filePath, error: String(err) });\n }\n }\n\n return { applied, skipped, errors };\n}\n\n/**\n * Checks if the given directory has uncommitted git changes.\n * Returns a list of modified files, or empty array if clean.\n */\nexport function checkGitDirty(repoPath: string): string[] {\n try {\n const output = execSync('git status --porcelain', {\n cwd: repoPath,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return output\n .split('\\n')\n .filter(Boolean)\n .map(line => line.slice(3).trim());\n } catch {\n return []; // Not a git repo or git not available\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport type { DevlinkState, DevlinkPlan } from '@kb-labs/devlink-contracts';\n\nconst DEFAULT_STATE: DevlinkState = {\n currentMode: null,\n lastApplied: null,\n frozenAt: null,\n};\n\nfunction getStatePath(rootDir: string): string {\n return join(rootDir, '.kb', 'devlink', 'state.json');\n}\n\nfunction getLockPath(rootDir: string): string {\n return join(rootDir, '.kb', 'devlink', 'lock.json');\n}\n\nfunction ensureDir(filePath: string): void {\n mkdirSync(dirname(filePath), { recursive: true });\n}\n\n// ─── State ────────────────────────────────────────────────────────────────────\n\nexport function loadState(rootDir: string): DevlinkState {\n const statePath = getStatePath(rootDir);\n if (!existsSync(statePath)) {return { ...DEFAULT_STATE };}\n\n try {\n return JSON.parse(readFileSync(statePath, 'utf-8')) as DevlinkState;\n } catch {\n return { ...DEFAULT_STATE };\n }\n}\n\nexport function saveState(rootDir: string, state: DevlinkState): void {\n const statePath = getStatePath(rootDir);\n ensureDir(statePath);\n writeFileSync(statePath, JSON.stringify(state, null, 2) + '\\n', 'utf-8');\n}\n\n// ─── Lock / Freeze ────────────────────────────────────────────────────────────\n\nexport interface LockFile {\n frozenAt: string;\n plan: DevlinkPlan;\n}\n\nexport function freeze(rootDir: string, currentPlan: DevlinkPlan): LockFile {\n const lock: LockFile = {\n frozenAt: new Date().toISOString(),\n plan: currentPlan,\n };\n\n const lockPath = getLockPath(rootDir);\n ensureDir(lockPath);\n writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n', 'utf-8');\n\n // Update state.frozenAt\n const state = loadState(rootDir);\n saveState(rootDir, { ...state, frozenAt: lock.frozenAt });\n\n return lock;\n}\n\nexport function loadLock(rootDir: string): LockFile | null {\n const lockPath = getLockPath(rootDir);\n if (!existsSync(lockPath)) {return null;}\n\n try {\n return JSON.parse(readFileSync(lockPath, 'utf-8')) as LockFile;\n } catch {\n return null;\n }\n}\n","import {\n readFileSync,\n writeFileSync,\n mkdirSync,\n existsSync,\n readdirSync,\n copyFileSync,\n rmSync,\n} from 'fs';\nimport { join, dirname } from 'path';\nimport type { DevlinkBackup, DevlinkMode } from '@kb-labs/devlink-contracts';\n\nfunction getBackupsDir(rootDir: string): string {\n return join(rootDir, '.kb', 'devlink', 'backups');\n}\n\nfunction getMetaPath(backupDir: string): string {\n return join(backupDir, 'meta.json');\n}\n\n/**\n * Creates a backup of given package.json files before a mutation.\n * Returns the backup metadata.\n */\nexport function createBackup(\n rootDir: string,\n filePaths: string[],\n description: string,\n currentMode: DevlinkMode | null\n): DevlinkBackup {\n const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n const backupDir = join(getBackupsDir(rootDir), id);\n mkdirSync(backupDir, { recursive: true });\n\n const backedUpFiles: string[] = [];\n\n for (const filePath of filePaths) {\n if (!existsSync(filePath)) {continue;}\n\n // Flatten the path for storage: replace / with __ to keep it flat\n const safeName = filePath.replace(/\\//g, '__').replace(/:/g, '_');\n const destPath = join(backupDir, safeName);\n copyFileSync(filePath, destPath);\n backedUpFiles.push(filePath);\n }\n\n const meta: DevlinkBackup = {\n id,\n timestamp: new Date().toISOString(),\n description,\n files: backedUpFiles,\n modeAtBackup: currentMode,\n };\n\n writeFileSync(getMetaPath(backupDir), JSON.stringify(meta, null, 2) + '\\n', 'utf-8');\n\n // Auto-prune: keep only the 10 most recent backups\n pruneBackups(rootDir, 10);\n\n return meta;\n}\n\n/**\n * Lists all backups, sorted newest first.\n */\nexport function listBackups(rootDir: string): DevlinkBackup[] {\n const backupsDir = getBackupsDir(rootDir);\n if (!existsSync(backupsDir)) {return [];}\n\n const entries = readdirSync(backupsDir, { withFileTypes: true });\n const backups: DevlinkBackup[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) {continue;}\n const metaPath = getMetaPath(join(backupsDir, entry.name));\n if (!existsSync(metaPath)) {continue;}\n\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf-8')) as DevlinkBackup;\n backups.push(meta);\n } catch {\n // Skip corrupted backups\n }\n }\n\n return backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n}\n\n/**\n * Returns the most recent backup, or null if none exist.\n */\nexport function getLastBackup(rootDir: string): DevlinkBackup | null {\n const backups = listBackups(rootDir);\n return backups[0] ?? null;\n}\n\n/**\n * Restores package.json files from a specific backup.\n */\nexport function restoreBackup(rootDir: string, backupId: string): { restored: number; errors: string[] } {\n const backupDir = join(getBackupsDir(rootDir), backupId);\n const metaPath = getMetaPath(backupDir);\n\n if (!existsSync(metaPath)) {\n throw new Error(`Backup ${backupId} not found`);\n }\n\n const meta = JSON.parse(readFileSync(metaPath, 'utf-8')) as DevlinkBackup;\n let restored = 0;\n const errors: string[] = [];\n\n for (const originalPath of meta.files) {\n const safeName = originalPath.replace(/\\//g, '__').replace(/:/g, '_');\n const srcPath = join(backupDir, safeName);\n\n if (!existsSync(srcPath)) {\n errors.push(`Backup file missing: ${safeName}`);\n continue;\n }\n\n try {\n mkdirSync(dirname(originalPath), { recursive: true });\n copyFileSync(srcPath, originalPath);\n restored++;\n } catch (err) {\n errors.push(`Failed to restore ${originalPath}: ${String(err)}`);\n }\n }\n\n return { restored, errors };\n}\n\n/**\n * Remove oldest backups beyond the retention limit.\n */\nexport function pruneBackups(rootDir: string, maxBackups = 10): number {\n const backups = listBackups(rootDir); // sorted newest first\n if (backups.length <= maxBackups) {return 0;}\n\n const backupsDir = getBackupsDir(rootDir);\n let pruned = 0;\n\n for (const old of backups.slice(maxBackups)) {\n const dir = join(backupsDir, old.id);\n try {\n rmSync(dir, { recursive: true, force: true });\n pruned++;\n } catch { /* skip */ }\n }\n\n return pruned;\n}\n","/**\n * Workspace YAML Manager\n *\n * Generates/updates pnpm-workspace.yaml in sub-repos to include\n * cross-repo paths needed for autonomous `cd sub-repo && pnpm install`.\n */\n\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join, relative } from 'path';\nimport yaml from 'js-yaml';\nimport type { PackageMap } from '@kb-labs/devlink-contracts';\nimport type { MonorepoInfo } from '../discovery/index.js';\n\ninterface PnpmWorkspace {\n packages?: string[];\n}\n\ninterface PackageJson {\n name?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\nexport interface WorkspaceYamlUpdate {\n repoName: string;\n repoPath: string;\n added: string[];\n removed: string[];\n kept: string[];\n}\n\n/**\n * Update pnpm-workspace.yaml in all sub-repos to include correct cross-repo paths.\n *\n * For each sub-repo:\n * 1. Keep intra-repo patterns (packages/*, apps/*, etc.)\n * 2. Analyze which cross-repo packages are needed (from deps in all package.json)\n * 3. Compute correct relative paths to those packages\n * 4. Write updated workspace.yaml\n */\nexport function updateWorkspaceYamls(\n monorepos: MonorepoInfo[],\n packageMap: PackageMap,\n rootDir: string,\n options: { dryRun?: boolean } = {},\n): WorkspaceYamlUpdate[] {\n const updates: WorkspaceYamlUpdate[] = [];\n\n for (const mono of monorepos) {\n const wsPath = join(mono.rootPath, 'pnpm-workspace.yaml');\n\n // Skip repos without workspace yaml (standalone packages)\n if (!existsSync(wsPath)) {continue;}\n\n const update = updateOneWorkspaceYaml(mono, monorepos, packageMap, rootDir, options);\n if (update) {\n updates.push(update);\n }\n }\n\n return updates;\n}\n\nfunction updateOneWorkspaceYaml(\n mono: MonorepoInfo,\n allMonorepos: MonorepoInfo[],\n packageMap: PackageMap,\n rootDir: string,\n options: { dryRun?: boolean },\n): WorkspaceYamlUpdate | null {\n const wsPath = join(mono.rootPath, 'pnpm-workspace.yaml');\n\n // Read current workspace.yaml\n let workspace: PnpmWorkspace;\n try {\n workspace = yaml.load(readFileSync(wsPath, 'utf-8')) as PnpmWorkspace;\n } catch {\n return null;\n }\n\n const currentPatterns = workspace.packages ?? [];\n\n // Separate intra-repo patterns from cross-repo paths\n const intraPatterns: string[] = [];\n for (const pattern of currentPatterns) {\n // Intra-repo: doesn't start with ../ (relative to own root)\n if (!pattern.startsWith('../') && !pattern.startsWith('..\\\\')) {\n intraPatterns.push(pattern);\n }\n }\n\n // Find all cross-repo packages needed by this sub-repo\n const neededRepos = new Set<string>(); // monorepo names\n for (const pkgPath of mono.packagePaths) {\n if (!existsSync(pkgPath)) {continue;}\n\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {continue;}\n\n const allDeps = {\n ...pkg.dependencies,\n ...pkg.devDependencies,\n ...pkg.peerDependencies,\n };\n\n for (const depName of Object.keys(allDeps)) {\n const entry = packageMap[depName];\n if (!entry) {continue;}\n // Cross-repo dep: target is in a different monorepo\n if (entry.monorepo !== mono.name) {\n neededRepos.add(entry.monorepo);\n }\n }\n }\n\n // Compute cross-repo patterns\n const crossPatterns: string[] = [];\n for (const repoName of neededRepos) {\n const targetMono = allMonorepos.find(m => m.name === repoName);\n if (!targetMono) {continue;}\n\n const relPath = relative(mono.rootPath, targetMono.rootPath);\n\n if (targetMono.workspacePackages.length > 0) {\n // Monorepo: add patterns for its internal packages\n // e.g. \"../../infra/kb-labs-devkit\" for standalone\n // e.g. \"../../platform/kb-labs-core/packages/*\" for monorepos with packages/*\n for (const pattern of targetMono.workspacePackages) {\n crossPatterns.push(`${relPath}/${pattern}`);\n }\n } else {\n // Standalone: add direct path\n crossPatterns.push(relPath);\n }\n }\n\n // Sort and deduplicate\n crossPatterns.sort();\n const newPatterns = [...intraPatterns, ...crossPatterns];\n\n // Check if anything changed\n const oldSet = new Set(currentPatterns);\n const newSet = new Set(newPatterns);\n const added = crossPatterns.filter(p => !oldSet.has(p));\n const removed = currentPatterns.filter(p => p.startsWith('../') && !newSet.has(p));\n const kept = intraPatterns;\n\n if (added.length === 0 && removed.length === 0) {\n return null; // No changes needed\n }\n\n // Write updated workspace.yaml\n if (!options.dryRun) {\n const output: PnpmWorkspace = { packages: newPatterns };\n writeFileSync(wsPath, yaml.dump(output, { lineWidth: -1, quotingType: '\"' }), 'utf-8');\n }\n\n return {\n repoName: mono.name,\n repoPath: mono.rootPath,\n added,\n removed,\n kept,\n };\n}\n","/**\n * Diagnostics — detect broken deps, stale lockfiles, cross-repo workspace:* issues\n */\n\nimport { readFileSync, existsSync, statSync } from 'fs';\nimport { join, dirname, resolve } from 'path';\nimport type { DiagnosticIssue, PackageMap } from '@kb-labs/devlink-contracts';\nimport type { MonorepoInfo } from '../discovery/index.js';\nimport { resolvePackageMonorepo } from '../discovery/index.js';\n\ninterface PackageJson {\n name?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n}\n\n/**\n * Run all diagnostic checks across the monorepo.\n */\nexport function diagnose(\n monorepos: MonorepoInfo[],\n packageMap: PackageMap,\n rootDir: string,\n): DiagnosticIssue[] {\n const issues: DiagnosticIssue[] = [];\n\n for (const mono of monorepos) {\n for (const pkgPath of mono.packagePaths) {\n if (!existsSync(pkgPath)) {continue;}\n\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PackageJson;\n } catch {continue;}\n\n const sections = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];\n for (const section of sections) {\n if (!section) {continue;}\n for (const [depName, depValue] of Object.entries(section)) {\n // Check broken link: paths\n if (depValue.startsWith('link:')) {\n const targetPath = resolve(dirname(pkgPath), depValue.slice(5));\n if (!existsSync(targetPath)) {\n issues.push({\n type: 'broken-link',\n severity: 'error',\n file: pkgPath,\n dep: depName,\n message: `${depName}: link:${depValue.slice(5)} → target does not exist`,\n fix: 'Run devlink switch --mode=local to recalculate paths',\n });\n }\n }\n\n // Check cross-repo workspace:*\n if (depValue.startsWith('workspace:') && packageMap[depName]) {\n const consumerMono = resolvePackageMonorepo(pkgPath, monorepos);\n const depMonorepo = packageMap[depName]!.monorepo;\n if (consumerMono && consumerMono.name !== depMonorepo) {\n issues.push({\n type: 'cross-repo-workspace',\n severity: 'warning',\n file: pkgPath,\n dep: depName,\n message: `${depName}: workspace:* crosses sub-repo boundary (${consumerMono.name} → ${depMonorepo})`,\n fix: 'Run devlink switch --mode=local to convert to link:',\n });\n }\n }\n }\n }\n }\n\n // Check stale lockfile\n checkStaleLockfile(mono, issues);\n }\n\n // Sort: errors first, then warnings\n issues.sort((a, b) => {\n if (a.severity !== b.severity) {return a.severity === 'error' ? -1 : 1;}\n return a.type.localeCompare(b.type);\n });\n\n return issues;\n}\n\n/**\n * Check if a sub-repo's lockfile is stale (older than any package.json).\n */\nfunction checkStaleLockfile(mono: MonorepoInfo, issues: DiagnosticIssue[]): void {\n const lockPath = join(mono.rootPath, 'pnpm-lock.yaml');\n if (!existsSync(lockPath)) {return;}\n\n let lockMtime: number;\n try {\n lockMtime = statSync(lockPath).mtimeMs;\n } catch {return;}\n\n for (const pkgPath of mono.packagePaths) {\n try {\n const pkgMtime = statSync(pkgPath).mtimeMs;\n if (pkgMtime > lockMtime) {\n issues.push({\n type: 'stale-lockfile',\n severity: 'warning',\n file: lockPath,\n message: `${mono.name}: pnpm-lock.yaml is older than ${pkgPath}`,\n fix: 'Delete lockfile and run pnpm install, or use devlink switch --install',\n });\n return; // One warning per repo is enough\n }\n } catch { /* skip */ }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@kb-labs/devlink-core",
3
+ "version": "1.5.0",
4
+ "type": "module",
5
+ "description": "Core business logic for DevLink plugin — discovery, plan, apply, state, backup.",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./discovery": {
14
+ "import": "./dist/discovery/index.js",
15
+ "types": "./dist/discovery/index.d.ts"
16
+ },
17
+ "./plan": {
18
+ "import": "./dist/plan/index.js",
19
+ "types": "./dist/plan/index.d.ts"
20
+ },
21
+ "./apply": {
22
+ "import": "./dist/apply/index.js",
23
+ "types": "./dist/apply/index.d.ts"
24
+ },
25
+ "./state": {
26
+ "import": "./dist/state/index.js",
27
+ "types": "./dist/state/index.d.ts"
28
+ },
29
+ "./backup": {
30
+ "import": "./dist/backup/index.js",
31
+ "types": "./dist/backup/index.d.ts"
32
+ },
33
+ "./dist/*": "./dist/*"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "sideEffects": false,
40
+ "scripts": {
41
+ "clean": "rimraf dist",
42
+ "build": "tsup --config tsup.config.ts",
43
+ "dev": "tsup --config tsup.config.ts --watch",
44
+ "lint": "eslint src --ext .ts",
45
+ "lint:fix": "eslint . --fix",
46
+ "type-check": "tsc --noEmit",
47
+ "test": "vitest run --passWithNoTests",
48
+ "test:watch": "vitest"
49
+ },
50
+ "dependencies": {
51
+ "@kb-labs/devlink-contracts": "^1.5.0",
52
+ "@kb-labs/sdk": "^1.5.0",
53
+ "js-yaml": "^4.1.0",
54
+ "zod": "^3.23.8"
55
+ },
56
+ "devDependencies": {
57
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
58
+ "@types/js-yaml": "^4.0.9",
59
+ "@types/node": "^24.3.3",
60
+ "eslint": "^9",
61
+ "rimraf": "^6.0.1",
62
+ "tsup": "^8.5.0",
63
+ "typescript": "^5.6.3",
64
+ "vitest": "^3.2.4"
65
+ },
66
+ "engines": {
67
+ "node": ">=20.0.0",
68
+ "pnpm": ">=9.0.0"
69
+ }
70
+ }