@boopkit/cli 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 (53) hide show
  1. package/README.md +65 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +85 -0
  4. package/dist/commands/add.d.ts +6 -0
  5. package/dist/commands/add.js +173 -0
  6. package/dist/commands/adopt.d.ts +5 -0
  7. package/dist/commands/adopt.js +286 -0
  8. package/dist/commands/init.d.ts +5 -0
  9. package/dist/commands/init.js +295 -0
  10. package/dist/commands/list.d.ts +6 -0
  11. package/dist/commands/list.js +105 -0
  12. package/dist/commands/loop.d.ts +63 -0
  13. package/dist/commands/loop.js +765 -0
  14. package/dist/commands/status.d.ts +40 -0
  15. package/dist/commands/status.js +303 -0
  16. package/dist/commands/status.test.d.ts +1 -0
  17. package/dist/commands/status.test.js +118 -0
  18. package/dist/commands/update.d.ts +8 -0
  19. package/dist/commands/update.js +204 -0
  20. package/dist/lib/capability.d.ts +17 -0
  21. package/dist/lib/capability.js +172 -0
  22. package/dist/lib/config.d.ts +70 -0
  23. package/dist/lib/config.js +59 -0
  24. package/dist/lib/db-status.d.ts +64 -0
  25. package/dist/lib/db-status.js +257 -0
  26. package/dist/lib/env.d.ts +25 -0
  27. package/dist/lib/env.js +116 -0
  28. package/dist/lib/execute.d.ts +21 -0
  29. package/dist/lib/execute.js +228 -0
  30. package/dist/lib/interpolate.d.ts +4 -0
  31. package/dist/lib/interpolate.js +13 -0
  32. package/dist/lib/manifest.d.ts +54 -0
  33. package/dist/lib/manifest.js +11 -0
  34. package/dist/lib/preflight.d.ts +31 -0
  35. package/dist/lib/preflight.js +402 -0
  36. package/dist/lib/profile.d.ts +11 -0
  37. package/dist/lib/profile.js +73 -0
  38. package/dist/lib/project.d.ts +36 -0
  39. package/dist/lib/project.js +71 -0
  40. package/dist/lib/project.test.d.ts +1 -0
  41. package/dist/lib/project.test.js +118 -0
  42. package/dist/lib/rewire.d.ts +19 -0
  43. package/dist/lib/rewire.js +222 -0
  44. package/dist/lib/safe-io.d.ts +66 -0
  45. package/dist/lib/safe-io.js +167 -0
  46. package/dist/lib/scaffold.d.ts +87 -0
  47. package/dist/lib/scaffold.js +548 -0
  48. package/dist/lib/update.d.ts +73 -0
  49. package/dist/lib/update.js +379 -0
  50. package/package.json +46 -0
  51. package/profiles/web-app/profile.md +113 -0
  52. package/profiles/web-app/reference/architecture.ref.md +65 -0
  53. package/profiles/web-app/reference/message-queue-patterns.ref.md +247 -0
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # boop
2
+
3
+ CLI tool to scaffold new projects from reusable profiles and capabilities. Get a running web app with proven architecture, conventions, and AI agent skills — in one command.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @boopkit/cli@beta
9
+ ```
10
+
11
+ Or use directly:
12
+
13
+ ```bash
14
+ npx @boopkit/cli@beta init myapp
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ boop init my-app
21
+ cd my-app
22
+ npm run dev
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ | Command | Description |
28
+ |---------|-------------|
29
+ | `boop list profiles` | List available profiles |
30
+ | `boop init <name>` | Create a new project from a profile (default profile is `web-app`) |
31
+ | `boop status` | Show current project status |
32
+ | `boop caps` | List available capabilities (alias for `boop list capabilities`) |
33
+ | `boop add <capability>` | Add a capability to an existing project |
34
+
35
+ ## What You Get
36
+
37
+ `boop init` scaffolds a full-stack web application with:
38
+
39
+ - **Express** server with Handlebars views and Tailwind CSS
40
+ - **REST API** with controllers, routes, and middleware
41
+ - **TypeScript** with strict mode and path aliases
42
+ - **Jest** testing setup with TDD workflow
43
+ - **Multi-process architecture** (web, worker, scheduler)
44
+ - **Heroku deployment** via Procfile
45
+ - **AI agent skills** for development, testing, design, and project management
46
+
47
+ Capabilities are additive. Start simple, add complexity when you need it:
48
+
49
+ ```bash
50
+ boop add users-and-accounts # Adds user authentication and account management features
51
+ boop add email # Adds email sending capabilities with templates
52
+ ```
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ npm install
58
+ npm run build
59
+ npm run test
60
+ npm run dev -- init test-project # Run from source
61
+ ```
62
+
63
+ ## License
64
+
65
+ Private
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const status_1 = require("./commands/status");
6
+ const init_1 = require("./commands/init");
7
+ const list_1 = require("./commands/list");
8
+ const add_1 = require("./commands/add");
9
+ const adopt_1 = require("./commands/adopt");
10
+ const update_1 = require("./commands/update");
11
+ const loop_1 = require("./commands/loop");
12
+ const program = new commander_1.Command();
13
+ program
14
+ .name('boop')
15
+ .description('Scaffold new projects from boop profiles and capabilities')
16
+ .version('0.1.0');
17
+ program
18
+ .command('init')
19
+ .description('Create a new project from a boop profile')
20
+ .argument('<name>', 'Project name')
21
+ .option('-p, --profile <profile>', 'Profile to use', 'web-app')
22
+ .option('--no-install', 'Skip npm install')
23
+ .action(init_1.runInit);
24
+ program
25
+ .command('list')
26
+ .description('List available profiles or capabilities')
27
+ .argument('<type>', 'What to list: profiles or capabilities')
28
+ .action(list_1.runList);
29
+ program
30
+ .command('status')
31
+ .description('Show current boop project status')
32
+ .option('--json', 'Output status as JSON')
33
+ .action(status_1.runStatus);
34
+ program
35
+ .command('caps')
36
+ .description('List available capabilities (shortcut for "list capabilities")')
37
+ .action(() => (0, list_1.runList)('capabilities'));
38
+ program
39
+ .command('add')
40
+ .description('Add a capability to an existing boop project')
41
+ .argument('[capability]', 'Capability to add')
42
+ .option('--no-install', 'Skip npm install')
43
+ .option('--json', 'Output structured JSON')
44
+ .option('--dry-run', 'Show what would be done without making changes')
45
+ .addHelpText('after', '\nRun `boop list capabilities` to see available options.')
46
+ .action(add_1.runAdd);
47
+ program
48
+ .command('update')
49
+ .description('Update scaffold files from capability packages')
50
+ .argument('[capability]', 'Capability to update (all if omitted)')
51
+ .option('--dry-run', 'Show what would change without writing')
52
+ .option('--force', 'Overwrite locally modified files (creates .bak backup)')
53
+ .option('--restore', 'Restore locally deleted scaffold files')
54
+ .option('--prune', 'Remove deprecated scaffold files from disk (creates .bak backup)')
55
+ .option('--json', 'Output structured JSON')
56
+ .action(update_1.runUpdate);
57
+ program
58
+ .command('adopt')
59
+ .description('Copy package source into the project for full ownership')
60
+ .argument('[capability]', 'Capability whose package to adopt')
61
+ .option('--no-install', 'Skip npm install')
62
+ .option('--json', 'Output structured JSON')
63
+ .action(adopt_1.runAdopt);
64
+ program
65
+ .command('loop')
66
+ .description('Continuously process ready beads issues via Claude Code sessions')
67
+ .option('--prefix <prefix>', 'Issue ID prefix to filter by')
68
+ .option('--work <path>', 'Spec file for planning phase')
69
+ .option('--flow <path>', 'Workflow file for planning phase')
70
+ .option('--run', 'Actually execute (dry-run by default)')
71
+ .option('--max <n>', 'Stop after N issues')
72
+ .option('--budget <amount>', 'Budget cap per issue in USD (default: 5.00)')
73
+ .option('--resume-limit <n>', 'Max resume attempts per issue (default: 3)')
74
+ .option('--poll', 'Keep polling when no ready issues found')
75
+ .option('--poll-interval <seconds>', 'Seconds between polls (default: 60)')
76
+ .option('--model <model>', 'Claude model to use (default: claude-opus-4-6)')
77
+ .option('--per-issue-branch', 'Create a separate branch per issue')
78
+ .option('--no-branch', 'Skip branch creation entirely')
79
+ .option('--features', 'Include feature-type issues (excluded by default)')
80
+ .option('--no-skip-perms', 'Do not skip permission prompts')
81
+ .option('--bare', 'Force API key auth (--bare mode); skips OAuth/keychain, uses ANTHROPIC_API_KEY strictly')
82
+ .option('--json', 'Output summary as JSON')
83
+ .addHelpText('after', '\nBy default runs in dry-run mode. Add --run to execute.')
84
+ .action(loop_1.runLoop);
85
+ program.parse(process.argv);
@@ -0,0 +1,6 @@
1
+ export interface AddOptions {
2
+ install?: boolean;
3
+ json?: boolean;
4
+ dryRun?: boolean;
5
+ }
6
+ export declare function runAdd(capabilityName: string | undefined, options: AddOptions): void;
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAdd = runAdd;
4
+ const preflight_1 = require("../lib/preflight");
5
+ const execute_1 = require("../lib/execute");
6
+ const interpolate_1 = require("../lib/interpolate");
7
+ const list_1 = require("./list");
8
+ function runAdd(capabilityName, options) {
9
+ // 0. Validate capability argument is provided
10
+ if (!capabilityName) {
11
+ console.error(`\x1b[31mERR\x1b[0m Missing capability argument.\n`);
12
+ (0, list_1.printGroupedCapabilities)();
13
+ console.error(`Usage: boop add <capability>`);
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ const targetDir = process.cwd();
18
+ // --- Phase 1: Pre-flight (read-only) ---
19
+ let plan;
20
+ try {
21
+ plan = (0, preflight_1.planAdd)(targetDir, capabilityName);
22
+ }
23
+ catch (err) {
24
+ if (err instanceof SyntaxError) {
25
+ console.error(`\x1b[31mERR\x1b[0m .boop/config.json is corrupt (invalid JSON). Delete it and run \`boop init\` to recreate.`);
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ if (err instanceof preflight_1.PreflightError) {
30
+ console.error(`\x1b[31mERR\x1b[0m Pre-flight failed:\n`);
31
+ for (const check of err.checks) {
32
+ const icon = check.passed ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
33
+ const msg = check.message ? ` — ${check.message}` : '';
34
+ console.error(` ${icon} ${check.name}${msg}`);
35
+ }
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ throw err;
40
+ }
41
+ const { manifest, cap, projectManifest, filesToCopy, warnings } = plan;
42
+ // Emit any warnings from pre-flight
43
+ for (const w of warnings) {
44
+ console.error(w);
45
+ }
46
+ // --- Dry run: print manifest and exit ---
47
+ if (options.dryRun) {
48
+ if (options.json) {
49
+ console.log(JSON.stringify(manifest, null, 2));
50
+ }
51
+ else {
52
+ console.log((0, execute_1.formatManifest)(manifest));
53
+ }
54
+ return;
55
+ }
56
+ // --- Phase 2: Execute from manifest ---
57
+ const result = (0, execute_1.executeManifest)(manifest, { install: options.install });
58
+ // Save audit log
59
+ try {
60
+ (0, execute_1.saveManifestLog)(manifest);
61
+ }
62
+ catch {
63
+ // Non-fatal — log dir might not be writable
64
+ }
65
+ if (!result.success) {
66
+ console.error(`\nWarning: ${result.failed.length} task(s) failed during execution.`);
67
+ }
68
+ // --- Output ---
69
+ const fileEntries = filesToCopy.map(f => ({ path: f }));
70
+ const skillEntries = [];
71
+ if (cap.skills) {
72
+ for (const s of cap.skills) {
73
+ skillEntries.push({ path: `skills/${s}` });
74
+ }
75
+ }
76
+ const hasEnv = cap.env && cap.env.length > 0;
77
+ const nextSteps = [];
78
+ if (hasEnv) {
79
+ for (const e of cap.env) {
80
+ const example = e.default ? ` (e.g. ${(0, interpolate_1.interpolate)(e.default, { projectName: projectManifest.name })})` : '';
81
+ nextSteps.push({ text: `Set ${e.name} in .env${example}` });
82
+ }
83
+ }
84
+ if (cap.scripts) {
85
+ const scriptNames = Object.keys(cap.scripts);
86
+ if (scriptNames.length > 0) {
87
+ const first = scriptNames[0];
88
+ const desc = cap.scripts[first].description;
89
+ nextSteps.push({ text: `Run \`npm run ${first}\` — ${desc}` });
90
+ }
91
+ }
92
+ nextSteps.push({ text: 'Run `npm run dev` to start developing' });
93
+ if (options.json) {
94
+ console.log(JSON.stringify({
95
+ capability: capabilityName,
96
+ project: projectManifest.name,
97
+ files: fileEntries,
98
+ skills: skillEntries,
99
+ nextSteps,
100
+ manifest: {
101
+ tasks: manifest.tasks.length,
102
+ completed: result.completed.length,
103
+ failed: result.failed.length,
104
+ skipped: result.skipped.length,
105
+ },
106
+ }, null, 2));
107
+ return;
108
+ }
109
+ // Determine which files were skipped due to conflicts
110
+ const conflictCheck = manifest.preflight.checks.find(c => c.name === 'no-file-conflicts');
111
+ const skippedFiles = new Set();
112
+ const copyTask = manifest.tasks.find(t => t.op === 'copy-files');
113
+ if (copyTask?.data) {
114
+ const conflicts = copyTask.data.conflicts || [];
115
+ for (const f of conflicts)
116
+ skippedFiles.add(f);
117
+ }
118
+ // Human-readable output
119
+ console.log(`\n${'─'.repeat(40)}`);
120
+ console.log(`Added capability "${capabilityName}" to ${projectManifest.name}\n`);
121
+ if (fileEntries.length > 0) {
122
+ console.log('Files:');
123
+ for (const f of fileEntries) {
124
+ if (skippedFiles.has(f.path)) {
125
+ console.log(` ◐ ${f.path} (skipped — already exists)`);
126
+ }
127
+ else {
128
+ console.log(` ✓ ${f.path}`);
129
+ }
130
+ }
131
+ }
132
+ if (skillEntries.length > 0) {
133
+ console.log('\nSkills available:');
134
+ for (const s of skillEntries) {
135
+ console.log(` ${s.path}`);
136
+ }
137
+ }
138
+ // Show capability-specific post-install guidance if available
139
+ if (cap.postInstall) {
140
+ const pi = cap.postInstall;
141
+ const vars = { projectName: projectManifest.name, capabilityName };
142
+ if (pi.prerequisites && pi.prerequisites.length > 0) {
143
+ console.log('\nPrerequisites:');
144
+ for (const prereq of pi.prerequisites) {
145
+ console.log(` - ${(0, interpolate_1.interpolate)(prereq, vars)}`);
146
+ }
147
+ }
148
+ if (pi.steps && pi.steps.length > 0) {
149
+ console.log('\nSetup steps:');
150
+ for (let i = 0; i < pi.steps.length; i++) {
151
+ console.log(` ${i + 1}. ${(0, interpolate_1.interpolate)(pi.steps[i], vars)}`);
152
+ }
153
+ }
154
+ if (pi.verify) {
155
+ console.log('\nVerify with:');
156
+ console.log(` ${(0, interpolate_1.interpolate)(pi.verify, vars)}`);
157
+ }
158
+ }
159
+ else {
160
+ console.log('\nNext steps:');
161
+ for (let i = 0; i < nextSteps.length; i++) {
162
+ console.log(` ${i + 1}. ${nextSteps[i].text}`);
163
+ }
164
+ }
165
+ if (hasEnv) {
166
+ console.log(`\nEnvironment variables:`);
167
+ for (const e of cap.env) {
168
+ const required = e.required ? ` (required at ${e.required})` : '';
169
+ console.log(` ${e.name}${required}`);
170
+ }
171
+ }
172
+ console.log(`\nRun \`boop status\` to check configuration.`);
173
+ }
@@ -0,0 +1,5 @@
1
+ export interface AdoptOptions {
2
+ install?: boolean;
3
+ json?: boolean;
4
+ }
5
+ export declare function runAdopt(capabilityName: string | undefined, options: AdoptOptions): void;
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runAdopt = runAdopt;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const config_1 = require("../lib/config");
41
+ const capability_1 = require("../lib/capability");
42
+ const project_1 = require("../lib/project");
43
+ const safe_io_1 = require("../lib/safe-io");
44
+ function runAdopt(capabilityName, options) {
45
+ // 0. Validate capability argument
46
+ if (!capabilityName) {
47
+ console.error(`\x1b[31mERR\x1b[0m Missing capability argument.\n`);
48
+ console.error('Usage: boop adopt <capability>');
49
+ process.exitCode = 1;
50
+ return;
51
+ }
52
+ const targetDir = process.cwd();
53
+ const shouldInstall = options.install !== false;
54
+ // 1. Validate we're in a boop project
55
+ let manifest;
56
+ try {
57
+ manifest = (0, project_1.readProjectManifest)(targetDir);
58
+ }
59
+ catch (err) {
60
+ if (err instanceof SyntaxError) {
61
+ console.error('Error: .boop/config.json is corrupt (invalid JSON). Delete it and run `boop init` to recreate.');
62
+ process.exitCode = 1;
63
+ return;
64
+ }
65
+ throw err;
66
+ }
67
+ if (!manifest) {
68
+ console.error('Not a boop project (no .boop/config.json found). Run `boop init` to create one.');
69
+ process.exitCode = 1;
70
+ return;
71
+ }
72
+ // 2. Validate capability is installed
73
+ if (!manifest.capabilities.includes(capabilityName)) {
74
+ console.error(`Error: Capability "${capabilityName}" is not installed. Run \`boop add ${capabilityName}\` first.`);
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ // 3. Resolve capability and verify it has a package
79
+ const cap = (0, capability_1.getCapability)(capabilityName);
80
+ if (!cap) {
81
+ console.error(`Error: Capability "${capabilityName}" not found.`);
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+ if (!cap.package) {
86
+ console.error(`Error: Capability "${capabilityName}" has no package to adopt.`);
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+ const pkgName = cap.package.replace(/^@boopkit\//, '');
91
+ const pkgDir = path.join((0, config_1.getBoopRoot)(), 'packages', pkgName);
92
+ // Check already adopted
93
+ if (manifest.adopted && manifest.adopted.includes(pkgName)) {
94
+ console.error(`Error: Package "${cap.package}" is already adopted.`);
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+ // 4. Warn if other installed packages depend on the package being adopted
99
+ warnDependentPackages(targetDir, cap.package, pkgName);
100
+ // 5. Copy package src/ → src/adopted/<pkgName>/
101
+ const srcDir = path.join(pkgDir, 'src');
102
+ const adoptedSrcDir = path.join(targetDir, 'src', 'adopted', pkgName);
103
+ const copiedSrc = [];
104
+ if (fs.existsSync(srcDir)) {
105
+ (0, safe_io_1.safeCopyDir)(srcDir, adoptedSrcDir, { onConflict: 'skip' });
106
+ copiedSrc.push(...listFilesRecursive(adoptedSrcDir).map(f => path.relative(targetDir, f)));
107
+ }
108
+ // 5. Copy package styles/ → styles/adopted/<pkgName>/
109
+ const stylesDir = path.join(pkgDir, 'styles');
110
+ const adoptedStylesDir = path.join(targetDir, 'styles', 'adopted', pkgName);
111
+ const copiedStyles = [];
112
+ if (fs.existsSync(stylesDir)) {
113
+ (0, safe_io_1.safeCopyDir)(stylesDir, adoptedStylesDir, { onConflict: 'skip' });
114
+ copiedStyles.push(...listFilesRecursive(adoptedStylesDir).map(f => path.relative(targetDir, f)));
115
+ }
116
+ // 6. Rewrite .ts imports
117
+ rewriteTsImports(targetDir, cap.package, pkgName);
118
+ // 7. Rewrite .css imports
119
+ rewriteCssImports(targetDir, cap.package, pkgName);
120
+ // 8. Remove package dependency from package.json
121
+ const pkgJsonPath = path.join(targetDir, 'package.json');
122
+ if (fs.existsSync(pkgJsonPath)) {
123
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
124
+ if (pkg.dependencies && pkg.dependencies[cap.package]) {
125
+ delete pkg.dependencies[cap.package];
126
+ (0, safe_io_1.createBackup)(pkgJsonPath);
127
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
128
+ }
129
+ }
130
+ // 9. Update manifest
131
+ if (!manifest.adopted)
132
+ manifest.adopted = [];
133
+ manifest.adopted.push(pkgName);
134
+ (0, project_1.writeProjectManifest)(targetDir, manifest);
135
+ // 10. Run npm install
136
+ if (shouldInstall) {
137
+ try {
138
+ console.log('\nInstalling dependencies...');
139
+ (0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
140
+ }
141
+ catch {
142
+ console.error('Warning: npm install failed. You can run it manually.');
143
+ }
144
+ }
145
+ // 11. Output
146
+ if (options.json) {
147
+ console.log(JSON.stringify({
148
+ capability: capabilityName,
149
+ package: cap.package,
150
+ adopted: {
151
+ src: copiedSrc,
152
+ styles: copiedStyles,
153
+ },
154
+ }, null, 2));
155
+ return;
156
+ }
157
+ console.log(`\n${'─'.repeat(40)}`);
158
+ console.log(`Adopted "${cap.package}" into ${manifest.name}\n`);
159
+ if (copiedSrc.length > 0) {
160
+ console.log('Source files copied:');
161
+ for (const f of copiedSrc) {
162
+ console.log(` ${f}`);
163
+ }
164
+ }
165
+ if (copiedStyles.length > 0) {
166
+ console.log('\nStyle files copied:');
167
+ for (const f of copiedStyles) {
168
+ console.log(` ${f}`);
169
+ }
170
+ }
171
+ console.log('\nImports rewritten from package to local paths.');
172
+ console.log(`Package "${cap.package}" removed from dependencies.`);
173
+ console.log(`\nYou now own this code — edit freely in src/adopted/${pkgName}/`);
174
+ }
175
+ function listFilesRecursive(dir) {
176
+ const results = [];
177
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
178
+ const fullPath = path.join(dir, entry.name);
179
+ if (entry.isDirectory()) {
180
+ results.push(...listFilesRecursive(fullPath));
181
+ }
182
+ else {
183
+ results.push(fullPath);
184
+ }
185
+ }
186
+ return results;
187
+ }
188
+ function rewriteTsImports(targetDir, packageName, pkgName) {
189
+ const srcDir = path.join(targetDir, 'src');
190
+ if (!fs.existsSync(srcDir))
191
+ return;
192
+ const tsFiles = findFilesWithExt(srcDir, ['.ts', '.tsx']);
193
+ for (const file of tsFiles) {
194
+ let content = fs.readFileSync(file, 'utf-8');
195
+ if (!content.includes(packageName))
196
+ continue;
197
+ const fileDir = path.dirname(file);
198
+ const adoptedDir = path.join(srcDir, 'adopted', pkgName);
199
+ let relativePath = path.relative(fileDir, adoptedDir);
200
+ if (!relativePath.startsWith('.')) {
201
+ relativePath = './' + relativePath;
202
+ }
203
+ // Rewrite bare package imports: from '@boopkit/cap-foo' → from './adopted/cap-foo'
204
+ content = content.replace(new RegExp(`from\\s+['"]${escapeRegex(packageName)}['"]`, 'g'), `from '${relativePath}'`);
205
+ // Rewrite deep imports: from '@boopkit/cap-foo/something' → from './adopted/cap-foo/something'
206
+ content = content.replace(new RegExp(`from\\s+['"]${escapeRegex(packageName)}/([^'"]+)['"]`, 'g'), `from '${relativePath}/$1'`);
207
+ (0, safe_io_1.createBackup)(file);
208
+ fs.writeFileSync(file, content, 'utf-8');
209
+ }
210
+ }
211
+ function rewriteCssImports(targetDir, packageName, pkgName) {
212
+ const stylesDir = path.join(targetDir, 'styles');
213
+ if (!fs.existsSync(stylesDir))
214
+ return;
215
+ const cssFiles = findFilesWithExt(stylesDir, ['.css']);
216
+ for (const file of cssFiles) {
217
+ let content = fs.readFileSync(file, 'utf-8');
218
+ if (!content.includes(packageName))
219
+ continue;
220
+ const fileDir = path.dirname(file);
221
+ const adoptedDir = path.join(stylesDir, 'adopted', pkgName);
222
+ let relativePath = path.relative(fileDir, adoptedDir);
223
+ if (!relativePath.startsWith('.')) {
224
+ relativePath = './' + relativePath;
225
+ }
226
+ // Rewrite @import "@boopkit/cap-foo/styles/x.css" → @import "./adopted/cap-foo/x.css"
227
+ content = content.replace(new RegExp(`@import\\s+["']${escapeRegex(packageName)}/styles/([^"']+)["']`, 'g'), `@import "${relativePath}/$1"`);
228
+ // Rewrite any remaining package references in @import
229
+ content = content.replace(new RegExp(`@import\\s+["']${escapeRegex(packageName)}/([^"']+)["']`, 'g'), `@import "${relativePath}/$1"`);
230
+ (0, safe_io_1.createBackup)(file);
231
+ fs.writeFileSync(file, content, 'utf-8');
232
+ }
233
+ }
234
+ function findFilesWithExt(dir, extensions) {
235
+ const results = [];
236
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
237
+ const fullPath = path.join(dir, entry.name);
238
+ if (entry.isDirectory()) {
239
+ results.push(...findFilesWithExt(fullPath, extensions));
240
+ }
241
+ else if (extensions.some(ext => entry.name.endsWith(ext))) {
242
+ results.push(fullPath);
243
+ }
244
+ }
245
+ return results;
246
+ }
247
+ function escapeRegex(str) {
248
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
249
+ }
250
+ function warnDependentPackages(targetDir, adoptedPackage, adoptedPkgName) {
251
+ const pkgJsonPath = path.join(targetDir, 'package.json');
252
+ if (!fs.existsSync(pkgJsonPath))
253
+ return;
254
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
255
+ const deps = pkg.dependencies || {};
256
+ const dependents = [];
257
+ for (const [depName, depValue] of Object.entries(deps)) {
258
+ if (!depName.startsWith('@boopkit/') || depName === adoptedPackage)
259
+ continue;
260
+ // Resolve the package dir from the file: link
261
+ const depStr = depValue;
262
+ let pkgDir;
263
+ if (depStr.startsWith('file:')) {
264
+ pkgDir = path.resolve(targetDir, depStr.slice(5));
265
+ }
266
+ else {
267
+ const shortName = depName.replace(/^@boopkit\//, '');
268
+ pkgDir = path.join((0, config_1.getBoopRoot)(), 'packages', shortName);
269
+ }
270
+ const depPkgJsonPath = path.join(pkgDir, 'package.json');
271
+ if (!fs.existsSync(depPkgJsonPath))
272
+ continue;
273
+ const depPkg = JSON.parse(fs.readFileSync(depPkgJsonPath, 'utf-8'));
274
+ const allDeps = {
275
+ ...depPkg.dependencies,
276
+ ...depPkg.peerDependencies,
277
+ };
278
+ if (allDeps[adoptedPackage]) {
279
+ dependents.push(depName);
280
+ }
281
+ }
282
+ if (dependents.length > 0) {
283
+ console.error(`Warning: The following installed packages depend on ${adoptedPackage}: ${dependents.join(', ')}. ` +
284
+ `They will continue using the node_modules copy if available.`);
285
+ }
286
+ }
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ profile?: string;
3
+ install?: boolean;
4
+ }
5
+ export declare function runInit(name: string, options: InitOptions): void;