@powerhousedao/ph-clint-cli 0.1.0-dev.25 → 0.1.0-dev.27

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.
@@ -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,8 +17,8 @@ export interface PhInitOptions {
9
17
  log?: (msg: string) => void;
10
18
  /** Override `ph` binary name (for tests). */
11
19
  binName?: string;
12
- /** Stdio passthrough (tests pass 'ignore'). */
13
- stdio?: 'inherit' | 'ignore';
20
+ /** Subprocess runner — uses CommandContext.runProcess when available. */
21
+ runProcess?: RunProcess;
14
22
  /** Explicit Powerhouse version to pin `ph init` to (tag or semver). */
15
23
  phVersion?: string;
16
24
  }
@@ -32,7 +40,8 @@ export interface PnpmInstallOptions {
32
40
  log?: (msg: string) => void;
33
41
  /** Override `pnpm` binary name (for tests). */
34
42
  binName?: string;
35
- stdio?: 'inherit' | 'ignore';
43
+ /** Subprocess runner — uses CommandContext.runProcess when available. */
44
+ runProcess?: RunProcess;
36
45
  }
37
46
  export interface PnpmInstallResult {
38
47
  ran: string[];
@@ -40,3 +49,4 @@ export interface PnpmInstallResult {
40
49
  reason?: string;
41
50
  }
42
51
  export declare function runPnpmInstall(options: PnpmInstallOptions): Promise<PnpmInstallResult>;
52
+ export {};
@@ -8,7 +8,7 @@
8
8
  import fs from 'node:fs/promises';
9
9
  import path from 'node:path';
10
10
  import { execFileSync } from 'node:child_process';
11
- import { hasCommandOnPath, runCommand } from './exec.js';
11
+ import { hasCommandOnPath } from './exec.js';
12
12
  /**
13
13
  * Detect the installed `ph` CLI version by parsing `ph --version` output.
14
14
  * Returns the version string (e.g. '6.0.0-dev.194') or undefined if
@@ -49,39 +49,51 @@ export async function runPhInit(options) {
49
49
  // placed a `.gitkeep` + README.md placeholder there; clear it out so
50
50
  // ph init has a blank slate to work with.
51
51
  await fs.rm(options.appDir, { recursive: true, force: true });
52
+ const args = ['init', appFolder, ...versionArgs, '--pnpm'];
52
53
  log(`Running \`${binName} init ${appFolder}\` (version: ${phVersion ?? 'auto'}) in ${options.targetDir} …`);
53
- const result = await runCommand(binName, [
54
- 'init',
55
- appFolder,
56
- ...versionArgs,
57
- '--pnpm',
58
- ], { cwd: options.targetDir, stdio: options.stdio ?? 'inherit' });
54
+ const run = options.runProcess;
55
+ if (run) {
56
+ const result = await run(`${binName} ${args.join(' ')}`, {
57
+ cwd: options.targetDir,
58
+ timeout: 300_000, // ph init + pnpm install can be slow
59
+ });
60
+ const exitCode = result.success ? 0 : 1;
61
+ if (!result.success) {
62
+ log(`\`${binName} init\` failed. ` +
63
+ 'You can re-run it manually after resolving the issue.');
64
+ }
65
+ // Patch scoped name
66
+ await patchScopedName(options, appFolder, log);
67
+ return { ran: true, exitCode };
68
+ }
69
+ // Fallback: direct spawn with stdio inherit (for backward compat / tests without context)
70
+ const { runCommand } = await import('./exec.js');
71
+ const result = await runCommand(binName, args, { cwd: options.targetDir, stdio: 'inherit' });
59
72
  if (result.exitCode !== 0) {
60
73
  log(`\`${binName} init\` exited with code ${result.exitCode}. ` +
61
74
  'You can re-run it manually after resolving the issue.');
62
75
  }
63
- // ph init creates an unscoped package name. If the spec has a scope, patch
64
- // the app's package.json to use the scoped name so `file:` deps and publish
65
- // both resolve correctly. Run even on non-zero exit — ph init may have
66
- // partially succeeded and created package.json.
67
- if (options.spec.scope) {
68
- const appPkgJsonPath = path.join(options.appDir, 'package.json');
69
- try {
70
- const raw = await fs.readFile(appPkgJsonPath, 'utf8');
71
- const pkg = JSON.parse(raw);
72
- const scopedName = `@${options.spec.scope}/${appFolder}`;
73
- if (pkg.name !== scopedName) {
74
- pkg.name = scopedName;
75
- pkg.publishConfig = { access: 'public' };
76
- await fs.writeFile(appPkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
77
- log(`Patched app package name to ${scopedName}`);
78
- }
79
- }
80
- catch (err) {
81
- log(`Warning: could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
76
+ await patchScopedName(options, appFolder, log);
77
+ return { ran: true, exitCode: result.exitCode };
78
+ }
79
+ async function patchScopedName(options, appFolder, log) {
80
+ if (!options.spec.scope)
81
+ return;
82
+ const appPkgJsonPath = path.join(options.appDir, 'package.json');
83
+ try {
84
+ const raw = await fs.readFile(appPkgJsonPath, 'utf8');
85
+ const pkg = JSON.parse(raw);
86
+ const scopedName = `@${options.spec.scope}/${appFolder}`;
87
+ if (pkg.name !== scopedName) {
88
+ pkg.name = scopedName;
89
+ pkg.publishConfig = { access: 'public' };
90
+ await fs.writeFile(appPkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
91
+ log(`Patched app package name to ${scopedName}`);
82
92
  }
83
93
  }
84
- return { ran: true, exitCode: result.exitCode };
94
+ catch (err) {
95
+ log(`Warning: could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
96
+ }
85
97
  }
86
98
  export async function runPnpmInstall(options) {
87
99
  const log = options.log ?? (() => { });
@@ -94,14 +106,27 @@ export async function runPnpmInstall(options) {
94
106
  const ran = [];
95
107
  for (const dir of options.dirs) {
96
108
  log(`Running \`${binName} install\` in ${dir} …`);
97
- const result = await runCommand(binName, ['install'], {
98
- cwd: dir,
99
- stdio: options.stdio ?? 'inherit',
100
- });
101
- if (result.exitCode !== 0) {
102
- log(`\`${binName} install\` exited with code ${result.exitCode} in ${dir}. ` +
103
- 'Retry manually once the underlying issue is resolved.');
104
- // Don't abort the whole chain — subsequent dirs may still work.
109
+ const run = options.runProcess;
110
+ if (run) {
111
+ const result = await run(`${binName} install`, {
112
+ cwd: dir,
113
+ timeout: 300_000,
114
+ });
115
+ if (!result.success) {
116
+ log(`\`${binName} install\` failed in ${dir}. ` +
117
+ 'Retry manually once the underlying issue is resolved.');
118
+ }
119
+ }
120
+ else {
121
+ const { runCommand } = await import('./exec.js');
122
+ const result = await runCommand(binName, ['install'], {
123
+ cwd: dir,
124
+ stdio: 'inherit',
125
+ });
126
+ if (result.exitCode !== 0) {
127
+ log(`\`${binName} install\` exited with code ${result.exitCode} in ${dir}. ` +
128
+ 'Retry manually once the underlying issue is resolved.');
129
+ }
105
130
  }
106
131
  ran.push(dir);
107
132
  }
@@ -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 execInDir(layout.app, 'pnpm build', input.verbose);
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 execInDir(layout.cli, 'pnpm build', input.verbose);
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 execInDir(layout?.cli ?? dir, 'pnpm build', input.verbose);
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
  },
@@ -102,7 +102,7 @@ export const init = defineCommand({
102
102
  promptForDefaults: true,
103
103
  promptOptional: ['description'],
104
104
  },
105
- execute: async (input, { workdir, stdout, reactor: getReactor, folders }) => {
105
+ execute: async (input, { workdir, stdout, reactor: getReactor, folders, runProcess }) => {
106
106
  const { name: bareName } = splitPackageName(input.name);
107
107
  const targetDir = path.resolve(workdir, input.dir ?? bareName);
108
108
  if (!input.force) {
@@ -151,6 +151,7 @@ export const init = defineCommand({
151
151
  appDir: result.appDir,
152
152
  spec,
153
153
  log: (msg) => stdout(msg + '\n'),
154
+ runProcess,
154
155
  });
155
156
  if (!phResult.ran) {
156
157
  stdout(`Note: \`ph init\` did not run — ${phResult.reason ?? 'see above'}.\n`);
@@ -170,6 +171,7 @@ export const init = defineCommand({
170
171
  await runPnpmInstall({
171
172
  dirs,
172
173
  log: (msg) => stdout(msg + '\n'),
174
+ runProcess,
173
175
  });
174
176
  }
175
177
  const next = [];
@@ -200,7 +200,7 @@ export const specChangeTrigger = createDocumentChangeTrigger({
200
200
  appDir: result.appDir,
201
201
  spec,
202
202
  log: (msg) => log?.info(`${TAG} ${msg}`),
203
- stdio: 'ignore',
203
+ runProcess: ctx.context.runProcess,
204
204
  });
205
205
  if (!phResult.ran) {
206
206
  log?.warn(`${TAG} ph init skipped: ${phResult.reason ?? 'unknown'}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/ph-clint-cli",
3
- "version": "0.1.0-dev.25",
3
+ "version": "0.1.0-dev.27",
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.25",
33
- "@powerhousedao/ph-clint-app": "^0.1.0-dev.25",
34
- "@powerhousedao/ph-clint-dev": "^0.1.0-dev.25",
32
+ "@powerhousedao/ph-clint": "^0.1.0-dev.27",
33
+ "@powerhousedao/ph-clint-app": "^0.1.0-dev.27",
34
+ "@powerhousedao/ph-clint-dev": "^0.1.0-dev.27",
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",