@powerhousedao/ph-clint-cli 0.1.0-dev.4 → 0.1.0-dev.41
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/cli.js +51 -12
- package/dist/codegen/actions.d.ts +42 -0
- package/dist/codegen/actions.js +387 -0
- package/dist/codegen/builders/agent-ts.d.ts +4 -4
- package/dist/codegen/builders/agent-ts.js +83 -2
- package/dist/codegen/builders/app-index-ts.d.ts +3 -10
- package/dist/codegen/builders/app-index-ts.js +10 -14
- package/dist/codegen/builders/cli-package-json.js +62 -8
- package/dist/codegen/builders/cli-ts.js +61 -24
- package/dist/codegen/builders/config-ts.js +11 -15
- package/dist/codegen/builders/demo-agent-ts.d.ts +8 -0
- package/dist/codegen/builders/demo-agent-ts.js +44 -0
- package/dist/codegen/builders/eslint-config-js.d.ts +1 -1
- package/dist/codegen/builders/eslint-config-js.js +7 -4
- package/dist/codegen/builders/framework-gen-ts.d.ts +4 -5
- package/dist/codegen/builders/framework-gen-ts.js +21 -18
- package/dist/codegen/builders/framework-ts.js +33 -7
- package/dist/codegen/builders/index.d.ts +10 -1
- package/dist/codegen/builders/index.js +30 -5
- package/dist/codegen/builders/manifest-json.d.ts +6 -0
- package/dist/codegen/builders/manifest-json.js +31 -0
- package/dist/codegen/builders/mastra-index-ts.d.ts +3 -0
- package/dist/codegen/builders/mastra-index-ts.js +47 -5
- package/dist/codegen/builders/publish-config-js.d.ts +8 -0
- package/dist/codegen/builders/publish-config-js.js +32 -0
- package/dist/codegen/builders/publish-config-ts.d.ts +8 -0
- package/dist/codegen/builders/publish-config-ts.js +32 -0
- package/dist/codegen/builders/readme-md.js +7 -6
- package/dist/codegen/builders/root-package-json.d.ts +3 -0
- package/dist/codegen/builders/root-package-json.js +23 -11
- package/dist/codegen/builders/smoke-test-ts.d.ts +2 -0
- package/dist/codegen/builders/smoke-test-ts.js +15 -0
- package/dist/codegen/generated.d.ts +17 -0
- package/dist/codegen/generated.js +49 -0
- package/dist/codegen/index.d.ts +3 -0
- package/dist/codegen/index.js +139 -14
- package/dist/codegen/scaffold.d.ts +31 -3
- package/dist/codegen/scaffold.js +114 -16
- package/dist/commands/clint-project-build.js +10 -27
- package/dist/commands/clint-project-init.d.ts +30 -8
- package/dist/commands/clint-project-init.js +45 -41
- package/dist/commands/clint-project-publish.d.ts +1 -0
- package/dist/commands/clint-project-publish.js +5 -3
- package/dist/commands/clint-project-regen.js +25 -1
- package/dist/commands/clint-skills-sync.d.ts +10 -0
- package/dist/commands/clint-skills-sync.js +56 -0
- package/dist/config.d.ts +3 -5
- package/dist/config.js +5 -7
- package/dist/framework.d.ts +5 -169
- package/dist/framework.gen.d.ts +1 -32
- package/dist/framework.js +0 -4
- package/{prompts/agent-profiles/AgentBase.md → dist/gen/agent-profiles/PhClintDevAgent.md} +1 -1
- package/dist/gen/skills/cli-setup/.cli-docs.md +135 -0
- package/dist/gen/skills/cli-setup/SKILL.md +125 -0
- package/dist/gen/skills/cli-setup/references/00.assess-cli-needs.md +53 -0
- package/dist/gen/skills/cli-setup/references/01.define-config-schema.md +78 -0
- package/dist/gen/skills/cli-setup/references/02.wire-cli.md +141 -0
- package/dist/gen/skills/cli-setup/references/03.add-entrypoint.md +128 -0
- package/dist/gen/skills/cli-setup/references/expected-outcome.md +3 -0
- package/dist/gen/skills/command-definition/.cli-docs.md +84 -0
- package/dist/gen/skills/command-definition/SKILL.md +99 -0
- package/dist/gen/skills/command-definition/references/00.assess-requirements.md +58 -0
- package/dist/gen/skills/command-definition/references/01.define-schema.md +72 -0
- package/dist/gen/skills/command-definition/references/02.implement-execute.md +118 -0
- package/dist/gen/skills/command-definition/references/03.add-prompting.md +54 -0
- package/dist/gen/skills/command-definition/references/expected-outcome.md +3 -0
- package/dist/gen/skills/service-definition/.cli-docs.md +214 -0
- package/dist/gen/skills/service-definition/SKILL.md +148 -0
- package/dist/gen/skills/service-definition/references/00.assess-service.md +69 -0
- package/dist/gen/skills/service-definition/references/01.define-service.md +166 -0
- package/dist/gen/skills/service-definition/references/02.add-preflight.md +132 -0
- package/dist/gen/skills/service-definition/references/03.add-project-scanner.md +82 -0
- package/dist/gen/skills/service-definition/references/04.wire-events.md +120 -0
- package/dist/gen/skills/service-definition/references/expected-outcome.md +3 -0
- package/dist/services/clint-project.js +21 -0
- package/dist/skills/sync.d.ts +40 -0
- package/dist/skills/sync.js +119 -0
- package/dist/spec/ensure-document.d.ts +17 -0
- package/dist/spec/ensure-document.js +62 -0
- package/dist/spec/from-document.d.ts +4 -1
- package/dist/spec/from-document.js +18 -8
- package/dist/spec/types.d.ts +85 -6
- package/dist/spec/types.js +90 -23
- package/dist/triggers/publish-trigger.d.ts +10 -0
- package/dist/triggers/publish-trigger.js +169 -0
- package/dist/triggers/spec-change.d.ts +5 -34
- package/dist/triggers/spec-change.js +130 -102
- package/package.json +7 -7
- package/prompts/skills-ext/.gitkeep +0 -0
- package/prompts/skills-tpl/.gitkeep +0 -0
package/dist/codegen/index.js
CHANGED
|
@@ -35,7 +35,10 @@ import { hashContent, hashFile, readHashes, writeHashes, } from './hashes.js';
|
|
|
35
35
|
import { hasMarkers, spliceMarkerRegions } from './markers.js';
|
|
36
36
|
import { writeFileEnsuringDir, isDirEmptyEnough } from './write.js';
|
|
37
37
|
import { migrateFlatToSplit } from './migrate/flat-to-split.js';
|
|
38
|
-
import { CLI_FILE_BUILDERS, buildReadme, buildRootPackageJson, buildAppIndexTs, } from './builders/index.js';
|
|
38
|
+
import { CLI_FILE_BUILDERS, getProfileFileBuilders, buildReadme, buildRootPackageJson, buildPublishConfigJs, buildAppIndexTs, } from './builders/index.js';
|
|
39
|
+
import { collectPostGenActions, } from './actions.js';
|
|
40
|
+
import { readGeneratedState, writeGeneratedState, generatedStateFromSpec, } from './generated.js';
|
|
41
|
+
import { patchAppPackageName } from './scaffold.js';
|
|
39
42
|
/**
|
|
40
43
|
* Generate / reconcile a project at `targetDir` from the given spec.
|
|
41
44
|
*/
|
|
@@ -50,8 +53,8 @@ export async function generateProject(options) {
|
|
|
50
53
|
let migrated = false;
|
|
51
54
|
if (resolvedMode === 'update' &&
|
|
52
55
|
existingSpec &&
|
|
53
|
-
|
|
54
|
-
spec.features.powerhouse
|
|
56
|
+
existingSpec.features.powerhouse === 'Disabled' &&
|
|
57
|
+
spec.features.powerhouse !== 'Disabled') {
|
|
55
58
|
await migrateFlatToSplit({
|
|
56
59
|
targetDir,
|
|
57
60
|
spec,
|
|
@@ -75,11 +78,12 @@ function resolveMode(requested, specExists) {
|
|
|
75
78
|
}
|
|
76
79
|
/** Build the list of files the new spec would emit, with their content. */
|
|
77
80
|
function planFiles(spec, targetDir) {
|
|
78
|
-
const split = spec.features.powerhouse
|
|
81
|
+
const split = spec.features.powerhouse !== 'Disabled';
|
|
79
82
|
const cliDir = split ? path.join(targetDir, getCliFolderName(spec)) : targetDir;
|
|
80
83
|
const appDir = split ? path.join(targetDir, getAppFolderName(spec)) : null;
|
|
81
84
|
const planned = [];
|
|
82
|
-
|
|
85
|
+
const allBuilders = [...CLI_FILE_BUILDERS, ...getProfileFileBuilders(spec)];
|
|
86
|
+
for (const builder of allBuilders) {
|
|
83
87
|
const content = builder.build(spec);
|
|
84
88
|
if (content === null)
|
|
85
89
|
continue;
|
|
@@ -98,6 +102,13 @@ function planFiles(spec, targetDir) {
|
|
|
98
102
|
absolutePath: readmeAbs,
|
|
99
103
|
content: buildReadme(spec),
|
|
100
104
|
});
|
|
105
|
+
// Publish config — always at the project root (both layouts).
|
|
106
|
+
const publishConfig = path.join(targetDir, 'publish.config.js');
|
|
107
|
+
planned.push({
|
|
108
|
+
relativePath: 'publish.config.js',
|
|
109
|
+
absolutePath: publishConfig,
|
|
110
|
+
content: buildPublishConfigJs(spec),
|
|
111
|
+
});
|
|
101
112
|
// Split-layout only: root package.json + app placeholder files.
|
|
102
113
|
if (split && appDir) {
|
|
103
114
|
const rootPkg = path.join(targetDir, 'package.json');
|
|
@@ -147,6 +158,82 @@ function buildAppReadmeContent(spec) {
|
|
|
147
158
|
'',
|
|
148
159
|
].join('\n');
|
|
149
160
|
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Folder rename handling
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
async function fileExists(filePath) {
|
|
165
|
+
try {
|
|
166
|
+
await fs.access(filePath);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Detect and execute folder renames when the project name changes.
|
|
175
|
+
*
|
|
176
|
+
* Reads the previous `GeneratedState` to find the old folder names, compares
|
|
177
|
+
* with the new spec's folder names. If they differ:
|
|
178
|
+
* 1. Renames the directories on disk.
|
|
179
|
+
* 2. Rewrites hash-record keys from old → new prefixes.
|
|
180
|
+
*
|
|
181
|
+
* Must run BEFORE `planFiles()` so the reconciliation loop sees the files
|
|
182
|
+
* in their new locations.
|
|
183
|
+
*/
|
|
184
|
+
async function handleFolderRename(targetDir, spec, prev, warn) {
|
|
185
|
+
const split = spec.features.powerhouse !== 'Disabled';
|
|
186
|
+
if (!split || !prev || !prev.cliFolderName) {
|
|
187
|
+
return { renamed: false, appInitialized: prev?.appInitialized ?? false };
|
|
188
|
+
}
|
|
189
|
+
const newCliFolder = getCliFolderName(spec);
|
|
190
|
+
const newAppFolder = getAppFolderName(spec);
|
|
191
|
+
const oldCliFolder = prev.cliFolderName;
|
|
192
|
+
const oldAppFolder = prev.appFolderName;
|
|
193
|
+
if (oldCliFolder === newCliFolder && oldAppFolder === newAppFolder) {
|
|
194
|
+
return { renamed: false, appInitialized: prev.appInitialized };
|
|
195
|
+
}
|
|
196
|
+
// Rename CLI folder
|
|
197
|
+
if (oldCliFolder !== newCliFolder) {
|
|
198
|
+
const oldPath = path.join(targetDir, oldCliFolder);
|
|
199
|
+
const newPath = path.join(targetDir, newCliFolder);
|
|
200
|
+
if (await fileExists(oldPath)) {
|
|
201
|
+
if (await fileExists(newPath)) {
|
|
202
|
+
warn(`cannot rename ${oldCliFolder} → ${newCliFolder}: target already exists`);
|
|
203
|
+
return { renamed: false, appInitialized: prev.appInitialized };
|
|
204
|
+
}
|
|
205
|
+
await fs.rename(oldPath, newPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Rename app folder
|
|
209
|
+
if (oldAppFolder !== newAppFolder) {
|
|
210
|
+
const oldPath = path.join(targetDir, oldAppFolder);
|
|
211
|
+
const newPath = path.join(targetDir, newAppFolder);
|
|
212
|
+
if (await fileExists(oldPath)) {
|
|
213
|
+
if (await fileExists(newPath)) {
|
|
214
|
+
warn(`cannot rename ${oldAppFolder} → ${newAppFolder}: target already exists`);
|
|
215
|
+
return { renamed: false, appInitialized: prev.appInitialized };
|
|
216
|
+
}
|
|
217
|
+
await fs.rename(oldPath, newPath);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Rewrite hash keys from old folder prefixes to new ones
|
|
221
|
+
const hashes = await readHashes(targetDir);
|
|
222
|
+
const remapped = {};
|
|
223
|
+
for (const [key, value] of Object.entries(hashes)) {
|
|
224
|
+
let newKey = key;
|
|
225
|
+
if (oldCliFolder !== newCliFolder && key.startsWith(oldCliFolder + '/')) {
|
|
226
|
+
newKey = newCliFolder + key.slice(oldCliFolder.length);
|
|
227
|
+
}
|
|
228
|
+
else if (oldAppFolder !== newAppFolder &&
|
|
229
|
+
key.startsWith(oldAppFolder + '/')) {
|
|
230
|
+
newKey = newAppFolder + key.slice(oldAppFolder.length);
|
|
231
|
+
}
|
|
232
|
+
remapped[newKey] = value;
|
|
233
|
+
}
|
|
234
|
+
await writeHashes(targetDir, remapped);
|
|
235
|
+
return { renamed: true, appInitialized: prev.appInitialized };
|
|
236
|
+
}
|
|
150
237
|
/** Create mode — empty target dir, write everything fresh. */
|
|
151
238
|
async function runCreate(options, _warn) {
|
|
152
239
|
const { targetDir, spec } = options;
|
|
@@ -170,7 +257,9 @@ async function runCreate(options, _warn) {
|
|
|
170
257
|
relativePath: path.relative(targetDir, getSpecPath(targetDir)),
|
|
171
258
|
});
|
|
172
259
|
await writeHashes(targetDir, hashes);
|
|
173
|
-
|
|
260
|
+
// App is not yet initialized on create — ph-init runs as a post-gen action.
|
|
261
|
+
await writeGeneratedState(targetDir, generatedStateFromSpec(spec, false));
|
|
262
|
+
const result = {
|
|
174
263
|
mode: 'create',
|
|
175
264
|
files,
|
|
176
265
|
skipped: [],
|
|
@@ -178,12 +267,18 @@ async function runCreate(options, _warn) {
|
|
|
178
267
|
migrated: false,
|
|
179
268
|
cliDir,
|
|
180
269
|
appDir,
|
|
270
|
+
pendingActions: [],
|
|
181
271
|
};
|
|
272
|
+
result.pendingActions = await collectPostGenActions(result, spec);
|
|
273
|
+
return result;
|
|
182
274
|
}
|
|
183
275
|
/** Update mode — reconcile against an existing project. */
|
|
184
276
|
async function runUpdate(options, warn, migrated) {
|
|
185
277
|
const { targetDir, spec } = options;
|
|
186
278
|
const force = !!options.force;
|
|
279
|
+
// --- Folder rename detection ---
|
|
280
|
+
const prev = await readGeneratedState(targetDir);
|
|
281
|
+
const { renamed, appInitialized: appWasInitialized } = await handleFolderRename(targetDir, spec, prev, warn);
|
|
187
282
|
const { planned, cliDir, appDir } = planFiles(spec, targetDir);
|
|
188
283
|
const plannedByKey = new Map(planned.map((p) => [p.relativePath, p]));
|
|
189
284
|
const previous = await readHashes(targetDir);
|
|
@@ -195,13 +290,20 @@ async function runUpdate(options, warn, migrated) {
|
|
|
195
290
|
for (const p of planned) {
|
|
196
291
|
if (p.initOnly) {
|
|
197
292
|
// User-owned files (e.g. `src/framework.ts` with its `configSchema`)
|
|
198
|
-
// are emitted only on create. In update mode we never overwrite them
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
next[p.relativePath] =
|
|
293
|
+
// are emitted only on create. In update mode we never overwrite them —
|
|
294
|
+
// but we DO create them if they don't yet exist on disk (e.g. first
|
|
295
|
+
// spec-change trigger run where no explicit `init` was run before).
|
|
296
|
+
const onDisk = await hashFile(p.absolutePath);
|
|
297
|
+
if (onDisk === null && p.content) {
|
|
298
|
+
await writeFileEnsuringDir(p.absolutePath, p.content);
|
|
299
|
+
next[p.relativePath] = hashContent(p.content);
|
|
300
|
+
files.push({ absolutePath: p.absolutePath, relativePath: p.relativePath });
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const stored = previous[p.relativePath];
|
|
304
|
+
if (stored !== undefined)
|
|
305
|
+
next[p.relativePath] = stored;
|
|
306
|
+
}
|
|
205
307
|
continue;
|
|
206
308
|
}
|
|
207
309
|
const existing = await hashFile(p.absolutePath);
|
|
@@ -280,7 +382,27 @@ async function runUpdate(options, warn, migrated) {
|
|
|
280
382
|
}
|
|
281
383
|
await writeProjectSpec(targetDir, spec);
|
|
282
384
|
await writeHashes(targetDir, next);
|
|
283
|
-
|
|
385
|
+
// Re-patch app package.json if name or scope changed and app is initialized.
|
|
386
|
+
// Add to `files` so collectPostGenActions sees the change and triggers
|
|
387
|
+
// app-install → app-build → cli-install → cli-build.
|
|
388
|
+
const appIsInitialized = appDir
|
|
389
|
+
? await fileExists(path.join(appDir, 'package.json'))
|
|
390
|
+
: false;
|
|
391
|
+
const nameOrScopeChanged = prev
|
|
392
|
+
? prev.name !== spec.name || prev.scope !== spec.scope
|
|
393
|
+
: false;
|
|
394
|
+
if (appDir && appIsInitialized && nameOrScopeChanged) {
|
|
395
|
+
const patched = await patchAppPackageName(appDir, spec, warn);
|
|
396
|
+
if (patched) {
|
|
397
|
+
const appFolder = path.basename(appDir);
|
|
398
|
+
files.push({
|
|
399
|
+
absolutePath: path.join(appDir, 'package.json'),
|
|
400
|
+
relativePath: path.join(appFolder, 'package.json'),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
await writeGeneratedState(targetDir, generatedStateFromSpec(spec, appIsInitialized));
|
|
405
|
+
const result = {
|
|
284
406
|
mode: 'update',
|
|
285
407
|
files,
|
|
286
408
|
skipped,
|
|
@@ -288,5 +410,8 @@ async function runUpdate(options, warn, migrated) {
|
|
|
288
410
|
migrated,
|
|
289
411
|
cliDir,
|
|
290
412
|
appDir,
|
|
413
|
+
pendingActions: [],
|
|
291
414
|
};
|
|
415
|
+
result.pendingActions = await collectPostGenActions(result, spec);
|
|
416
|
+
return result;
|
|
292
417
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { ClintProjectSpec } from '../spec/types.js';
|
|
2
|
+
import type { ProcessRunOptions } from '@powerhousedao/ph-clint';
|
|
3
|
+
/**
|
|
4
|
+
* Signature for the subprocess runner — matches CommandContext.runProcess.
|
|
5
|
+
*/
|
|
6
|
+
type RunProcess = (command: string, opts?: Omit<ProcessRunOptions, 'onOutput'>) => Promise<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
output: string;
|
|
9
|
+
}>;
|
|
2
10
|
export interface PhInitOptions {
|
|
3
11
|
/** Absolute path to the project root. */
|
|
4
12
|
targetDir: string;
|
|
@@ -9,22 +17,41 @@ export interface PhInitOptions {
|
|
|
9
17
|
log?: (msg: string) => void;
|
|
10
18
|
/** Override `ph` binary name (for tests). */
|
|
11
19
|
binName?: string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
20
|
+
/** Subprocess runner — uses CommandContext.runProcess when available. */
|
|
21
|
+
runProcess?: RunProcess;
|
|
22
|
+
/** Explicit Powerhouse version to pin `ph init` to (tag or semver). */
|
|
23
|
+
phVersion?: string;
|
|
14
24
|
}
|
|
15
25
|
export interface PhInitResult {
|
|
16
26
|
ran: boolean;
|
|
17
27
|
exitCode?: number;
|
|
18
28
|
reason?: string;
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Detect the installed `ph` CLI version by parsing `ph --version` output.
|
|
32
|
+
* Returns the version string (e.g. '6.0.0-dev.194') or undefined if
|
|
33
|
+
* detection fails.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPhVersion(binName: string): string | undefined;
|
|
20
36
|
export declare function runPhInit(options: PhInitOptions): Promise<PhInitResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Patch the app `package.json` name to the scoped form (`@scope/name-app`).
|
|
39
|
+
* Called after `ph init` and after name/scope renames.
|
|
40
|
+
*
|
|
41
|
+
* Returns `true` if the file was actually changed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function patchAppPackageName(appDir: string, spec: {
|
|
44
|
+
name: string;
|
|
45
|
+
scope?: string;
|
|
46
|
+
}, log?: (msg: string) => void): Promise<boolean>;
|
|
21
47
|
export interface PnpmInstallOptions {
|
|
22
48
|
/** Directories to run `pnpm install` in, in order. */
|
|
23
49
|
dirs: string[];
|
|
24
50
|
log?: (msg: string) => void;
|
|
25
51
|
/** Override `pnpm` binary name (for tests). */
|
|
26
52
|
binName?: string;
|
|
27
|
-
|
|
53
|
+
/** Subprocess runner — uses CommandContext.runProcess when available. */
|
|
54
|
+
runProcess?: RunProcess;
|
|
28
55
|
}
|
|
29
56
|
export interface PnpmInstallResult {
|
|
30
57
|
ran: string[];
|
|
@@ -32,3 +59,4 @@ export interface PnpmInstallResult {
|
|
|
32
59
|
reason?: string;
|
|
33
60
|
}
|
|
34
61
|
export declare function runPnpmInstall(options: PnpmInstallOptions): Promise<PnpmInstallResult>;
|
|
62
|
+
export {};
|
package/dist/codegen/scaffold.js
CHANGED
|
@@ -7,33 +7,118 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'node:fs/promises';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import {
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import { hasCommandOnPath } from './exec.js';
|
|
12
|
+
/**
|
|
13
|
+
* Detect the installed `ph` CLI version by parsing `ph --version` output.
|
|
14
|
+
* Returns the version string (e.g. '6.0.0-dev.194') or undefined if
|
|
15
|
+
* detection fails.
|
|
16
|
+
*/
|
|
17
|
+
export function getPhVersion(binName) {
|
|
18
|
+
try {
|
|
19
|
+
const output = execFileSync(binName, ['--version'], {
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
timeout: 5_000,
|
|
22
|
+
});
|
|
23
|
+
const match = output.match(/PH CMD version:\s*(\S+)/);
|
|
24
|
+
return match?.[1];
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
11
30
|
export async function runPhInit(options) {
|
|
12
31
|
const log = options.log ?? (() => { });
|
|
13
32
|
const binName = options.binName ?? 'ph';
|
|
14
33
|
const appFolder = path.basename(options.appDir);
|
|
34
|
+
// Guard: never re-init an already-initialized app directory.
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(path.join(options.appDir, 'package.json'));
|
|
37
|
+
log(`App directory ${appFolder} already initialized — skipping ph init.`);
|
|
38
|
+
return { ran: false, reason: 'already-initialized' };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// No package.json — proceed with init.
|
|
42
|
+
}
|
|
15
43
|
if (!(await hasCommandOnPath(binName))) {
|
|
16
44
|
log(`Skipping \`${binName} init\` — \`${binName}\` is not on PATH. ` +
|
|
17
45
|
`Install @powerhousedao/ph-cli and re-run manually inside ${appFolder}/.`);
|
|
18
46
|
return { ran: false, reason: 'ph-not-on-path' };
|
|
19
47
|
}
|
|
48
|
+
// Pin ph init to the same version as the installed ph CLI, so the
|
|
49
|
+
// generated app uses matching Powerhouse packages.
|
|
50
|
+
const phVersion = options.phVersion ?? getPhVersion(binName);
|
|
51
|
+
const tags = ['dev', 'staging', 'latest'];
|
|
52
|
+
const versionArgs = phVersion
|
|
53
|
+
? tags.includes(phVersion)
|
|
54
|
+
? [`--${phVersion}`]
|
|
55
|
+
: ['--version', phVersion]
|
|
56
|
+
: ['--dev']; // fallback if detection fails
|
|
20
57
|
// `ph init {name}` creates a fresh directory beside the cwd. Our generator
|
|
21
58
|
// placed a `.gitkeep` + README.md placeholder there; clear it out so
|
|
22
59
|
// ph init has a blank slate to work with.
|
|
23
60
|
await fs.rm(options.appDir, { recursive: true, force: true });
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
const args = ['init', appFolder, ...versionArgs, '--pnpm'];
|
|
62
|
+
log(`Running \`${binName} init ${appFolder}\` (version: ${phVersion ?? 'auto'}) in ${options.targetDir} …`);
|
|
63
|
+
const run = options.runProcess;
|
|
64
|
+
if (run) {
|
|
65
|
+
const result = await run(`${binName} ${args.join(' ')}`, {
|
|
66
|
+
cwd: options.targetDir,
|
|
67
|
+
timeout: 300_000, // ph init + pnpm install can be slow
|
|
68
|
+
});
|
|
69
|
+
const exitCode = result.success ? 0 : 1;
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
log(`\`${binName} init\` failed. ` +
|
|
72
|
+
'You can re-run it manually after resolving the issue.');
|
|
73
|
+
}
|
|
74
|
+
// Patch scoped name
|
|
75
|
+
await patchScopedName(options, appFolder, log);
|
|
76
|
+
return { ran: true, exitCode };
|
|
77
|
+
}
|
|
78
|
+
// Fallback: direct spawn with stdio inherit (for backward compat / tests without context)
|
|
79
|
+
const { runCommand } = await import('./exec.js');
|
|
80
|
+
const result = await runCommand(binName, args, { cwd: options.targetDir, stdio: 'inherit' });
|
|
31
81
|
if (result.exitCode !== 0) {
|
|
32
82
|
log(`\`${binName} init\` exited with code ${result.exitCode}. ` +
|
|
33
83
|
'You can re-run it manually after resolving the issue.');
|
|
34
84
|
}
|
|
85
|
+
await patchScopedName(options, appFolder, log);
|
|
35
86
|
return { ran: true, exitCode: result.exitCode };
|
|
36
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Patch the app `package.json` name to the scoped form (`@scope/name-app`).
|
|
90
|
+
* Called after `ph init` and after name/scope renames.
|
|
91
|
+
*
|
|
92
|
+
* Returns `true` if the file was actually changed.
|
|
93
|
+
*/
|
|
94
|
+
export async function patchAppPackageName(appDir, spec, log) {
|
|
95
|
+
const appFolder = path.basename(appDir);
|
|
96
|
+
const scopedName = spec.scope
|
|
97
|
+
? `@${spec.scope}/${appFolder}`
|
|
98
|
+
: appFolder;
|
|
99
|
+
const appPkgJsonPath = path.join(appDir, 'package.json');
|
|
100
|
+
try {
|
|
101
|
+
const raw = await fs.readFile(appPkgJsonPath, 'utf8');
|
|
102
|
+
const pkg = JSON.parse(raw);
|
|
103
|
+
if (pkg.name !== scopedName) {
|
|
104
|
+
pkg.name = scopedName;
|
|
105
|
+
if (spec.scope) {
|
|
106
|
+
pkg.publishConfig = { access: 'public' };
|
|
107
|
+
}
|
|
108
|
+
await fs.writeFile(appPkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
109
|
+
log?.(`Patched app package name to ${scopedName}`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
log?.(`Warning: could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function patchScopedName(options, _appFolder, log) {
|
|
120
|
+
await patchAppPackageName(options.appDir, options.spec, log);
|
|
121
|
+
}
|
|
37
122
|
export async function runPnpmInstall(options) {
|
|
38
123
|
const log = options.log ?? (() => { });
|
|
39
124
|
const binName = options.binName ?? 'pnpm';
|
|
@@ -45,14 +130,27 @@ export async function runPnpmInstall(options) {
|
|
|
45
130
|
const ran = [];
|
|
46
131
|
for (const dir of options.dirs) {
|
|
47
132
|
log(`Running \`${binName} install\` in ${dir} …`);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
133
|
+
const run = options.runProcess;
|
|
134
|
+
if (run) {
|
|
135
|
+
const result = await run(`${binName} install`, {
|
|
136
|
+
cwd: dir,
|
|
137
|
+
timeout: 300_000,
|
|
138
|
+
});
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
log(`\`${binName} install\` failed in ${dir}. ` +
|
|
141
|
+
'Retry manually once the underlying issue is resolved.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const { runCommand } = await import('./exec.js');
|
|
146
|
+
const result = await runCommand(binName, ['install'], {
|
|
147
|
+
cwd: dir,
|
|
148
|
+
stdio: 'inherit',
|
|
149
|
+
});
|
|
150
|
+
if (result.exitCode !== 0) {
|
|
151
|
+
log(`\`${binName} install\` exited with code ${result.exitCode} in ${dir}. ` +
|
|
152
|
+
'Retry manually once the underlying issue is resolved.');
|
|
153
|
+
}
|
|
56
154
|
}
|
|
57
155
|
ran.push(dir);
|
|
58
156
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
2
|
import { defineCommand } from '@powerhousedao/ph-clint';
|
|
4
3
|
import { z } from 'zod';
|
|
5
4
|
import { detectLayout } from '@powerhousedao/ph-clint-dev';
|
|
@@ -7,44 +6,28 @@ const inputSchema = z.object({
|
|
|
7
6
|
dir: z.string().optional().describe('Project root directory (default: cwd)'),
|
|
8
7
|
verbose: z.boolean().default(false),
|
|
9
8
|
});
|
|
10
|
-
function execInDir(dir, cmd, verbose) {
|
|
11
|
-
return new Promise((resolve, reject) => {
|
|
12
|
-
const proc = spawn(cmd, {
|
|
13
|
-
cwd: dir,
|
|
14
|
-
shell: true,
|
|
15
|
-
stdio: verbose ? 'inherit' : 'pipe',
|
|
16
|
-
});
|
|
17
|
-
let stderr = '';
|
|
18
|
-
if (!verbose && proc.stderr) {
|
|
19
|
-
proc.stderr.on('data', (data) => {
|
|
20
|
-
stderr += data.toString();
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
proc.on('close', (code) => {
|
|
24
|
-
if (code === 0)
|
|
25
|
-
resolve();
|
|
26
|
-
else
|
|
27
|
-
reject(new Error(`Command "${cmd}" failed in ${dir} (exit ${code})${stderr ? `:\n${stderr}` : ''}`));
|
|
28
|
-
});
|
|
29
|
-
proc.on('error', (err) => reject(err));
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
9
|
export const build = defineCommand({
|
|
33
10
|
id: 'clint-project-build',
|
|
34
11
|
description: 'Build all packages in the project',
|
|
35
12
|
inputSchema,
|
|
36
|
-
execute: async (input, { workdir, stdout }) => {
|
|
13
|
+
execute: async (input, { workdir, stdout, runProcess }) => {
|
|
37
14
|
const dir = path.resolve(input.dir ?? workdir);
|
|
38
15
|
const layout = detectLayout(dir);
|
|
39
16
|
if (layout?.type === 'split') {
|
|
40
17
|
stdout(`Building app package (${layout.app})...\n`);
|
|
41
|
-
await
|
|
18
|
+
const appResult = await runProcess('pnpm build', { cwd: layout.app, timeout: 120_000 });
|
|
19
|
+
if (!appResult.success)
|
|
20
|
+
return { text: `App build failed.` };
|
|
42
21
|
stdout(`Building cli package (${layout.cli})...\n`);
|
|
43
|
-
await
|
|
22
|
+
const cliResult = await runProcess('pnpm build', { cwd: layout.cli, timeout: 120_000 });
|
|
23
|
+
if (!cliResult.success)
|
|
24
|
+
return { text: `CLI build failed.` };
|
|
44
25
|
}
|
|
45
26
|
else {
|
|
46
27
|
stdout(`Building project...\n`);
|
|
47
|
-
await
|
|
28
|
+
const result = await runProcess('pnpm build', { cwd: layout?.cli ?? dir, timeout: 120_000 });
|
|
29
|
+
if (!result.success)
|
|
30
|
+
return { text: `Build failed.` };
|
|
48
31
|
}
|
|
49
32
|
return { text: 'Build complete.' };
|
|
50
33
|
},
|
|
@@ -8,7 +8,6 @@ declare const inputSchema: z.ZodObject<{
|
|
|
8
8
|
enableMastra: z.ZodDefault<z.ZodBoolean>;
|
|
9
9
|
enableRoutine: z.ZodDefault<z.ZodBoolean>;
|
|
10
10
|
force: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
-
skipPhInit: z.ZodDefault<z.ZodBoolean>;
|
|
12
11
|
skipInstall: z.ZodDefault<z.ZodBoolean>;
|
|
13
12
|
}, z.core.$strip>;
|
|
14
13
|
type InitInput = z.output<typeof inputSchema>;
|
|
@@ -21,7 +20,6 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
|
|
|
21
20
|
enableMastra: z.ZodDefault<z.ZodBoolean>;
|
|
22
21
|
enableRoutine: z.ZodDefault<z.ZodBoolean>;
|
|
23
22
|
force: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
-
skipPhInit: z.ZodDefault<z.ZodBoolean>;
|
|
25
23
|
skipInstall: z.ZodDefault<z.ZodBoolean>;
|
|
26
24
|
}, z.core.$strip>, {
|
|
27
25
|
text: string;
|
|
@@ -35,21 +33,45 @@ export declare const init: import("@powerhousedao/ph-clint").Command<z.ZodObject
|
|
|
35
33
|
version: string;
|
|
36
34
|
description: string;
|
|
37
35
|
features: {
|
|
38
|
-
powerhouse:
|
|
39
|
-
enabled: boolean;
|
|
40
|
-
switchboard: boolean;
|
|
41
|
-
connect: boolean;
|
|
42
|
-
};
|
|
36
|
+
powerhouse: "Disabled" | "Reactor" | "Switchboard" | "Connect";
|
|
43
37
|
mastra: {
|
|
44
38
|
enabled: boolean;
|
|
39
|
+
agentId: string | null;
|
|
40
|
+
agentName: string | null;
|
|
41
|
+
agentDescription: string | null;
|
|
42
|
+
agentImage: string | null;
|
|
43
|
+
models: {
|
|
44
|
+
id: string;
|
|
45
|
+
isDefault: boolean;
|
|
46
|
+
}[];
|
|
47
|
+
profiles: {
|
|
48
|
+
id: string;
|
|
49
|
+
title: string;
|
|
50
|
+
content: string;
|
|
51
|
+
}[];
|
|
45
52
|
};
|
|
46
53
|
routine: {
|
|
47
54
|
enabled: boolean;
|
|
48
55
|
};
|
|
49
56
|
};
|
|
50
|
-
|
|
57
|
+
packages: {
|
|
58
|
+
id: string;
|
|
59
|
+
packageName: string;
|
|
60
|
+
documentTypes: string[];
|
|
61
|
+
}[];
|
|
62
|
+
externalSkills: {
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
githubUrl: string;
|
|
66
|
+
}[];
|
|
67
|
+
deployment: {
|
|
68
|
+
proxyEnabled: boolean;
|
|
69
|
+
supportedResources: string[];
|
|
70
|
+
};
|
|
51
71
|
scope?: string | undefined;
|
|
52
72
|
bin?: string | undefined;
|
|
73
|
+
documentId?: string | undefined;
|
|
74
|
+
documentType?: string | undefined;
|
|
53
75
|
};
|
|
54
76
|
fileCount: number;
|
|
55
77
|
};
|