@powerhousedao/ph-clint-cli 0.1.0-dev.47 → 0.1.0-dev.48

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.
@@ -9,6 +9,10 @@ export type PostGenAction = {
9
9
  } | {
10
10
  kind: 'app-install';
11
11
  dir: string;
12
+ } | {
13
+ kind: 'app-ph-install';
14
+ appDir: string;
15
+ packages: string[];
12
16
  } | {
13
17
  kind: 'app-build';
14
18
  dir: string;
@@ -9,12 +9,14 @@
9
9
  */
10
10
  import fs from 'node:fs/promises';
11
11
  import path from 'node:path';
12
+ import { getAppPackageName } from '../spec/types.js';
12
13
  // ---------------------------------------------------------------------------
13
14
  // Fixed execution order — index defines precedence
14
15
  // ---------------------------------------------------------------------------
15
16
  const ACTION_ORDER = [
16
17
  'ph-init',
17
18
  'app-install',
19
+ 'app-ph-install',
18
20
  'app-build',
19
21
  'cli-install',
20
22
  'cli-build',
@@ -28,11 +30,12 @@ function actionIndex(kind) {
28
30
  // ---------------------------------------------------------------------------
29
31
  /** Returns true if `downstream` depends on `upstream` succeeding. */
30
32
  function dependsOn(downstream, upstream) {
31
- // ph-init → app-install → app-build → cli-install → cli-build
33
+ // ph-init → app-install → app-ph-install → app-build → cli-install → cli-build
32
34
  // skills-sync is independent of the build chain
33
35
  const chain = [
34
36
  'ph-init',
35
37
  'app-install',
38
+ 'app-ph-install',
36
39
  'app-build',
37
40
  'cli-install',
38
41
  'cli-build',
@@ -57,6 +60,29 @@ async function fileExists(filePath) {
57
60
  return false;
58
61
  }
59
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
+ }
60
86
  // ---------------------------------------------------------------------------
61
87
  // collectPostGenActions
62
88
  // ---------------------------------------------------------------------------
@@ -133,6 +159,16 @@ export async function collectPostGenActions(result, spec) {
133
159
  }
134
160
  }
135
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
+ }
136
172
  // Build the action list from earliest onward
137
173
  const actions = [];
138
174
  if (earliest && split && result.appDir) {
@@ -148,6 +184,12 @@ export async function collectPostGenActions(result, spec) {
148
184
  if (ei <= actionIndex('app-install')) {
149
185
  actions.push({ kind: 'app-install', dir: result.appDir });
150
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
+ }
151
193
  if (ei <= actionIndex('app-build')) {
152
194
  // Add install if not already in the list and node_modules is missing
153
195
  if (!actions.some(a => a.kind === 'app-install') &&
@@ -202,6 +244,8 @@ function actionLabel(action) {
202
244
  return `Initialize reactor package (${path.basename(action.appDir)})`;
203
245
  case 'app-install':
204
246
  return `Install dependencies (${path.basename(action.dir)})`;
247
+ case 'app-ph-install':
248
+ return `Register external packages (${action.packages.join(', ')})`;
205
249
  case 'app-build':
206
250
  return `Build (${path.basename(action.dir)})`;
207
251
  case 'cli-install':
@@ -224,7 +268,9 @@ export async function runPostGenActions(actions, ctx) {
224
268
  const effective = actions.filter((a) => {
225
269
  if (skip.has(a.kind))
226
270
  return false;
227
- // If app-install is skipped, also skip app-build
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;
228
274
  if (a.kind === 'app-build' && skip.has('app-install'))
229
275
  return false;
230
276
  // If cli-install is skipped, also skip cli-build
@@ -332,6 +378,15 @@ async function executeAction(action, ctx) {
332
378
  }
333
379
  return result.exitCode === 0;
334
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
+ }
335
390
  case 'app-install':
336
391
  case 'cli-install': {
337
392
  if (ctx.runProcess) {
@@ -4,6 +4,11 @@
4
4
  * specific regions on subsequent runs (delta mode — future phase).
5
5
  */
6
6
  import { getAppPackageName, phAtLeast } from '../../spec/types.js';
7
+ /** External packages that have document types (not the app package). */
8
+ function getExternalPackages(spec) {
9
+ const appPkg = getAppPackageName(spec);
10
+ return spec.packages.filter((p) => p.packageName !== appPkg && p.documentTypes.length > 0);
11
+ }
7
12
  export function buildCliTs(spec) {
8
13
  const { mastra, routine, powerhouse } = spec.features;
9
14
  const ph = powerhouse;
@@ -29,6 +34,11 @@ export function buildCliTs(spec) {
29
34
  lines.push(`import { configSchema, secretsSchema } from './framework.js';`);
30
35
  if (phAtLeast(ph, 'Reactor')) {
31
36
  lines.push(`import { documentModels } from '${getAppPackageName(spec)}';`);
37
+ const extPkgs = getExternalPackages(spec);
38
+ for (let i = 0; i < extPkgs.length; i++) {
39
+ const alias = extPkgs.length === 1 ? 'extModels' : `extModels${i}`;
40
+ lines.push(`import { documentModels as ${alias} } from '${extPkgs[i].packageName}';`);
41
+ }
32
42
  }
33
43
  if (mastra.enabled) {
34
44
  lines.push(`import { createAgent } from './agents/agent.js';`);
@@ -131,7 +141,14 @@ export function buildCliTs(spec) {
131
141
  lines.push('cli.configureReactor({');
132
142
  lines.push(' create: (ctx) =>');
133
143
  lines.push(' buildDefaultReactor(ctx, {');
134
- lines.push(' documentModels,');
144
+ const extPkgs = getExternalPackages(spec);
145
+ if (extPkgs.length === 0) {
146
+ lines.push(' documentModels,');
147
+ }
148
+ else {
149
+ const aliases = extPkgs.map((_, i) => extPkgs.length === 1 ? 'extModels' : `extModels${i}`);
150
+ lines.push(` documentModels: [...documentModels, ${aliases.map(a => `...${a}`).join(', ')}],`);
151
+ }
135
152
  lines.push(` drive: { name: '${spec.name}' },`);
136
153
  lines.push(' }),');
137
154
  lines.push(` switchboard: { enabled: ${String(phAtLeast(ph, 'Switchboard'))} },`);
@@ -44,6 +44,16 @@ export declare function patchAppPackageName(appDir: string, spec: {
44
44
  name: string;
45
45
  scope?: string;
46
46
  }, log?: (msg: string) => void): Promise<boolean>;
47
+ /**
48
+ * Run `ph install <packages> --local --pnpm` in the app directory to register
49
+ * external packages in `powerhouse.config.json`.
50
+ */
51
+ export declare function runPhInstallPackages(options: {
52
+ appDir: string;
53
+ packages: string[];
54
+ log?: (msg: string) => void;
55
+ runProcess?: RunProcess;
56
+ }): Promise<boolean>;
47
57
  export interface PnpmInstallOptions {
48
58
  /** Directories to run `pnpm install` in, in order. */
49
59
  dirs: string[];
@@ -119,6 +119,33 @@ export async function patchAppPackageName(appDir, spec, log) {
119
119
  async function patchScopedName(options, _appFolder, log) {
120
120
  await patchAppPackageName(options.appDir, options.spec, log);
121
121
  }
122
+ /**
123
+ * Run `ph install <packages> --local --pnpm` in the app directory to register
124
+ * external packages in `powerhouse.config.json`.
125
+ */
126
+ export async function runPhInstallPackages(options) {
127
+ const { appDir, packages, log = () => { }, runProcess } = options;
128
+ const binName = 'ph';
129
+ if (!(await hasCommandOnPath(binName))) {
130
+ log(`Skipping — \`${binName}\` is not on PATH.`);
131
+ return false;
132
+ }
133
+ const args = ['install', ...packages, '--local', '--pnpm'];
134
+ log(`Running \`${binName} install ${packages.join(' ')} --local\` in ${path.basename(appDir)} …`);
135
+ if (runProcess) {
136
+ const result = await runProcess(`${binName} ${args.join(' ')}`, {
137
+ cwd: appDir,
138
+ timeout: 300_000,
139
+ });
140
+ return result.success;
141
+ }
142
+ const { runCommand } = await import('./exec.js');
143
+ const result = await runCommand(binName, args, {
144
+ cwd: appDir,
145
+ stdio: 'inherit',
146
+ });
147
+ return result.exitCode === 0;
148
+ }
122
149
  export async function runPnpmInstall(options) {
123
150
  const log = options.log ?? (() => { });
124
151
  const binName = options.binName ?? 'pnpm';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/ph-clint-cli",
3
- "version": "0.1.0-dev.47",
3
+ "version": "0.1.0-dev.48",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -29,9 +29,9 @@
29
29
  "@mastra/core": "^1.22.0",
30
30
  "@mastra/libsql": "^1.7.4",
31
31
  "@mastra/memory": "^1.13.1",
32
- "@powerhousedao/ph-clint": "^0.1.0-dev.47",
33
- "@powerhousedao/ph-clint-app": "^0.1.0-dev.47",
34
- "@powerhousedao/ph-clint-dev": "^0.1.0-dev.47",
32
+ "@powerhousedao/ph-clint": "^0.1.0-dev.48",
33
+ "@powerhousedao/ph-clint-app": "^0.1.0-dev.48",
34
+ "@powerhousedao/ph-clint-dev": "^0.1.0-dev.48",
35
35
  "@powerhousedao/reactor": "6.0.0-dev.170",
36
36
  "@powerhousedao/reactor-api": "6.0.0-dev.170",
37
37
  "@powerhousedao/shared": "6.0.0-dev.170",