@powerhousedao/ph-clint-cli 0.1.0-dev.75 → 0.1.0-dev.77
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/dist/codegen/actions.d.ts +46 -1
- package/dist/codegen/actions.js +424 -1
- package/dist/codegen/builders/cli-package-json.d.ts +8 -1
- package/dist/codegen/builders/cli-package-json.js +128 -1
- package/dist/codegen/builders/index.d.ts +52 -1
- package/dist/codegen/builders/index.js +85 -1
- package/dist/codegen/exec.d.ts +2 -1
- package/dist/codegen/exec.js +14 -1
- package/dist/codegen/generated.d.ts +17 -1
- package/dist/codegen/generated.js +49 -1
- package/dist/codegen/hashes.d.ts +6 -1
- package/dist/codegen/hashes.js +60 -1
- package/dist/codegen/markers.d.ts +36 -1
- package/dist/codegen/markers.js +86 -1
- package/dist/codegen/scaffold.d.ts +72 -1
- package/dist/codegen/scaffold.js +150 -1
- package/dist/codegen/write.d.ts +6 -1
- package/dist/codegen/write.js +32 -1
- package/dist/commands/clint-project-build.d.ts +3 -0
- package/dist/commands/clint-project-init.d.ts +3 -0
- package/dist/commands/clint-project-publish.d.ts +3 -0
- package/dist/commands/clint-project-regen.d.ts +3 -0
- package/dist/commands/clint-skills-sync.d.ts +3 -0
- package/dist/framework.d.ts +18 -0
- package/dist/framework.gen.d.ts +3 -0
- package/dist/main.js +0 -0
- package/dist/skills/sync.d.ts +40 -1
- package/dist/skills/sync.js +119 -1
- package/dist/triggers/publish-trigger.d.ts +3 -0
- package/dist/triggers/spec-change.d.ts +3 -0
- package/package.json +9 -9
- package/dist/codegen/builders/publish-config-ts.d.ts +0 -8
- package/dist/codegen/builders/publish-config-ts.js +0 -32
- package/dist/commands/init.d.ts +0 -57
- package/dist/commands/init.js +0 -168
- package/dist/commands/regen.d.ts +0 -18
- package/dist/commands/regen.js +0 -68
|
@@ -1 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
import type { GenerateProjectResult } from './index.js';
|
|
2
|
+
import type { ClintProjectSpec, ExternalSkill } from '../spec/types.js';
|
|
3
|
+
import type { ProcessRunOptions } from '@powerhousedao/ph-clint';
|
|
4
|
+
export type PostGenAction = {
|
|
5
|
+
kind: 'ph-init';
|
|
6
|
+
targetDir: string;
|
|
7
|
+
appDir: string;
|
|
8
|
+
spec: ClintProjectSpec;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'app-install';
|
|
11
|
+
dir: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'app-ph-install';
|
|
14
|
+
appDir: string;
|
|
15
|
+
packages: string[];
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'app-build';
|
|
18
|
+
dir: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'cli-install';
|
|
21
|
+
dir: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'cli-build';
|
|
24
|
+
dir: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'skills-sync';
|
|
27
|
+
targetDir: string;
|
|
28
|
+
desired: ExternalSkill[];
|
|
29
|
+
};
|
|
30
|
+
export type PostGenActionKind = PostGenAction['kind'];
|
|
31
|
+
export interface PostGenActionContext {
|
|
32
|
+
log: (msg: string) => void;
|
|
33
|
+
runProcess: (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>) => Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
output: string;
|
|
36
|
+
}>;
|
|
37
|
+
skip?: Set<PostGenActionKind>;
|
|
38
|
+
}
|
|
39
|
+
export interface PostGenActionResult {
|
|
40
|
+
action: PostGenAction;
|
|
41
|
+
status: 'success' | 'failed' | 'skipped';
|
|
42
|
+
reason?: string;
|
|
43
|
+
durationMs: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function collectPostGenActions(result: GenerateProjectResult, spec: ClintProjectSpec): Promise<PostGenAction[]>;
|
|
46
|
+
export declare function runPostGenActions(actions: PostGenAction[], ctx: PostGenActionContext): Promise<PostGenActionResult[]>;
|
package/dist/codegen/actions.js
CHANGED
|
@@ -1 +1,424 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Post-generation actions — a typed pipeline that runs after codegen.
|
|
3
|
+
*
|
|
4
|
+
* `collectPostGenActions()` inspects a `GenerateProjectResult` to determine
|
|
5
|
+
* which side-effects (install, build, ph-init, skills-sync) are needed.
|
|
6
|
+
*
|
|
7
|
+
* `runPostGenActions()` executes them sequentially with proper ordering,
|
|
8
|
+
* failure cascading, and log output.
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { getAppPackageName } from '../spec/types.js';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Fixed execution order — index defines precedence
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const ACTION_ORDER = [
|
|
17
|
+
'ph-init',
|
|
18
|
+
'app-install',
|
|
19
|
+
'app-ph-install',
|
|
20
|
+
'app-build',
|
|
21
|
+
'cli-install',
|
|
22
|
+
'cli-build',
|
|
23
|
+
'skills-sync',
|
|
24
|
+
];
|
|
25
|
+
function actionIndex(kind) {
|
|
26
|
+
return ACTION_ORDER.indexOf(kind);
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Dependency map — which actions depend on which (for failure cascading)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/** Returns true if `downstream` depends on `upstream` succeeding. */
|
|
32
|
+
function dependsOn(downstream, upstream) {
|
|
33
|
+
// ph-init → app-install → app-ph-install → app-build → cli-install → cli-build
|
|
34
|
+
// skills-sync is independent of the build chain
|
|
35
|
+
const chain = [
|
|
36
|
+
'ph-init',
|
|
37
|
+
'app-install',
|
|
38
|
+
'app-ph-install',
|
|
39
|
+
'app-build',
|
|
40
|
+
'cli-install',
|
|
41
|
+
'cli-build',
|
|
42
|
+
];
|
|
43
|
+
if (downstream === 'skills-sync')
|
|
44
|
+
return false;
|
|
45
|
+
const di = chain.indexOf(downstream);
|
|
46
|
+
const ui = chain.indexOf(upstream);
|
|
47
|
+
if (di < 0 || ui < 0)
|
|
48
|
+
return false;
|
|
49
|
+
return di > ui;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Helpers
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
async function fileExists(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
await fs.access(filePath);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** External packages that have document types (not the app package). */
|
|
64
|
+
function getExternalPackages(spec) {
|
|
65
|
+
const appPkg = getAppPackageName(spec);
|
|
66
|
+
return spec.packages.filter((p) => p.packageName !== appPkg && p.documentTypes.length > 0);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns package names from `extPkgs` that are not yet registered in
|
|
70
|
+
* the app's `powerhouse.config.json`.
|
|
71
|
+
*/
|
|
72
|
+
async function getMissingPhPackages(appDir, extPkgs) {
|
|
73
|
+
try {
|
|
74
|
+
const raw = await fs.readFile(path.join(appDir, 'powerhouse.config.json'), 'utf8');
|
|
75
|
+
const config = JSON.parse(raw);
|
|
76
|
+
const registered = new Set((config.packages ?? []).map((p) => p.packageName));
|
|
77
|
+
return extPkgs
|
|
78
|
+
.map((p) => p.packageName)
|
|
79
|
+
.filter((name) => !registered.has(name));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// No config file yet (pre-init) — all packages are missing
|
|
83
|
+
return extPkgs.map((p) => p.packageName);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// collectPostGenActions
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
export async function collectPostGenActions(result, spec) {
|
|
90
|
+
const split = result.appDir !== null;
|
|
91
|
+
const relPaths = new Set(result.files.map((f) => f.relativePath));
|
|
92
|
+
// Determine the earliest triggered action in the chain
|
|
93
|
+
let earliest = null;
|
|
94
|
+
if (split) {
|
|
95
|
+
const appFolder = result.appDir ? path.basename(result.appDir) : '';
|
|
96
|
+
const cliFolder = path.basename(result.cliDir);
|
|
97
|
+
// ph-init needed: app dir placeholder written but app not yet initialized.
|
|
98
|
+
// Check the actual filesystem — if package.json already exists on disk,
|
|
99
|
+
// ph init has already run and must not run again.
|
|
100
|
+
const appPkgJsonChanged = relPaths.has(path.join(appFolder, 'package.json'));
|
|
101
|
+
const appGitkeepWritten = relPaths.has(path.join(appFolder, '.gitkeep'));
|
|
102
|
+
// ph-init needed: app dir placeholder written but app not yet initialized.
|
|
103
|
+
// Check the actual filesystem — if package.json already exists on disk,
|
|
104
|
+
// ph init has already run and must not run again.
|
|
105
|
+
if (appGitkeepWritten && !appPkgJsonChanged) {
|
|
106
|
+
const appAlreadyInitialized = result.appDir
|
|
107
|
+
? await fileExists(path.join(result.appDir, 'package.json'))
|
|
108
|
+
: false;
|
|
109
|
+
if (!appAlreadyInitialized) {
|
|
110
|
+
earliest = 'ph-init';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Migration always triggers full rebuild
|
|
114
|
+
if (result.migrated) {
|
|
115
|
+
earliest = earliest ?? 'app-install';
|
|
116
|
+
}
|
|
117
|
+
// App package.json changed
|
|
118
|
+
if (!earliest && appPkgJsonChanged) {
|
|
119
|
+
earliest = 'app-install';
|
|
120
|
+
}
|
|
121
|
+
// App source regenerated (framework.gen.ts, index.ts, etc.)
|
|
122
|
+
if (!earliest) {
|
|
123
|
+
for (const rel of relPaths) {
|
|
124
|
+
if (rel.startsWith(appFolder + '/') &&
|
|
125
|
+
rel.endsWith('.ts')) {
|
|
126
|
+
earliest = 'app-build';
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// CLI package.json changed
|
|
132
|
+
if (!earliest) {
|
|
133
|
+
if (relPaths.has(path.join(cliFolder, 'package.json'))) {
|
|
134
|
+
earliest = 'cli-install';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// CLI source or prompts regenerated (.ts files, agent profile .md files)
|
|
138
|
+
if (!earliest) {
|
|
139
|
+
for (const rel of relPaths) {
|
|
140
|
+
if (rel.startsWith(cliFolder + '/') &&
|
|
141
|
+
(rel.endsWith('.ts') || rel.endsWith('.md'))) {
|
|
142
|
+
earliest = 'cli-build';
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Flat layout
|
|
150
|
+
if (relPaths.has('package.json')) {
|
|
151
|
+
earliest = 'cli-install';
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
for (const rel of relPaths) {
|
|
155
|
+
if (rel.endsWith('.ts') || rel.endsWith('.md')) {
|
|
156
|
+
earliest = 'cli-build';
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Check if external packages need registering in the app
|
|
163
|
+
const extPkgs = split && result.appDir ? getExternalPackages(spec) : [];
|
|
164
|
+
if (split && result.appDir && extPkgs.length > 0) {
|
|
165
|
+
const missing = await getMissingPhPackages(result.appDir, extPkgs);
|
|
166
|
+
if (missing.length > 0) {
|
|
167
|
+
if (!earliest || actionIndex(earliest) > actionIndex('app-ph-install')) {
|
|
168
|
+
earliest = 'app-ph-install';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Build the action list from earliest onward
|
|
173
|
+
const actions = [];
|
|
174
|
+
if (earliest && split && result.appDir) {
|
|
175
|
+
const ei = actionIndex(earliest);
|
|
176
|
+
if (ei <= actionIndex('ph-init')) {
|
|
177
|
+
actions.push({
|
|
178
|
+
kind: 'ph-init',
|
|
179
|
+
targetDir: path.dirname(result.appDir),
|
|
180
|
+
appDir: result.appDir,
|
|
181
|
+
spec,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (ei <= actionIndex('app-install')) {
|
|
185
|
+
actions.push({ kind: 'app-install', dir: result.appDir });
|
|
186
|
+
}
|
|
187
|
+
if (ei <= actionIndex('app-ph-install') && extPkgs.length > 0) {
|
|
188
|
+
const missing = await getMissingPhPackages(result.appDir, extPkgs);
|
|
189
|
+
if (missing.length > 0) {
|
|
190
|
+
actions.push({ kind: 'app-ph-install', appDir: result.appDir, packages: missing });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (ei <= actionIndex('app-build')) {
|
|
194
|
+
// Add install if not already in the list and node_modules is missing
|
|
195
|
+
if (!actions.some(a => a.kind === 'app-install') &&
|
|
196
|
+
!(await fileExists(path.join(result.appDir, 'node_modules')))) {
|
|
197
|
+
actions.push({ kind: 'app-install', dir: result.appDir });
|
|
198
|
+
}
|
|
199
|
+
actions.push({ kind: 'app-build', dir: result.appDir });
|
|
200
|
+
}
|
|
201
|
+
if (ei <= actionIndex('cli-install')) {
|
|
202
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
203
|
+
}
|
|
204
|
+
if (ei <= actionIndex('cli-build')) {
|
|
205
|
+
// Add install if not already in the list and node_modules is missing
|
|
206
|
+
if (!actions.some(a => a.kind === 'cli-install') &&
|
|
207
|
+
!(await fileExists(path.join(result.cliDir, 'node_modules')))) {
|
|
208
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
209
|
+
}
|
|
210
|
+
actions.push({ kind: 'cli-build', dir: result.cliDir });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (earliest && !split) {
|
|
214
|
+
const ei = actionIndex(earliest);
|
|
215
|
+
if (ei <= actionIndex('cli-install')) {
|
|
216
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
217
|
+
}
|
|
218
|
+
if (ei <= actionIndex('cli-build')) {
|
|
219
|
+
// Add install if not already in the list and node_modules is missing
|
|
220
|
+
if (!actions.some(a => a.kind === 'cli-install') &&
|
|
221
|
+
!(await fileExists(path.join(result.cliDir, 'node_modules')))) {
|
|
222
|
+
actions.push({ kind: 'cli-install', dir: result.cliDir });
|
|
223
|
+
}
|
|
224
|
+
actions.push({ kind: 'cli-build', dir: result.cliDir });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// skills-sync is independent — append if needed
|
|
228
|
+
if (spec.externalSkills.length > 0) {
|
|
229
|
+
const targetDir = split ? result.cliDir : result.cliDir;
|
|
230
|
+
actions.push({
|
|
231
|
+
kind: 'skills-sync',
|
|
232
|
+
targetDir,
|
|
233
|
+
desired: spec.externalSkills,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return actions;
|
|
237
|
+
}
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Human-readable label for an action
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
function actionLabel(action) {
|
|
242
|
+
switch (action.kind) {
|
|
243
|
+
case 'ph-init':
|
|
244
|
+
return `Initialize reactor package (${path.basename(action.appDir)})`;
|
|
245
|
+
case 'app-install':
|
|
246
|
+
return `Install dependencies (${path.basename(action.dir)})`;
|
|
247
|
+
case 'app-ph-install':
|
|
248
|
+
return `Register external packages (${action.packages.join(', ')})`;
|
|
249
|
+
case 'app-build':
|
|
250
|
+
return `Build (${path.basename(action.dir)})`;
|
|
251
|
+
case 'cli-install':
|
|
252
|
+
return `Install dependencies (${path.basename(action.dir)})`;
|
|
253
|
+
case 'cli-build':
|
|
254
|
+
return `Build (${path.basename(action.dir)})`;
|
|
255
|
+
case 'skills-sync':
|
|
256
|
+
return `Sync external skills (${action.desired.length} skill${action.desired.length === 1 ? '' : 's'})`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// runPostGenActions — effectful runner
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
export async function runPostGenActions(actions, ctx) {
|
|
263
|
+
if (actions.length === 0)
|
|
264
|
+
return [];
|
|
265
|
+
const skip = ctx.skip ?? new Set();
|
|
266
|
+
// Filter out explicitly skipped actions. Also skip builds if their
|
|
267
|
+
// corresponding install is skipped (building without install is pointless).
|
|
268
|
+
const effective = actions.filter((a) => {
|
|
269
|
+
if (skip.has(a.kind))
|
|
270
|
+
return false;
|
|
271
|
+
// If app-install is skipped, also skip app-ph-install and app-build
|
|
272
|
+
if (a.kind === 'app-ph-install' && skip.has('app-install'))
|
|
273
|
+
return false;
|
|
274
|
+
if (a.kind === 'app-build' && skip.has('app-install'))
|
|
275
|
+
return false;
|
|
276
|
+
// If cli-install is skipped, also skip cli-build
|
|
277
|
+
if (a.kind === 'cli-build' && skip.has('cli-install'))
|
|
278
|
+
return false;
|
|
279
|
+
return true;
|
|
280
|
+
});
|
|
281
|
+
if (effective.length === 0)
|
|
282
|
+
return [];
|
|
283
|
+
// Print numbered summary
|
|
284
|
+
ctx.log(`\nPost-generation steps (${effective.length}):`);
|
|
285
|
+
effective.forEach((a, i) => {
|
|
286
|
+
ctx.log(` ${i + 1}. ${actionLabel(a)}`);
|
|
287
|
+
});
|
|
288
|
+
ctx.log('');
|
|
289
|
+
const results = [];
|
|
290
|
+
const failed = new Set();
|
|
291
|
+
for (let i = 0; i < effective.length; i++) {
|
|
292
|
+
const action = effective[i];
|
|
293
|
+
const label = actionLabel(action);
|
|
294
|
+
const prefix = `[${i + 1}/${effective.length}]`;
|
|
295
|
+
// Check if a dependency failed
|
|
296
|
+
const failedDep = [...failed].find((f) => dependsOn(action.kind, f));
|
|
297
|
+
if (failedDep) {
|
|
298
|
+
ctx.log(`${prefix} ${label}... skipped (dependency failed)`);
|
|
299
|
+
results.push({
|
|
300
|
+
action,
|
|
301
|
+
status: 'skipped',
|
|
302
|
+
reason: 'dependency failed',
|
|
303
|
+
durationMs: 0,
|
|
304
|
+
});
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
ctx.log(`${prefix} ${label}...`);
|
|
308
|
+
const t0 = Date.now();
|
|
309
|
+
try {
|
|
310
|
+
const success = await executeAction(action, ctx);
|
|
311
|
+
const elapsed = Date.now() - t0;
|
|
312
|
+
if (success) {
|
|
313
|
+
ctx.log(` done (${formatDuration(elapsed)})`);
|
|
314
|
+
results.push({ action, status: 'success', durationMs: elapsed });
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
ctx.log(` FAILED`);
|
|
318
|
+
results.push({ action, status: 'failed', durationMs: elapsed });
|
|
319
|
+
failed.add(action.kind);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
const elapsed = Date.now() - t0;
|
|
324
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
325
|
+
ctx.log(` FAILED: ${msg}`);
|
|
326
|
+
results.push({
|
|
327
|
+
action,
|
|
328
|
+
status: 'failed',
|
|
329
|
+
reason: msg,
|
|
330
|
+
durationMs: elapsed,
|
|
331
|
+
});
|
|
332
|
+
failed.add(action.kind);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Print summary
|
|
336
|
+
const succeeded = results.filter((r) => r.status === 'success').length;
|
|
337
|
+
const failedCount = results.filter((r) => r.status === 'failed').length;
|
|
338
|
+
const skippedCount = results.filter((r) => r.status === 'skipped').length;
|
|
339
|
+
ctx.log('');
|
|
340
|
+
if (failedCount === 0 && skippedCount === 0) {
|
|
341
|
+
ctx.log(`Post-generation: ${succeeded} step${succeeded === 1 ? '' : 's'} completed successfully.`);
|
|
342
|
+
ctx.log('Ready — run `pnpm dev` to start.');
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
ctx.log(`Post-generation: ${succeeded} succeeded, ${failedCount} failed, ${skippedCount} skipped.`);
|
|
346
|
+
if (failedCount > 0) {
|
|
347
|
+
ctx.log('Fix the reported error(s) and re-run `ph-clint clint-project-regen`.');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return results;
|
|
351
|
+
}
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Action dispatch
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
async function executeAction(action, ctx) {
|
|
356
|
+
switch (action.kind) {
|
|
357
|
+
case 'ph-init': {
|
|
358
|
+
const { runPhInit } = await import('./scaffold.js');
|
|
359
|
+
const result = await runPhInit({
|
|
360
|
+
targetDir: action.targetDir,
|
|
361
|
+
appDir: action.appDir,
|
|
362
|
+
spec: action.spec,
|
|
363
|
+
log: (msg) => ctx.log(` ${msg}`),
|
|
364
|
+
runProcess: ctx.runProcess,
|
|
365
|
+
});
|
|
366
|
+
if (!result.ran) {
|
|
367
|
+
ctx.log(` ${result.reason ?? 'ph init did not run'}`);
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
if (result.exitCode === 0) {
|
|
371
|
+
// Mark app as initialized in generated state
|
|
372
|
+
const { readGeneratedState, writeGeneratedState } = await import('./generated.js');
|
|
373
|
+
const gen = await readGeneratedState(action.targetDir);
|
|
374
|
+
if (gen) {
|
|
375
|
+
gen.appInitialized = true;
|
|
376
|
+
await writeGeneratedState(action.targetDir, gen);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return result.exitCode === 0;
|
|
380
|
+
}
|
|
381
|
+
case 'app-ph-install': {
|
|
382
|
+
const { runPhInstallPackages } = await import('./scaffold.js');
|
|
383
|
+
return runPhInstallPackages({
|
|
384
|
+
appDir: action.appDir,
|
|
385
|
+
packages: action.packages,
|
|
386
|
+
log: (msg) => ctx.log(` ${msg}`),
|
|
387
|
+
runProcess: ctx.runProcess,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
case 'app-install':
|
|
391
|
+
case 'cli-install': {
|
|
392
|
+
const result = await ctx.runProcess('pnpm install', {
|
|
393
|
+
cwd: action.dir,
|
|
394
|
+
timeout: 300_000,
|
|
395
|
+
});
|
|
396
|
+
return result.success;
|
|
397
|
+
}
|
|
398
|
+
case 'app-build':
|
|
399
|
+
case 'cli-build': {
|
|
400
|
+
return runBuild(action.dir, ctx);
|
|
401
|
+
}
|
|
402
|
+
case 'skills-sync': {
|
|
403
|
+
const { syncExternalSkills } = await import('../skills/sync.js');
|
|
404
|
+
await syncExternalSkills({
|
|
405
|
+
targetDir: action.targetDir,
|
|
406
|
+
desired: action.desired,
|
|
407
|
+
log: (msg) => ctx.log(` ${msg}`),
|
|
408
|
+
});
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function runBuild(dir, ctx) {
|
|
414
|
+
const result = await ctx.runProcess('pnpm build', {
|
|
415
|
+
cwd: dir,
|
|
416
|
+
timeout: 120_000,
|
|
417
|
+
});
|
|
418
|
+
return result.success;
|
|
419
|
+
}
|
|
420
|
+
function formatDuration(ms) {
|
|
421
|
+
if (ms < 1000)
|
|
422
|
+
return `${ms}ms`;
|
|
423
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
424
|
+
}
|
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Builds the CLI-side `package.json` (the ph-clint CLI itself).
|
|
3
|
+
*
|
|
4
|
+
* When `features.powerhouse.enabled`, this file lives at
|
|
5
|
+
* `{root}/{name}-cli/package.json`. Otherwise at `{root}/package.json`.
|
|
6
|
+
*/
|
|
7
|
+
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
8
|
+
export declare function buildCliPackageJson(spec: ClintProjectSpec): string;
|
|
@@ -1 +1,128 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Builds the CLI-side `package.json` (the ph-clint CLI itself).
|
|
3
|
+
*
|
|
4
|
+
* When `features.powerhouse.enabled`, this file lives at
|
|
5
|
+
* `{root}/{name}-cli/package.json`. Otherwise at `{root}/package.json`.
|
|
6
|
+
*/
|
|
7
|
+
import { getAppDirName, getAppPackageName, getBinName, getPackageName, phAtLeast, } from '../../spec/types.js';
|
|
8
|
+
import { CLI_VERSION } from '../../config.js';
|
|
9
|
+
import { getPhVersion } from '../scaffold.js';
|
|
10
|
+
/**
|
|
11
|
+
* Derive a semver range from the running ph-clint-cli version that respects
|
|
12
|
+
* the dev / staging / production channel boundary. This is the version range
|
|
13
|
+
* emitted into *scaffolded* projects' package.json for @powerhousedao/ph-clint
|
|
14
|
+
* and @powerhousedao/ph-clint-dev dependencies.
|
|
15
|
+
*
|
|
16
|
+
* CLI_VERSION is the version of ph-clint-cli itself (read from its own
|
|
17
|
+
* package.json at startup). Since all framework packages share a lockstep
|
|
18
|
+
* version, this is also the correct base for the dependency range.
|
|
19
|
+
*
|
|
20
|
+
* Examples:
|
|
21
|
+
* "0.1.0" → "^0.1.0" (production — stable only)
|
|
22
|
+
* "0.1.0-staging.2" → "^0.1.0-staging.0" (staging + stable)
|
|
23
|
+
* "0.1.0-dev.4" → "^0.1.0-dev.0" (dev + staging + stable)
|
|
24
|
+
*
|
|
25
|
+
* This works because semver orders prereleases alphabetically:
|
|
26
|
+
* dev.N < staging.N < release. A caret range starting at dev.0 naturally
|
|
27
|
+
* upgrades through staging to production.
|
|
28
|
+
*/
|
|
29
|
+
function phClintRange(version) {
|
|
30
|
+
const match = /^(\d+\.\d+\.\d+)(?:-([a-z]+)\.\d+)?/.exec(version);
|
|
31
|
+
if (!match)
|
|
32
|
+
return `^${version}`;
|
|
33
|
+
const base = match[1];
|
|
34
|
+
const channel = match[2]; // "dev", "staging", or undefined
|
|
35
|
+
// For prerelease channels, pin exact version. Caret ranges like ^0.1.0-dev.0
|
|
36
|
+
// resolve to `latest` dist-tag (0.1.0-dev.0) instead of the highest match,
|
|
37
|
+
// causing "export not found" errors when the scaffolding CLI is newer.
|
|
38
|
+
return channel ? version : `^${base}`;
|
|
39
|
+
}
|
|
40
|
+
/** Version range for @powerhousedao/ph-clint{,-dev} deps in scaffolded projects. */
|
|
41
|
+
const PH_CLINT_VERSION = phClintRange(CLI_VERSION);
|
|
42
|
+
const POWERHOUSE_VERSION_FALLBACK = '6.0.0-dev.170';
|
|
43
|
+
let _powerhouseVersion;
|
|
44
|
+
function getPowerhouseVersion() {
|
|
45
|
+
if (_powerhouseVersion === undefined) {
|
|
46
|
+
_powerhouseVersion = getPhVersion('ph') ?? POWERHOUSE_VERSION_FALLBACK;
|
|
47
|
+
}
|
|
48
|
+
return _powerhouseVersion;
|
|
49
|
+
}
|
|
50
|
+
export function buildCliPackageJson(spec) {
|
|
51
|
+
const { mastra, powerhouse } = spec.features;
|
|
52
|
+
const pkgName = getPackageName(spec);
|
|
53
|
+
const bin = getBinName(spec);
|
|
54
|
+
const dependencies = {
|
|
55
|
+
'@powerhousedao/ph-clint': PH_CLINT_VERSION,
|
|
56
|
+
zod: '^4.3.6',
|
|
57
|
+
};
|
|
58
|
+
if (mastra.enabled) {
|
|
59
|
+
dependencies['@mastra/core'] = '^1.22.0';
|
|
60
|
+
dependencies['@mastra/libsql'] = '^1.7.4';
|
|
61
|
+
dependencies['@mastra/mcp'] = '^1.4.1';
|
|
62
|
+
dependencies['@mastra/memory'] = '^1.13.1';
|
|
63
|
+
}
|
|
64
|
+
if (phAtLeast(powerhouse, 'Reactor')) {
|
|
65
|
+
const appPkg = getAppPackageName(spec);
|
|
66
|
+
const appDir = getAppDirName(spec);
|
|
67
|
+
dependencies[appPkg] = `file:../${appDir}`;
|
|
68
|
+
dependencies['@powerhousedao/reactor'] = getPowerhouseVersion();
|
|
69
|
+
dependencies['@powerhousedao/reactor-api'] = getPowerhouseVersion();
|
|
70
|
+
dependencies['@powerhousedao/shared'] = getPowerhouseVersion();
|
|
71
|
+
dependencies['document-model'] = getPowerhouseVersion();
|
|
72
|
+
// External reactor packages (non-app) as versioned dependencies.
|
|
73
|
+
// Compare bare names (without scope) so that a spec entry with
|
|
74
|
+
// packageName "foo-app" still matches the scoped "@scope/foo-app".
|
|
75
|
+
const appBare = appPkg.replace(/^@[^/]+\//, '');
|
|
76
|
+
for (const pkg of spec.packages) {
|
|
77
|
+
const pkgBare = pkg.packageName.replace(/^@[^/]+\//, '');
|
|
78
|
+
if (pkgBare === appBare)
|
|
79
|
+
continue;
|
|
80
|
+
dependencies[pkg.packageName] = 'latest';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const scripts = {
|
|
84
|
+
'build:skills': 'tsx scripts/build-skills.ts',
|
|
85
|
+
'build:manifest': 'build-manifest',
|
|
86
|
+
build: 'pnpm build:skills && tsc && pnpm build:manifest',
|
|
87
|
+
dev: 'tsx src/main.ts',
|
|
88
|
+
start: 'node dist/main.js',
|
|
89
|
+
test: "NODE_OPTIONS='--experimental-vm-modules' jest --detectOpenHandles",
|
|
90
|
+
lint: 'eslint src',
|
|
91
|
+
};
|
|
92
|
+
if (mastra.enabled) {
|
|
93
|
+
scripts['mastra:dev'] = 'mastra dev';
|
|
94
|
+
scripts['mastra:build'] = 'mastra build';
|
|
95
|
+
scripts['mastra:start'] = 'mastra start';
|
|
96
|
+
}
|
|
97
|
+
// Single-layout: publish scripts live in the CLI package.json (which IS the
|
|
98
|
+
// root). Split-layout gets these from root-package-json.ts instead.
|
|
99
|
+
if (!phAtLeast(powerhouse, 'Reactor')) {
|
|
100
|
+
scripts['publish:dev'] = 'ph-publish dev -c ./publish.config.js';
|
|
101
|
+
scripts['publish:staging'] = 'ph-publish staging -c ./publish.config.js';
|
|
102
|
+
scripts['publish:production'] = 'ph-publish production -c ./publish.config.js';
|
|
103
|
+
}
|
|
104
|
+
const pkg = {
|
|
105
|
+
name: pkgName,
|
|
106
|
+
version: spec.version,
|
|
107
|
+
publishConfig: { access: 'public' },
|
|
108
|
+
type: 'module',
|
|
109
|
+
};
|
|
110
|
+
if (spec.description)
|
|
111
|
+
pkg.description = spec.description;
|
|
112
|
+
pkg.bin = { [bin]: './dist/main.js' };
|
|
113
|
+
pkg.files = ['dist'];
|
|
114
|
+
pkg.scripts = scripts;
|
|
115
|
+
pkg.dependencies = dependencies;
|
|
116
|
+
pkg.devDependencies = {
|
|
117
|
+
'@jest/globals': '^30.2.0',
|
|
118
|
+
'@types/node': '^25.5.2',
|
|
119
|
+
jest: '^30.2.0',
|
|
120
|
+
'@powerhousedao/ph-clint-dev': PH_CLINT_VERSION,
|
|
121
|
+
'ts-jest': '^29.4.6',
|
|
122
|
+
tsx: '^4.19.0',
|
|
123
|
+
typescript: '^6.0.2',
|
|
124
|
+
'typescript-eslint': '^8.33.0',
|
|
125
|
+
};
|
|
126
|
+
pkg.engines = { node: '>=22.13.0' };
|
|
127
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
128
|
+
}
|
|
@@ -1 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Registry of file builders. Each entry knows its own relative path (within
|
|
3
|
+
* the CLI sub-tree) and a function that builds its content from the spec.
|
|
4
|
+
*
|
|
5
|
+
* Orchestrated by `codegen/index.ts` — it walks this registry, adjusts paths
|
|
6
|
+
* for flat vs split layouts, and writes the files.
|
|
7
|
+
*/
|
|
8
|
+
import { buildCliPackageJson } from './cli-package-json.js';
|
|
9
|
+
import { buildCliTs } from './cli-ts.js';
|
|
10
|
+
import { buildConfigTs } from './config-ts.js';
|
|
11
|
+
import { buildMainTs } from './main-ts.js';
|
|
12
|
+
import { buildTsconfigJson } from './tsconfig-json.js';
|
|
13
|
+
import { buildJestConfigJs } from './jest-config-js.js';
|
|
14
|
+
import { buildEslintConfigJs } from './eslint-config-js.js';
|
|
15
|
+
import { buildGitignore } from './gitignore.js';
|
|
16
|
+
import { buildMastraIndexTs } from './mastra-index-ts.js';
|
|
17
|
+
import { buildBuildSkillsScript } from './build-skills-ts.js';
|
|
18
|
+
import { buildAgentBaseMd } from './agent-base-md.js';
|
|
19
|
+
import { buildAgentTs } from './agent-ts.js';
|
|
20
|
+
import { buildDemoAgentTs } from './demo-agent-ts.js';
|
|
21
|
+
import { buildFrameworkGenTs } from './framework-gen-ts.js';
|
|
22
|
+
import { buildFrameworkTs } from './framework-ts.js';
|
|
23
|
+
import { buildManifestJson } from './manifest-json.js';
|
|
24
|
+
import { buildSmokeTestTs } from './smoke-test-ts.js';
|
|
25
|
+
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
26
|
+
/**
|
|
27
|
+
* A spec-driven file builder. Returns `null` from `build` to indicate the
|
|
28
|
+
* file should not be emitted for the given spec.
|
|
29
|
+
*/
|
|
30
|
+
export interface FileBuilder {
|
|
31
|
+
/** Path relative to the CLI root (flat or `{name}-cli/`). */
|
|
32
|
+
relativePath: string;
|
|
33
|
+
build(spec: ClintProjectSpec): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* When true, this file is only emitted on `mode: 'create'`. In `update`
|
|
36
|
+
* mode the planner ignores the builder entirely, leaving any on-disk
|
|
37
|
+
* version untouched regardless of hash state. Used for files that carry
|
|
38
|
+
* user-owned hand edits (e.g. `src/framework.ts`'s `configSchema`).
|
|
39
|
+
*/
|
|
40
|
+
initOnly?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate dynamic file builders for agent profile markdown files.
|
|
44
|
+
* One file per profile in `prompts/agent-profiles/{id}.md`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getProfileFileBuilders(spec: ClintProjectSpec): FileBuilder[];
|
|
47
|
+
export declare const CLI_FILE_BUILDERS: FileBuilder[];
|
|
48
|
+
export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, buildSmokeTestTs, };
|
|
49
|
+
export { buildRootPackageJson } from './root-package-json.js';
|
|
50
|
+
export { buildPublishConfigJs } from './publish-config-js.js';
|
|
51
|
+
export { buildReadme } from './readme-md.js';
|
|
52
|
+
export { buildAppIndexTs } from './app-index-ts.js';
|