@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
@@ -0,0 +1,17 @@
1
+ import { CapabilityMetadata } from './config';
2
+ /**
3
+ * Resolve the directory of an installed @boopkit/cap-{name} package.
4
+ * Returns null if not found.
5
+ */
6
+ export declare function getPackageDir(name: string): string | null;
7
+ /**
8
+ * List all capabilities by scanning package directory and capabilities/ source tree (test fixtures only).
9
+ * Package resolution takes priority. Returns metadata for each valid capability.
10
+ */
11
+ export declare function listCapabilities(): CapabilityMetadata[];
12
+ /**
13
+ * Get a single capability's metadata by name.
14
+ * Tries package resolution first, falls back to source tree.
15
+ * Returns null if not found in either location.
16
+ */
17
+ export declare function getCapability(name: string): CapabilityMetadata | null;
@@ -0,0 +1,172 @@
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.getPackageDir = getPackageDir;
37
+ exports.listCapabilities = listCapabilities;
38
+ exports.getCapability = getCapability;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const config_1 = require("./config");
42
+ /**
43
+ * Resolve the directory of an installed @boopkit/cap-{name} package.
44
+ * Returns null if not found.
45
+ */
46
+ function getPackageDir(name) {
47
+ const pkgName = `@boopkit/cap-${name}`;
48
+ try {
49
+ const pkgJsonPath = require.resolve(`${pkgName}/package.json`, {
50
+ paths: [(0, config_1.getBoopRoot)()],
51
+ });
52
+ return path.dirname(pkgJsonPath);
53
+ }
54
+ catch {
55
+ // Fall back to scanning for a package whose capability.json has this name
56
+ return findPackageDirByCapabilityName(name);
57
+ }
58
+ }
59
+ /**
60
+ * Read capability metadata from a package's capability.json.
61
+ * Returns null if the package is not found or has no capability.json.
62
+ */
63
+ function getCapabilityFromPackage(name) {
64
+ // getPackageDir already falls back to findPackageDirByCapabilityName on
65
+ // require.resolve failure, so no secondary scan needed here.
66
+ const pkgDir = getPackageDir(name);
67
+ if (!pkgDir)
68
+ return null;
69
+ const capJsonPath = path.join(pkgDir, 'capability.json');
70
+ if (!fs.existsSync(capJsonPath))
71
+ return null;
72
+ // Check the package.json for boopkit.capability === true
73
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
74
+ if (!fs.existsSync(pkgJsonPath))
75
+ return null;
76
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
77
+ if (!pkgJson.boopkit?.capability)
78
+ return null;
79
+ const capJson = JSON.parse(fs.readFileSync(capJsonPath, 'utf-8'));
80
+ return capJson;
81
+ }
82
+ /**
83
+ * Scan node_modules/@boopkit/cap-* to find a package whose capability.json
84
+ * has the given capability name. Handles mismatches between capability name
85
+ * and package name (e.g. "users-and-accounts" → @boopkit/cap-users).
86
+ */
87
+ function findPackageDirByCapabilityName(name) {
88
+ const nodeModulesBoopkit = path.join((0, config_1.getBoopRoot)(), 'node_modules', '@boopkit');
89
+ if (!fs.existsSync(nodeModulesBoopkit))
90
+ return null;
91
+ const entries = fs.readdirSync(nodeModulesBoopkit, { withFileTypes: true });
92
+ for (const entry of entries) {
93
+ if ((!entry.isDirectory() && !entry.isSymbolicLink()) || !entry.name.startsWith('cap-'))
94
+ continue;
95
+ const dir = path.join(nodeModulesBoopkit, entry.name);
96
+ const capJsonPath = path.join(dir, 'capability.json');
97
+ if (!fs.existsSync(capJsonPath))
98
+ continue;
99
+ try {
100
+ const capJson = JSON.parse(fs.readFileSync(capJsonPath, 'utf-8'));
101
+ if (capJson.name === name)
102
+ return dir;
103
+ }
104
+ catch {
105
+ continue;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * List all capabilities by scanning package directory and capabilities/ source tree (test fixtures only).
112
+ * Package resolution takes priority. Returns metadata for each valid capability.
113
+ */
114
+ function listCapabilities() {
115
+ const seen = new Set();
116
+ const capabilities = [];
117
+ // Scan node_modules/@boopkit/cap-* for packages with boopkit.capability === true
118
+ const nodeModulesBoopkit = path.join((0, config_1.getBoopRoot)(), 'node_modules', '@boopkit');
119
+ if (fs.existsSync(nodeModulesBoopkit)) {
120
+ const entries = fs.readdirSync(nodeModulesBoopkit, { withFileTypes: true });
121
+ for (const entry of entries) {
122
+ if ((!entry.isDirectory() && !entry.isSymbolicLink()) || !entry.name.startsWith('cap-'))
123
+ continue;
124
+ const capName = entry.name.replace(/^cap-/, '');
125
+ const cap = getCapabilityFromPackage(capName);
126
+ if (cap) {
127
+ seen.add(capName);
128
+ capabilities.push(cap);
129
+ }
130
+ }
131
+ }
132
+ // Fallback: scan capabilities/ source tree for test fixtures
133
+ const capabilitiesDir = path.join((0, config_1.getBoopRoot)(), 'capabilities');
134
+ if (fs.existsSync(capabilitiesDir)) {
135
+ const entries = fs.readdirSync(capabilitiesDir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (!entry.isDirectory())
138
+ continue;
139
+ if (seen.has(entry.name))
140
+ continue; // Package takes priority
141
+ const cap = getCapabilityFromSourceTree(entry.name);
142
+ if (cap) {
143
+ capabilities.push(cap);
144
+ }
145
+ }
146
+ }
147
+ return capabilities.sort((a, b) => a.name.localeCompare(b.name));
148
+ }
149
+ /**
150
+ * Get a single capability's metadata by name.
151
+ * Tries package resolution first, falls back to source tree.
152
+ * Returns null if not found in either location.
153
+ */
154
+ function getCapability(name) {
155
+ // Try package first
156
+ const fromPackage = getCapabilityFromPackage(name);
157
+ if (fromPackage)
158
+ return fromPackage;
159
+ // Fall back to source tree
160
+ return getCapabilityFromSourceTree(name);
161
+ }
162
+ /**
163
+ * Get capability metadata from the capabilities/ source tree (test fixtures only).
164
+ */
165
+ function getCapabilityFromSourceTree(name) {
166
+ const capMd = path.join((0, config_1.getBoopRoot)(), 'capabilities', name, 'capability.md');
167
+ const parsed = (0, config_1.parseFrontmatter)(capMd);
168
+ if (!parsed) {
169
+ return null;
170
+ }
171
+ return parsed.data;
172
+ }
@@ -0,0 +1,70 @@
1
+ /** Root of the boop package (where profiles/ and packages/ live). */
2
+ export declare function getBoopRoot(): string;
3
+ export interface KernelConfig {
4
+ runtime: string;
5
+ languages: string[];
6
+ deploy: string;
7
+ port: number;
8
+ scripts: Record<string, string>;
9
+ dependencies?: string[];
10
+ devDependencies?: string[];
11
+ nodeVersion?: string;
12
+ }
13
+ export interface ProfileMetadata {
14
+ name: string;
15
+ description: string;
16
+ defaultCapabilities: string[];
17
+ kernel: KernelConfig;
18
+ }
19
+ export interface CapabilityScript {
20
+ cmd: string;
21
+ description: string;
22
+ }
23
+ export interface CapabilityHook {
24
+ phase: 'imports' | 'init' | 'preRoutes' | 'postRoutes';
25
+ order?: number;
26
+ /** Raw code injected into generated server file. TRUST BOUNDARY: not escaped or sandboxed. */
27
+ code: string;
28
+ description?: string;
29
+ }
30
+ export interface CapabilityEnvVar {
31
+ name: string;
32
+ description: string;
33
+ default?: string;
34
+ required?: 'boot' | 'runtime';
35
+ }
36
+ export interface CapabilitySetupCheck {
37
+ type: 'env-set' | 'service' | 'database' | 'prisma-migrate' | 'prisma-generate';
38
+ label: string;
39
+ hint?: string;
40
+ }
41
+ export interface CapabilityPostInstall {
42
+ prerequisites?: string[];
43
+ steps?: string[];
44
+ verify?: string;
45
+ setupChecks?: CapabilitySetupCheck[];
46
+ }
47
+ export interface CapabilityMetadata {
48
+ name: string;
49
+ description: string;
50
+ type?: 'application' | 'development' | 'operations' | 'demo';
51
+ requires?: string[];
52
+ dependencies?: string[];
53
+ devDependencies?: string[];
54
+ files?: string[];
55
+ skills?: string[];
56
+ scripts?: Record<string, CapabilityScript>;
57
+ package?: string;
58
+ hooks?: CapabilityHook[];
59
+ env?: CapabilityEnvVar[];
60
+ postInstall?: CapabilityPostInstall;
61
+ }
62
+ export interface ParsedFrontmatter {
63
+ data: Record<string, unknown>;
64
+ content: string;
65
+ }
66
+ /**
67
+ * Parse YAML frontmatter from a markdown file.
68
+ * Returns null if the file does not exist.
69
+ */
70
+ export declare function parseFrontmatter(filePath: string): ParsedFrontmatter | null;
@@ -0,0 +1,59 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getBoopRoot = getBoopRoot;
40
+ exports.parseFrontmatter = parseFrontmatter;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const gray_matter_1 = __importDefault(require("gray-matter"));
44
+ /** Root of the boop package (where profiles/ and packages/ live). */
45
+ function getBoopRoot() {
46
+ return path.resolve(__dirname, '..', '..');
47
+ }
48
+ /**
49
+ * Parse YAML frontmatter from a markdown file.
50
+ * Returns null if the file does not exist.
51
+ */
52
+ function parseFrontmatter(filePath) {
53
+ if (!fs.existsSync(filePath)) {
54
+ return null;
55
+ }
56
+ const raw = fs.readFileSync(filePath, 'utf-8');
57
+ const { data, content } = (0, gray_matter_1.default)(raw);
58
+ return { data, content };
59
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Parse host and port from a PostgreSQL connection URL.
3
+ * Returns null if the URL is missing or unparseable.
4
+ */
5
+ export declare function parseDbHostPort(url: string | undefined): {
6
+ host: string;
7
+ port: number;
8
+ } | null;
9
+ /**
10
+ * Check if a TCP port is reachable (e.g. Postgres is listening).
11
+ * Resolves true/false; never rejects.
12
+ */
13
+ export declare function checkTcpReachable(host: string, port: number, timeoutMs?: number): Promise<boolean>;
14
+ /**
15
+ * Check if a specific database is connectable via psql.
16
+ * Returns true (connectable), false (not connectable), or null (psql not available).
17
+ */
18
+ export declare function checkDatabaseConnectable(dbUrl: string): Promise<boolean | null>;
19
+ /**
20
+ * Check if the _prisma_migrations table exists in the database.
21
+ * Returns true if migrations have been run, false if not, null if psql unavailable.
22
+ */
23
+ export declare function checkMigrationsTable(dbUrl: string): Promise<boolean | null>;
24
+ /**
25
+ * Check if any Prisma migrations exist in the project.
26
+ * Returns true if prisma/migrations/ contains at least one migration subdirectory.
27
+ */
28
+ export declare function hasPrismaMigrations(projectDir: string): boolean;
29
+ /**
30
+ * Check if any .prisma files in the project define models.
31
+ * Returns true if at least one file contains a `model` declaration.
32
+ */
33
+ export declare function hasPrismaModels(projectDir: string): boolean;
34
+ /**
35
+ * Check if the Prisma client has been generated.
36
+ */
37
+ export declare function isPrismaClientGenerated(projectDir: string): boolean;
38
+ /**
39
+ * Check if Prisma schema is in sync with the database.
40
+ * Uses `prisma migrate status` which reports pending migrations.
41
+ * Returns true if in sync, false if not, null if prisma not available.
42
+ */
43
+ export declare function checkPrismaMigrateStatus(projectDir: string): Promise<boolean | null>;
44
+ export interface SetupStep {
45
+ label: string;
46
+ done: boolean;
47
+ hint?: string;
48
+ }
49
+ export interface CheckContext {
50
+ envValues: Map<string, string>;
51
+ projectDir: string;
52
+ projectName: string;
53
+ /** Accumulated results from earlier checks — keyed by check type. */
54
+ results: Map<string, boolean>;
55
+ }
56
+ /**
57
+ * Run all setup checks declared in a capability's setupChecks list.
58
+ * Returns an ordered list of steps with live done/pending status.
59
+ */
60
+ export declare function runSetupChecks(checks: {
61
+ type: string;
62
+ label: string;
63
+ hint?: string;
64
+ }[], envValues: Map<string, string>, projectDir: string, projectName: string): Promise<SetupStep[]>;
@@ -0,0 +1,257 @@
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.parseDbHostPort = parseDbHostPort;
37
+ exports.checkTcpReachable = checkTcpReachable;
38
+ exports.checkDatabaseConnectable = checkDatabaseConnectable;
39
+ exports.checkMigrationsTable = checkMigrationsTable;
40
+ exports.hasPrismaMigrations = hasPrismaMigrations;
41
+ exports.hasPrismaModels = hasPrismaModels;
42
+ exports.isPrismaClientGenerated = isPrismaClientGenerated;
43
+ exports.checkPrismaMigrateStatus = checkPrismaMigrateStatus;
44
+ exports.runSetupChecks = runSetupChecks;
45
+ const child_process_1 = require("child_process");
46
+ const fs = __importStar(require("fs"));
47
+ const net = __importStar(require("net"));
48
+ const path = __importStar(require("path"));
49
+ /**
50
+ * Parse host and port from a PostgreSQL connection URL.
51
+ * Returns null if the URL is missing or unparseable.
52
+ */
53
+ function parseDbHostPort(url) {
54
+ if (!url)
55
+ return null;
56
+ try {
57
+ const parsed = new URL(url);
58
+ return {
59
+ host: parsed.hostname || 'localhost',
60
+ port: parseInt(parsed.port, 10) || 5432,
61
+ };
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ /**
68
+ * Check if a TCP port is reachable (e.g. Postgres is listening).
69
+ * Resolves true/false; never rejects.
70
+ */
71
+ function checkTcpReachable(host, port, timeoutMs = 2000) {
72
+ return new Promise((resolve) => {
73
+ const socket = new net.Socket();
74
+ socket.setTimeout(timeoutMs);
75
+ socket.once('connect', () => {
76
+ socket.destroy();
77
+ resolve(true);
78
+ });
79
+ socket.once('timeout', () => {
80
+ socket.destroy();
81
+ resolve(false);
82
+ });
83
+ socket.once('error', () => {
84
+ socket.destroy();
85
+ resolve(false);
86
+ });
87
+ socket.connect(port, host);
88
+ });
89
+ }
90
+ /**
91
+ * Check if a specific database is connectable via psql.
92
+ * Returns true (connectable), false (not connectable), or null (psql not available).
93
+ */
94
+ function checkDatabaseConnectable(dbUrl) {
95
+ return new Promise((resolve) => {
96
+ (0, child_process_1.execFile)('psql', [dbUrl, '-c', 'SELECT 1'], { timeout: 3000 }, (error) => {
97
+ if (error) {
98
+ if ('code' in error && error.code === 'ENOENT') {
99
+ resolve(null); // psql not installed
100
+ }
101
+ else {
102
+ resolve(false); // connection failed (db doesn't exist, auth error, etc.)
103
+ }
104
+ }
105
+ else {
106
+ resolve(true);
107
+ }
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Check if the _prisma_migrations table exists in the database.
113
+ * Returns true if migrations have been run, false if not, null if psql unavailable.
114
+ */
115
+ function checkMigrationsTable(dbUrl) {
116
+ return new Promise((resolve) => {
117
+ (0, child_process_1.execFile)('psql', [dbUrl, '-tAc', "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = '_prisma_migrations')"], { timeout: 3000 }, (error, stdout) => {
118
+ if (error) {
119
+ if ('code' in error && error.code === 'ENOENT') {
120
+ resolve(null);
121
+ }
122
+ else {
123
+ resolve(false);
124
+ }
125
+ }
126
+ else {
127
+ resolve(stdout.trim() === 't');
128
+ }
129
+ });
130
+ });
131
+ }
132
+ /**
133
+ * Check if any Prisma migrations exist in the project.
134
+ * Returns true if prisma/migrations/ contains at least one migration subdirectory.
135
+ */
136
+ function hasPrismaMigrations(projectDir) {
137
+ const migrationsDir = path.join(projectDir, 'prisma', 'schema', 'migrations');
138
+ if (!fs.existsSync(migrationsDir))
139
+ return false;
140
+ const entries = fs.readdirSync(migrationsDir, { withFileTypes: true });
141
+ return entries.some(e => e.isDirectory());
142
+ }
143
+ /**
144
+ * Check if any .prisma files in the project define models.
145
+ * Returns true if at least one file contains a `model` declaration.
146
+ */
147
+ function hasPrismaModels(projectDir) {
148
+ const prismaDir = path.join(projectDir, 'prisma', 'schema');
149
+ if (!fs.existsSync(prismaDir))
150
+ return false;
151
+ const files = fs.readdirSync(prismaDir).filter(f => f.endsWith('.prisma'));
152
+ return files.some(f => {
153
+ const content = fs.readFileSync(path.join(prismaDir, f), 'utf-8');
154
+ return /^\s*model\s+\w+/m.test(content);
155
+ });
156
+ }
157
+ /**
158
+ * Check if the Prisma client has been generated.
159
+ */
160
+ function isPrismaClientGenerated(projectDir) {
161
+ return fs.existsSync(path.join(projectDir, 'node_modules', '.prisma', 'client', 'index.js'));
162
+ }
163
+ /**
164
+ * Check if Prisma schema is in sync with the database.
165
+ * Uses `prisma migrate status` which reports pending migrations.
166
+ * Returns true if in sync, false if not, null if prisma not available.
167
+ */
168
+ function checkPrismaMigrateStatus(projectDir) {
169
+ return new Promise((resolve) => {
170
+ (0, child_process_1.execFile)('npx', ['prisma', 'migrate', 'status', '--schema=prisma/schema/'], { timeout: 10000, cwd: projectDir }, (error, stdout) => {
171
+ if (error) {
172
+ if ('code' in error && error.code === 'ENOENT') {
173
+ resolve(null);
174
+ }
175
+ else {
176
+ // prisma migrate status exits non-zero when migrations are pending
177
+ resolve(false);
178
+ }
179
+ }
180
+ else {
181
+ // Trust Prisma's assessment of migration status
182
+ resolve(stdout.includes('Database schema is up to date'));
183
+ }
184
+ });
185
+ });
186
+ }
187
+ /**
188
+ * Run a single setup check by type. Returns true/false for pass/fail.
189
+ * Checks can depend on earlier results via ctx.results.
190
+ */
191
+ async function runCheck(type, ctx) {
192
+ switch (type) {
193
+ case 'env-set': {
194
+ const dbUrl = ctx.envValues.get('DATABASE_URL');
195
+ return !!dbUrl;
196
+ }
197
+ case 'service': {
198
+ const dbUrl = ctx.envValues.get('DATABASE_URL');
199
+ if (!dbUrl)
200
+ return false;
201
+ const hostPort = parseDbHostPort(dbUrl);
202
+ if (!hostPort)
203
+ return false;
204
+ return checkTcpReachable(hostPort.host, hostPort.port);
205
+ }
206
+ case 'database': {
207
+ if (!ctx.results.get('service'))
208
+ return false;
209
+ const dbUrl = ctx.envValues.get('DATABASE_URL');
210
+ if (!dbUrl)
211
+ return false;
212
+ const result = await checkDatabaseConnectable(dbUrl);
213
+ return result === true;
214
+ }
215
+ case 'prisma-migrate': {
216
+ // Skip only if a database check was explicitly run and failed
217
+ if (ctx.results.has('database') && !ctx.results.get('database'))
218
+ return false;
219
+ // If schema has models but no migrations exist, migration hasn't been run
220
+ if (hasPrismaModels(ctx.projectDir) && !hasPrismaMigrations(ctx.projectDir))
221
+ return false;
222
+ const result = await checkPrismaMigrateStatus(ctx.projectDir);
223
+ return result === true;
224
+ }
225
+ case 'prisma-generate': {
226
+ return isPrismaClientGenerated(ctx.projectDir);
227
+ }
228
+ default:
229
+ return false;
230
+ }
231
+ }
232
+ /**
233
+ * Run all setup checks declared in a capability's setupChecks list.
234
+ * Returns an ordered list of steps with live done/pending status.
235
+ */
236
+ async function runSetupChecks(checks, envValues, projectDir, projectName) {
237
+ const ctx = { envValues, projectDir, projectName, results: new Map() };
238
+ const steps = [];
239
+ for (const check of checks) {
240
+ const done = await runCheck(check.type, ctx);
241
+ ctx.results.set(check.type, done);
242
+ let hint = check.hint;
243
+ // When prisma-migrate fails and no migrations exist yet, the first
244
+ // `prisma migrate dev` will prompt interactively for a name.
245
+ // Adjust the hint so users (and agents) pass --name to avoid the prompt.
246
+ // Only apply fallback if the capability hint doesn't already specify --name.
247
+ if (check.type === 'prisma-migrate' && !done && !hasPrismaMigrations(ctx.projectDir) && (!hint || !hint.includes('--name'))) {
248
+ hint = 'npm run db:migrate -- --name init';
249
+ }
250
+ steps.push({
251
+ label: check.label,
252
+ done,
253
+ hint,
254
+ });
255
+ }
256
+ return steps;
257
+ }
@@ -0,0 +1,25 @@
1
+ export interface EnvEntry {
2
+ name: string;
3
+ description: string;
4
+ default?: string;
5
+ capability: string;
6
+ }
7
+ /**
8
+ * Generate or append to .env and .env.example files from capability env declarations.
9
+ * - Creates files if they don't exist
10
+ * - Appends new entries without duplicating existing ones
11
+ * - .env gets empty values (so the var is unset and capabilities degrade gracefully)
12
+ * - .env.example gets interpolated defaults (as documentation for the developer)
13
+ */
14
+ export declare function generateEnvFiles(dir: string, entries: EnvEntry[], projectName: string): void;
15
+ /**
16
+ * Read a .env file and return the set of variable names that have non-empty values.
17
+ * Returns an empty set if the file does not exist.
18
+ */
19
+ export declare function readEnvKeys(filePath: string): Set<string>;
20
+ /**
21
+ * Read a .env file and return a map of variable names to their values.
22
+ * Only includes variables with non-empty values.
23
+ * Returns an empty map if the file does not exist.
24
+ */
25
+ export declare function readEnvValues(filePath: string): Map<string, string>;