@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.
- package/README.md +65 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +85 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.js +173 -0
- package/dist/commands/adopt.d.ts +5 -0
- package/dist/commands/adopt.js +286 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +295 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +105 -0
- package/dist/commands/loop.d.ts +63 -0
- package/dist/commands/loop.js +765 -0
- package/dist/commands/status.d.ts +40 -0
- package/dist/commands/status.js +303 -0
- package/dist/commands/status.test.d.ts +1 -0
- package/dist/commands/status.test.js +118 -0
- package/dist/commands/update.d.ts +8 -0
- package/dist/commands/update.js +204 -0
- package/dist/lib/capability.d.ts +17 -0
- package/dist/lib/capability.js +172 -0
- package/dist/lib/config.d.ts +70 -0
- package/dist/lib/config.js +59 -0
- package/dist/lib/db-status.d.ts +64 -0
- package/dist/lib/db-status.js +257 -0
- package/dist/lib/env.d.ts +25 -0
- package/dist/lib/env.js +116 -0
- package/dist/lib/execute.d.ts +21 -0
- package/dist/lib/execute.js +228 -0
- package/dist/lib/interpolate.d.ts +4 -0
- package/dist/lib/interpolate.js +13 -0
- package/dist/lib/manifest.d.ts +54 -0
- package/dist/lib/manifest.js +11 -0
- package/dist/lib/preflight.d.ts +31 -0
- package/dist/lib/preflight.js +402 -0
- package/dist/lib/profile.d.ts +11 -0
- package/dist/lib/profile.js +73 -0
- package/dist/lib/project.d.ts +36 -0
- package/dist/lib/project.js +71 -0
- package/dist/lib/project.test.d.ts +1 -0
- package/dist/lib/project.test.js +118 -0
- package/dist/lib/rewire.d.ts +19 -0
- package/dist/lib/rewire.js +222 -0
- package/dist/lib/safe-io.d.ts +66 -0
- package/dist/lib/safe-io.js +167 -0
- package/dist/lib/scaffold.d.ts +87 -0
- package/dist/lib/scaffold.js +548 -0
- package/dist/lib/update.d.ts +73 -0
- package/dist/lib/update.js +379 -0
- package/package.json +46 -0
- package/profiles/web-app/profile.md +113 -0
- package/profiles/web-app/reference/architecture.ref.md +65 -0
- package/profiles/web-app/reference/message-queue-patterns.ref.md +247 -0
package/dist/lib/env.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
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.generateEnvFiles = generateEnvFiles;
|
|
37
|
+
exports.readEnvKeys = readEnvKeys;
|
|
38
|
+
exports.readEnvValues = readEnvValues;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const interpolate_1 = require("./interpolate");
|
|
43
|
+
/**
|
|
44
|
+
* Generate or append to .env and .env.example files from capability env declarations.
|
|
45
|
+
* - Creates files if they don't exist
|
|
46
|
+
* - Appends new entries without duplicating existing ones
|
|
47
|
+
* - .env gets empty values (so the var is unset and capabilities degrade gracefully)
|
|
48
|
+
* - .env.example gets interpolated defaults (as documentation for the developer)
|
|
49
|
+
*/
|
|
50
|
+
function generateEnvFiles(dir, entries, projectName) {
|
|
51
|
+
const envPath = path.join(dir, '.env');
|
|
52
|
+
const examplePath = path.join(dir, '.env.example');
|
|
53
|
+
const existingEnv = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
|
|
54
|
+
const existingExample = fs.existsSync(examplePath) ? fs.readFileSync(examplePath, 'utf-8') : '';
|
|
55
|
+
const newEnvLines = buildNewEntries(existingEnv, entries, projectName, false);
|
|
56
|
+
const newExampleLines = buildNewEntries(existingExample, entries, projectName, true);
|
|
57
|
+
fs.writeFileSync(envPath, existingEnv + newEnvLines, 'utf-8');
|
|
58
|
+
fs.writeFileSync(examplePath, existingExample + newExampleLines, 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
function buildNewEntries(existingContent, entries, projectName, includeDefaults) {
|
|
61
|
+
const lines = [];
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
// Skip if this env var already exists in the file
|
|
64
|
+
const pattern = new RegExp(`^${escapeRegex(entry.name)}=`, 'm');
|
|
65
|
+
if (pattern.test(existingContent)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const value = includeDefaults && entry.default
|
|
69
|
+
? (0, interpolate_1.interpolate)(entry.default, { projectName, username: os.userInfo().username })
|
|
70
|
+
: '';
|
|
71
|
+
lines.push(`# ${entry.name} — ${entry.description} (${entry.capability} capability)`);
|
|
72
|
+
lines.push(`${entry.name}=${value}`);
|
|
73
|
+
}
|
|
74
|
+
return lines.length > 0 ? lines.join('\n') + '\n' : '';
|
|
75
|
+
}
|
|
76
|
+
function escapeRegex(str) {
|
|
77
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read a .env file and return the set of variable names that have non-empty values.
|
|
81
|
+
* Returns an empty set if the file does not exist.
|
|
82
|
+
*/
|
|
83
|
+
function readEnvKeys(filePath) {
|
|
84
|
+
const values = readEnvValues(filePath);
|
|
85
|
+
return new Set(values.keys());
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read a .env file and return a map of variable names to their values.
|
|
89
|
+
* Only includes variables with non-empty values.
|
|
90
|
+
* Returns an empty map if the file does not exist.
|
|
91
|
+
*/
|
|
92
|
+
function readEnvValues(filePath) {
|
|
93
|
+
if (!fs.existsSync(filePath)) {
|
|
94
|
+
return new Map();
|
|
95
|
+
}
|
|
96
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
97
|
+
const entries = new Map();
|
|
98
|
+
for (const line of content.split('\n')) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
101
|
+
continue;
|
|
102
|
+
const eqIndex = trimmed.indexOf('=');
|
|
103
|
+
if (eqIndex === -1)
|
|
104
|
+
continue;
|
|
105
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
106
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
107
|
+
// Strip surrounding quotes (single or double)
|
|
108
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
109
|
+
value = value.slice(1, -1);
|
|
110
|
+
}
|
|
111
|
+
if (key && value) {
|
|
112
|
+
entries.set(key, value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return entries;
|
|
116
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute phase for `boop add`.
|
|
3
|
+
*
|
|
4
|
+
* Walks a pre-flight manifest and performs each operation using the
|
|
5
|
+
* safe-io utilities. Each task is marked done/failed/skipped as it executes.
|
|
6
|
+
*/
|
|
7
|
+
import { AddManifest, ExecuteResult } from './manifest';
|
|
8
|
+
/**
|
|
9
|
+
* Execute all tasks in a manifest. Returns the result with per-task status.
|
|
10
|
+
*/
|
|
11
|
+
export declare function executeManifest(manifest: AddManifest, options?: {
|
|
12
|
+
install?: boolean;
|
|
13
|
+
}): ExecuteResult;
|
|
14
|
+
/**
|
|
15
|
+
* Save a completed manifest to .boop/logs/ for audit trail.
|
|
16
|
+
*/
|
|
17
|
+
export declare function saveManifestLog(manifest: AddManifest): void;
|
|
18
|
+
/**
|
|
19
|
+
* Format a manifest as a human-readable summary (for --dry-run output).
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatManifest(manifest: AddManifest): string;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Execute phase for `boop add`.
|
|
4
|
+
*
|
|
5
|
+
* Walks a pre-flight manifest and performs each operation using the
|
|
6
|
+
* safe-io utilities. Each task is marked done/failed/skipped as it executes.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.executeManifest = executeManifest;
|
|
43
|
+
exports.saveManifestLog = saveManifestLog;
|
|
44
|
+
exports.formatManifest = formatManifest;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const scaffold_1 = require("./scaffold");
|
|
49
|
+
const project_1 = require("./project");
|
|
50
|
+
const env_1 = require("./env");
|
|
51
|
+
const rewire_1 = require("./rewire");
|
|
52
|
+
const safe_io_1 = require("./safe-io");
|
|
53
|
+
/**
|
|
54
|
+
* Execute all tasks in a manifest. Returns the result with per-task status.
|
|
55
|
+
*/
|
|
56
|
+
function executeManifest(manifest, options = {}) {
|
|
57
|
+
const shouldInstall = options.install !== false;
|
|
58
|
+
const completed = [];
|
|
59
|
+
const failed = [];
|
|
60
|
+
const skipped = [];
|
|
61
|
+
// Back up each mutable target file once before executing any tasks
|
|
62
|
+
const backedUp = new Set();
|
|
63
|
+
for (const task of manifest.tasks) {
|
|
64
|
+
if (task.op === 'copy-files' || task.op === 'run-command')
|
|
65
|
+
continue;
|
|
66
|
+
if (task.target && fs.existsSync(task.target) && !backedUp.has(task.target)) {
|
|
67
|
+
(0, safe_io_1.createBackup)(task.target);
|
|
68
|
+
backedUp.add(task.target);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const task of manifest.tasks) {
|
|
72
|
+
// Skip npm install if install=false
|
|
73
|
+
if (task.op === 'run-command' && !shouldInstall) {
|
|
74
|
+
task.status = 'skipped';
|
|
75
|
+
skipped.push(task);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
executeTask(task, manifest);
|
|
80
|
+
task.status = 'done';
|
|
81
|
+
completed.push(task);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
task.status = 'failed';
|
|
85
|
+
failed.push(task);
|
|
86
|
+
// Log but continue — best effort for remaining tasks
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
console.error(`Warning: Task "${task.detail}" failed: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
success: failed.length === 0,
|
|
93
|
+
completed,
|
|
94
|
+
failed,
|
|
95
|
+
skipped,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function executeTask(task, manifest) {
|
|
99
|
+
const { targetDir, capability: capabilityName } = manifest;
|
|
100
|
+
switch (task.op) {
|
|
101
|
+
case 'copy-files': {
|
|
102
|
+
(0, scaffold_1.copyCapabilityFiles)(targetDir, capabilityName);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'merge-deps': {
|
|
106
|
+
const data = task.data;
|
|
107
|
+
(0, scaffold_1.mergePackageJson)(targetDir, data.dependencies, data.devDependencies, undefined, { skipBackup: true });
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'merge-scripts': {
|
|
111
|
+
const data = task.data;
|
|
112
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
113
|
+
if (fs.existsSync(pkgPath)) {
|
|
114
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
115
|
+
if (!pkg.scripts)
|
|
116
|
+
pkg.scripts = {};
|
|
117
|
+
for (const [scriptName, scriptDef] of Object.entries(data.scripts)) {
|
|
118
|
+
if (!(scriptName in pkg.scripts)) {
|
|
119
|
+
pkg.scripts[scriptName] = scriptDef.cmd;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'link-package': {
|
|
127
|
+
const data = task.data;
|
|
128
|
+
(0, scaffold_1.mergePackageJson)(targetDir, undefined, undefined, {
|
|
129
|
+
[data.packageName]: (0, scaffold_1.getBoopkitVersion)(data.packageName),
|
|
130
|
+
}, { skipBackup: true });
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'set-env': {
|
|
134
|
+
const data = task.data;
|
|
135
|
+
(0, env_1.generateEnvFiles)(targetDir, data.entries, data.projectName);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'inject-hook': {
|
|
139
|
+
const data = task.data;
|
|
140
|
+
const appPath = path.join(targetDir, 'src', 'app.ts');
|
|
141
|
+
const serverPath = path.join(targetDir, 'src', 'server.ts');
|
|
142
|
+
// New structure: all hooks in app.ts; legacy: all in server.ts
|
|
143
|
+
const targetFile = fs.existsSync(appPath) ? appPath : serverPath;
|
|
144
|
+
if (fs.existsSync(targetFile)) {
|
|
145
|
+
(0, rewire_1.rewireServerFile)(targetFile, capabilityName, data.hooks, {
|
|
146
|
+
needsEnv: data.needsEnv,
|
|
147
|
+
skipBackup: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'inject-css': {
|
|
153
|
+
const data = task.data;
|
|
154
|
+
(0, scaffold_1.injectCssImports)(targetDir, data.cssCapMap, { skipBackup: true });
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'generate-skills-index': {
|
|
158
|
+
(0, scaffold_1.generateSkillsIndex)(targetDir);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'update-manifest': {
|
|
162
|
+
const data = task.data;
|
|
163
|
+
const projectManifest = (0, project_1.readProjectManifest)(targetDir);
|
|
164
|
+
if (projectManifest) {
|
|
165
|
+
projectManifest.capabilities.push(capabilityName);
|
|
166
|
+
if (data.hasEnv && data.envVars) {
|
|
167
|
+
if (!projectManifest.capabilityConfig)
|
|
168
|
+
projectManifest.capabilityConfig = {};
|
|
169
|
+
const envConfig = {};
|
|
170
|
+
for (const e of data.envVars) {
|
|
171
|
+
envConfig[e.name] = { required: e.required, set: false };
|
|
172
|
+
}
|
|
173
|
+
const capConfig = { env: envConfig };
|
|
174
|
+
projectManifest.capabilityConfig[capabilityName] = capConfig;
|
|
175
|
+
}
|
|
176
|
+
(0, project_1.writeProjectManifest)(targetDir, projectManifest);
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'run-command': {
|
|
181
|
+
const data = task.data;
|
|
182
|
+
console.log(`\nRunning ${data.command}...`);
|
|
183
|
+
(0, child_process_1.execSync)(data.command, { cwd: targetDir, stdio: 'inherit' });
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Save a completed manifest to .boop/logs/ for audit trail.
|
|
190
|
+
*/
|
|
191
|
+
function saveManifestLog(manifest) {
|
|
192
|
+
const logsDir = path.join(manifest.targetDir, '.boop', 'logs');
|
|
193
|
+
if (!fs.existsSync(logsDir)) {
|
|
194
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
const timestamp = manifest.timestamp.replace(/[:.]/g, '-');
|
|
197
|
+
const logPath = path.join(logsDir, `add-${manifest.capability}-${timestamp}.json`);
|
|
198
|
+
fs.writeFileSync(logPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Format a manifest as a human-readable summary (for --dry-run output).
|
|
202
|
+
*/
|
|
203
|
+
function formatManifest(manifest) {
|
|
204
|
+
const lines = [];
|
|
205
|
+
lines.push(`Plan: boop add ${manifest.capability}`);
|
|
206
|
+
lines.push(`Target: ${manifest.targetDir}`);
|
|
207
|
+
lines.push('');
|
|
208
|
+
// Pre-flight checks
|
|
209
|
+
lines.push('Pre-flight checks:');
|
|
210
|
+
for (const check of manifest.preflight.checks) {
|
|
211
|
+
const hasWarning = check.passed && check.message;
|
|
212
|
+
const icon = !check.passed ? '✗' : hasWarning ? '◐' : '✓';
|
|
213
|
+
const msg = check.message ? ` — ${check.message}` : '';
|
|
214
|
+
lines.push(` ${icon} ${check.name}${msg}`);
|
|
215
|
+
}
|
|
216
|
+
lines.push('');
|
|
217
|
+
// Tasks
|
|
218
|
+
lines.push(`Tasks (${manifest.tasks.length}):`);
|
|
219
|
+
for (let i = 0; i < manifest.tasks.length; i++) {
|
|
220
|
+
const task = manifest.tasks[i];
|
|
221
|
+
lines.push(` ${i + 1}. [${task.op}] ${task.detail}`);
|
|
222
|
+
if (task.source) {
|
|
223
|
+
lines.push(` from: ${task.source}`);
|
|
224
|
+
}
|
|
225
|
+
lines.push(` to: ${task.target}`);
|
|
226
|
+
}
|
|
227
|
+
return lines.join('\n');
|
|
228
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.interpolate = interpolate;
|
|
4
|
+
/**
|
|
5
|
+
* Replace ${name} placeholders in a string with values from a vars map.
|
|
6
|
+
*/
|
|
7
|
+
function interpolate(str, vars) {
|
|
8
|
+
let result = str;
|
|
9
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
10
|
+
result = result.split(`\${${name}}`).join(value);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the two-phase pre-flight → execute architecture.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 (pre-flight) builds an AddManifest — a complete, ordered list of
|
|
5
|
+
* every mutation that `boop add` will perform.
|
|
6
|
+
*
|
|
7
|
+
* Phase 2 (execute) walks the manifest, performing each operation and
|
|
8
|
+
* updating task status.
|
|
9
|
+
*/
|
|
10
|
+
export type ManifestOp = 'copy-files' | 'merge-deps' | 'merge-scripts' | 'link-package' | 'inject-hook' | 'inject-css' | 'set-env' | 'generate-skills-index' | 'update-manifest' | 'run-command';
|
|
11
|
+
export type TaskStatus = 'pending' | 'done' | 'failed' | 'skipped';
|
|
12
|
+
export interface ManifestTask {
|
|
13
|
+
op: ManifestOp;
|
|
14
|
+
/** Where data comes from (capability dir, package dir, etc.) */
|
|
15
|
+
source?: string;
|
|
16
|
+
/** What gets modified in the developer's project */
|
|
17
|
+
target: string;
|
|
18
|
+
/** Human-readable description of what this task does */
|
|
19
|
+
detail: string;
|
|
20
|
+
/** Execution status — starts as 'pending', updated during execute phase */
|
|
21
|
+
status: TaskStatus;
|
|
22
|
+
/** Additional data needed for execution (op-specific) */
|
|
23
|
+
data?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface PreflightCheck {
|
|
26
|
+
name: string;
|
|
27
|
+
passed: boolean;
|
|
28
|
+
message?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PreflightResult {
|
|
31
|
+
passed: boolean;
|
|
32
|
+
checks: PreflightCheck[];
|
|
33
|
+
}
|
|
34
|
+
export interface AddManifest {
|
|
35
|
+
/** Capability being added */
|
|
36
|
+
capability: string;
|
|
37
|
+
/** Target project directory */
|
|
38
|
+
targetDir: string;
|
|
39
|
+
/** ISO 8601 timestamp when the plan was created */
|
|
40
|
+
timestamp: string;
|
|
41
|
+
/** Results of all pre-flight checks */
|
|
42
|
+
preflight: PreflightResult;
|
|
43
|
+
/** Ordered list of operations to perform */
|
|
44
|
+
tasks: ManifestTask[];
|
|
45
|
+
}
|
|
46
|
+
export interface ExecuteResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
/** Tasks that were executed */
|
|
49
|
+
completed: ManifestTask[];
|
|
50
|
+
/** Tasks that failed */
|
|
51
|
+
failed: ManifestTask[];
|
|
52
|
+
/** Tasks that were skipped */
|
|
53
|
+
skipped: ManifestTask[];
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for the two-phase pre-flight → execute architecture.
|
|
4
|
+
*
|
|
5
|
+
* Phase 1 (pre-flight) builds an AddManifest — a complete, ordered list of
|
|
6
|
+
* every mutation that `boop add` will perform.
|
|
7
|
+
*
|
|
8
|
+
* Phase 2 (execute) walks the manifest, performing each operation and
|
|
9
|
+
* updating task status.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-flight planning for `boop add`.
|
|
3
|
+
*
|
|
4
|
+
* Reads all inputs, validates everything, and produces a manifest of tasks
|
|
5
|
+
* to execute — without writing a single file.
|
|
6
|
+
*/
|
|
7
|
+
import { CapabilityMetadata } from './config';
|
|
8
|
+
import { BoopManifest } from './project';
|
|
9
|
+
import { AddManifest, PreflightCheck } from './manifest';
|
|
10
|
+
export declare class PreflightError extends Error {
|
|
11
|
+
readonly checks: PreflightCheck[];
|
|
12
|
+
constructor(message: string, checks: PreflightCheck[]);
|
|
13
|
+
}
|
|
14
|
+
export interface PlanAddResult {
|
|
15
|
+
manifest: AddManifest;
|
|
16
|
+
/** Resolved capability metadata (for use in output formatting) */
|
|
17
|
+
cap: CapabilityMetadata;
|
|
18
|
+
/** Current project manifest (for use in output formatting) */
|
|
19
|
+
projectManifest: BoopManifest;
|
|
20
|
+
/** Files that would be copied (relative paths) */
|
|
21
|
+
filesToCopy: string[];
|
|
22
|
+
/** Warnings generated during pre-flight (non-fatal) */
|
|
23
|
+
warnings: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Plan a `boop add` operation. Reads filesystem, validates everything,
|
|
27
|
+
* and produces a manifest. Writes nothing.
|
|
28
|
+
*
|
|
29
|
+
* Throws PreflightError if any check fails.
|
|
30
|
+
*/
|
|
31
|
+
export declare function planAdd(targetDir: string, capabilityName: string): PlanAddResult;
|