@powerhousedao/ph-clint-cli 0.1.0-dev.57 → 0.1.0-dev.59
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 +1 -46
- package/dist/codegen/actions.js +1 -424
- package/dist/codegen/builders/cli-package-json.d.ts +1 -8
- package/dist/codegen/builders/cli-package-json.js +1 -128
- package/dist/codegen/builders/index.d.ts +1 -52
- package/dist/codegen/builders/index.js +1 -85
- package/dist/codegen/builders/publish-config-ts.d.ts +8 -0
- package/dist/codegen/builders/publish-config-ts.js +32 -0
- package/dist/codegen/exec.d.ts +1 -2
- package/dist/codegen/exec.js +1 -14
- package/dist/codegen/generated.d.ts +1 -17
- package/dist/codegen/generated.js +1 -49
- package/dist/codegen/hashes.d.ts +1 -6
- package/dist/codegen/hashes.js +1 -60
- package/dist/codegen/markers.d.ts +1 -36
- package/dist/codegen/markers.js +1 -86
- package/dist/codegen/scaffold.d.ts +1 -72
- package/dist/codegen/scaffold.js +1 -150
- package/dist/codegen/write.d.ts +1 -6
- package/dist/codegen/write.js +1 -32
- package/dist/commands/init.d.ts +57 -0
- package/dist/commands/init.js +168 -0
- package/dist/commands/regen.d.ts +18 -0
- package/dist/commands/regen.js +68 -0
- package/dist/main.js +0 -0
- package/dist/skills/sync.d.ts +1 -40
- package/dist/skills/sync.js +1 -119
- package/package.json +4 -4
|
@@ -1,85 +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
|
-
/**
|
|
26
|
-
* Generate dynamic file builders for agent profile markdown files.
|
|
27
|
-
* One file per profile in `prompts/agent-profiles/{id}.md`.
|
|
28
|
-
*/
|
|
29
|
-
export function getProfileFileBuilders(spec) {
|
|
30
|
-
const { mastra } = spec.features;
|
|
31
|
-
if (!mastra.enabled || mastra.profiles.length === 0)
|
|
32
|
-
return [];
|
|
33
|
-
return mastra.profiles.map((profile) => ({
|
|
34
|
-
relativePath: `prompts/agent-profiles/${profile.id}.md`,
|
|
35
|
-
build: () => profile.content + '\n',
|
|
36
|
-
}));
|
|
37
|
-
}
|
|
38
|
-
export const CLI_FILE_BUILDERS = [
|
|
39
|
-
{ relativePath: 'powerhouse.manifest.json', build: buildManifestJson },
|
|
40
|
-
{ relativePath: 'package.json', build: buildCliPackageJson },
|
|
41
|
-
{ relativePath: 'tsconfig.json', build: () => buildTsconfigJson() },
|
|
42
|
-
{ relativePath: 'jest.config.js', build: () => buildJestConfigJs() },
|
|
43
|
-
{ relativePath: 'eslint.config.js', build: () => buildEslintConfigJs() },
|
|
44
|
-
{ relativePath: '.gitignore', build: () => buildGitignore() },
|
|
45
|
-
{ relativePath: 'src/main.ts', build: () => buildMainTs() },
|
|
46
|
-
{ relativePath: 'src/cli.ts', build: buildCliTs },
|
|
47
|
-
{ relativePath: 'src/config.ts', build: buildConfigTs },
|
|
48
|
-
// User-owned outside markers; generated config/secrets inside markers.
|
|
49
|
-
{ relativePath: 'src/framework.ts', build: buildFrameworkTs },
|
|
50
|
-
// Machine-owned; regenerated on every run. Null when no document types.
|
|
51
|
-
{ relativePath: 'src/framework.gen.ts', build: buildFrameworkGenTs },
|
|
52
|
-
{ relativePath: 'src/mastra/index.ts', build: buildMastraIndexTs },
|
|
53
|
-
{ relativePath: 'scripts/build-skills.ts', build: () => buildBuildSkillsScript() },
|
|
54
|
-
{
|
|
55
|
-
relativePath: 'prompts/agent-profiles/AgentBase.md',
|
|
56
|
-
build: (spec) => (spec.features.mastra.profiles.length > 0 ? null : buildAgentBaseMd(spec)),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
relativePath: 'src/agents/agent.ts',
|
|
60
|
-
build: (spec) => (spec.features.mastra.enabled ? buildAgentTs(spec) : null),
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
relativePath: 'src/agents/demo-agent.ts',
|
|
64
|
-
build: buildDemoAgentTs,
|
|
65
|
-
},
|
|
66
|
-
// Gitkeep placeholders so directories are tracked in git when otherwise empty.
|
|
67
|
-
{ relativePath: 'src/commands/.gitkeep', build: () => '' },
|
|
68
|
-
{ relativePath: 'src/services/.gitkeep', build: () => '' },
|
|
69
|
-
{
|
|
70
|
-
relativePath: 'src/triggers/.gitkeep',
|
|
71
|
-
build: (spec) => (spec.features.routine.enabled ? '' : ''),
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
relativePath: 'src/agents/.gitkeep',
|
|
75
|
-
build: (spec) => (spec.features.mastra.enabled ? null : ''),
|
|
76
|
-
},
|
|
77
|
-
{ relativePath: 'prompts/skills-tpl/.gitkeep', build: () => '' },
|
|
78
|
-
{ relativePath: 'prompts/skills-ext/.gitkeep', build: () => '' },
|
|
79
|
-
{ relativePath: 'tests/smoke.test.ts', build: buildSmokeTestTs, initOnly: true },
|
|
80
|
-
];
|
|
81
|
-
export { buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, buildSmokeTestTs, };
|
|
82
|
-
export { buildRootPackageJson } from './root-package-json.js';
|
|
83
|
-
export { buildPublishConfigJs } from './publish-config-js.js';
|
|
84
|
-
export { buildReadme } from './readme-md.js';
|
|
85
|
-
export { buildAppIndexTs } from './app-index-ts.js';
|
|
1
|
+
export { CLI_FILE_BUILDERS, getProfileFileBuilders, buildCliPackageJson, buildCliTs, buildConfigTs, buildMainTs, buildTsconfigJson, buildJestConfigJs, buildEslintConfigJs, buildGitignore, buildMastraIndexTs, buildBuildSkillsScript, buildAgentBaseMd, buildAgentTs, buildDemoAgentTs, buildFrameworkGenTs, buildFrameworkTs, buildManifestJson, buildSmokeTestTs, buildRootPackageJson, buildPublishConfigJs, buildReadme, buildAppIndexTs, } from '@powerhousedao/ph-clint-dev/codegen/builders';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the root `publish.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Split-layout: a lockstep group with both app and cli packages.
|
|
5
|
+
* Single-layout: a single-package group at `.`.
|
|
6
|
+
*/
|
|
7
|
+
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
8
|
+
export declare function buildPublishConfigTs(spec: ClintProjectSpec): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the root `publish.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Split-layout: a lockstep group with both app and cli packages.
|
|
5
|
+
* Single-layout: a single-package group at `.`.
|
|
6
|
+
*/
|
|
7
|
+
import { phAtLeast } from '../../spec/types.js';
|
|
8
|
+
export function buildPublishConfigTs(spec) {
|
|
9
|
+
const split = phAtLeast(spec.features.powerhouse, 'Reactor');
|
|
10
|
+
// The publish pipeline appends the prerelease suffix (e.g. -dev.0), so we
|
|
11
|
+
// only store the base semver (M.m.p) here.
|
|
12
|
+
const baseVersion = spec.version.replace(/-.*$/, '');
|
|
13
|
+
const packages = split
|
|
14
|
+
? `[
|
|
15
|
+
{ path: '${spec.name}-app', category: 'app' },
|
|
16
|
+
{ path: '${spec.name}-cli', category: 'cli' },
|
|
17
|
+
]`
|
|
18
|
+
: `[
|
|
19
|
+
{ path: '.', category: 'cli' },
|
|
20
|
+
]`;
|
|
21
|
+
return `// Publish config — loaded by ph-clint's publish pipeline.
|
|
22
|
+
// definePublishConfig is an identity function; a plain export is equivalent.
|
|
23
|
+
export default {
|
|
24
|
+
groups: {
|
|
25
|
+
'${spec.name}': {
|
|
26
|
+
version: '${baseVersion}',
|
|
27
|
+
packages: ${packages},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
`;
|
|
32
|
+
}
|
package/dist/codegen/exec.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function hasCommandOnPath(command: string): Promise<boolean>;
|
|
1
|
+
export { hasCommandOnPath, getPhVersion, } from '@powerhousedao/ph-clint-dev/codegen/exec';
|
package/dist/codegen/exec.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Small helpers for invoking external commands during code generation.
|
|
3
|
-
*/
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
/** Returns true if `command` is on PATH. */
|
|
6
|
-
export async function hasCommandOnPath(command) {
|
|
7
|
-
return new Promise((resolve) => {
|
|
8
|
-
const child = spawn(process.platform === 'win32' ? 'where' : 'which', [command], {
|
|
9
|
-
stdio: 'ignore',
|
|
10
|
-
});
|
|
11
|
-
child.once('error', () => resolve(false));
|
|
12
|
-
child.once('close', (code) => resolve(code === 0));
|
|
13
|
-
});
|
|
14
|
-
}
|
|
1
|
+
export { hasCommandOnPath, getPhVersion, } from '@powerhousedao/ph-clint-dev/codegen/exec';
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export interface GeneratedState {
|
|
3
|
-
name: string;
|
|
4
|
-
scope: string | undefined;
|
|
5
|
-
cliFolderName: string;
|
|
6
|
-
appFolderName: string;
|
|
7
|
-
/** True once `ph init` has run successfully in the app dir. */
|
|
8
|
-
appInitialized: boolean;
|
|
9
|
-
}
|
|
10
|
-
export declare function getGeneratedPath(targetDir: string): string;
|
|
11
|
-
export declare function readGeneratedState(targetDir: string): Promise<GeneratedState | null>;
|
|
12
|
-
export declare function writeGeneratedState(targetDir: string, state: GeneratedState): Promise<void>;
|
|
13
|
-
/**
|
|
14
|
-
* Derive a `GeneratedState` from a spec. The `appInitialized` flag must be
|
|
15
|
-
* supplied by the caller (it depends on filesystem state, not the spec).
|
|
16
|
-
*/
|
|
17
|
-
export declare function generatedStateFromSpec(spec: ClintProjectSpec, appInitialized: boolean): GeneratedState;
|
|
1
|
+
export { type GeneratedState, getGeneratedPath, readGeneratedState, writeGeneratedState, generatedStateFromSpec, } from '@powerhousedao/ph-clint-dev/codegen/generated';
|
|
@@ -1,49 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Tracks the derived state from the last successful code generation.
|
|
3
|
-
*
|
|
4
|
-
* Stored at `{targetDir}/.ph/ph-clint-cli/generated.json`. Used to detect
|
|
5
|
-
* folder renames (name/scope changes) and re-apply patches that depend on
|
|
6
|
-
* the project identity.
|
|
7
|
-
*/
|
|
8
|
-
import fs from 'node:fs/promises';
|
|
9
|
-
import path from 'node:path';
|
|
10
|
-
import { getAppFolderName, getCliFolderName, } from '../spec/types.js';
|
|
11
|
-
const GEN_DIR = path.join('.ph', 'ph-clint-cli');
|
|
12
|
-
const GEN_FILE = 'generated.json';
|
|
13
|
-
export function getGeneratedPath(targetDir) {
|
|
14
|
-
return path.join(targetDir, GEN_DIR, GEN_FILE);
|
|
15
|
-
}
|
|
16
|
-
export async function readGeneratedState(targetDir) {
|
|
17
|
-
try {
|
|
18
|
-
const raw = await fs.readFile(getGeneratedPath(targetDir), 'utf8');
|
|
19
|
-
return JSON.parse(raw);
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
if (err &&
|
|
23
|
-
typeof err === 'object' &&
|
|
24
|
-
'code' in err &&
|
|
25
|
-
err.code === 'ENOENT') {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
throw err;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
export async function writeGeneratedState(targetDir, state) {
|
|
32
|
-
const file = getGeneratedPath(targetDir);
|
|
33
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
34
|
-
await fs.writeFile(file, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Derive a `GeneratedState` from a spec. The `appInitialized` flag must be
|
|
38
|
-
* supplied by the caller (it depends on filesystem state, not the spec).
|
|
39
|
-
*/
|
|
40
|
-
export function generatedStateFromSpec(spec, appInitialized) {
|
|
41
|
-
const split = spec.features.powerhouse !== 'Disabled';
|
|
42
|
-
return {
|
|
43
|
-
name: spec.name,
|
|
44
|
-
scope: spec.scope,
|
|
45
|
-
cliFolderName: split ? getCliFolderName(spec) : '',
|
|
46
|
-
appFolderName: split ? getAppFolderName(spec) : '',
|
|
47
|
-
appInitialized,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
1
|
+
export { getGeneratedPath, readGeneratedState, writeGeneratedState, generatedStateFromSpec, } from '@powerhousedao/ph-clint-dev/codegen/generated';
|
package/dist/codegen/hashes.d.ts
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
export type HashRecord
|
|
2
|
-
export declare function getHashesPath(targetDir: string): string;
|
|
3
|
-
export declare function hashContent(content: string): string;
|
|
4
|
-
export declare function hashFile(filePath: string): Promise<string | null>;
|
|
5
|
-
export declare function readHashes(targetDir: string): Promise<HashRecord>;
|
|
6
|
-
export declare function writeHashes(targetDir: string, hashes: HashRecord): Promise<void>;
|
|
1
|
+
export { type HashRecord, getHashesPath, hashContent, hashFile, readHashes, writeHashes, } from '@powerhousedao/ph-clint-dev/codegen/hashes';
|
package/dist/codegen/hashes.js
CHANGED
|
@@ -1,60 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Per-file content hashes written by the generator so subsequent runs can
|
|
3
|
-
* detect whether a previously-generated file has been hand-edited.
|
|
4
|
-
*
|
|
5
|
-
* Stored at `{targetDir}/.ph/ph-clint-cli/.hashes.json` alongside the project
|
|
6
|
-
* spec. Keys are paths relative to the project root.
|
|
7
|
-
*/
|
|
8
|
-
import crypto from 'node:crypto';
|
|
9
|
-
import fs from 'node:fs/promises';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
const HASHES_DIR = path.join('.ph', 'ph-clint-cli');
|
|
12
|
-
const HASHES_FILE = '.hashes.json';
|
|
13
|
-
export function getHashesPath(targetDir) {
|
|
14
|
-
return path.join(targetDir, HASHES_DIR, HASHES_FILE);
|
|
15
|
-
}
|
|
16
|
-
export function hashContent(content) {
|
|
17
|
-
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
18
|
-
}
|
|
19
|
-
function isEnoent(err) {
|
|
20
|
-
return (!!err &&
|
|
21
|
-
typeof err === 'object' &&
|
|
22
|
-
'code' in err &&
|
|
23
|
-
err.code === 'ENOENT');
|
|
24
|
-
}
|
|
25
|
-
export async function hashFile(filePath) {
|
|
26
|
-
try {
|
|
27
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
28
|
-
return hashContent(content);
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
if (isEnoent(err))
|
|
32
|
-
return null;
|
|
33
|
-
throw err;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export async function readHashes(targetDir) {
|
|
37
|
-
try {
|
|
38
|
-
const raw = await fs.readFile(getHashesPath(targetDir), 'utf8');
|
|
39
|
-
const parsed = JSON.parse(raw);
|
|
40
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
41
|
-
return parsed;
|
|
42
|
-
}
|
|
43
|
-
return {};
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
if (isEnoent(err))
|
|
47
|
-
return {};
|
|
48
|
-
throw err;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export async function writeHashes(targetDir, hashes) {
|
|
52
|
-
const file = getHashesPath(targetDir);
|
|
53
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
54
|
-
// Sort keys for stable output across runs.
|
|
55
|
-
const sorted = {};
|
|
56
|
-
for (const key of Object.keys(hashes).sort()) {
|
|
57
|
-
sorted[key] = hashes[key];
|
|
58
|
-
}
|
|
59
|
-
await fs.writeFile(file, JSON.stringify(sorted, null, 2) + '\n', 'utf8');
|
|
60
|
-
}
|
|
1
|
+
export { getHashesPath, hashContent, hashFile, readHashes, writeHashes, } from '@powerhousedao/ph-clint-dev/codegen/hashes';
|
|
@@ -1,36 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Marker-region parsing for `@clint:begin {name}` / `@clint:end {name}`
|
|
3
|
-
* comment blocks. Used in delta mode to splice freshly-generated regions
|
|
4
|
-
* into an existing, user-edited file while preserving everything outside
|
|
5
|
-
* the markers.
|
|
6
|
-
*
|
|
7
|
-
* The only syntactic requirement is that the markers are on their own lines,
|
|
8
|
-
* as ordinary line comments (`//`). Indentation is preserved for output.
|
|
9
|
-
*/
|
|
10
|
-
export interface MarkerRegion {
|
|
11
|
-
/** Region identifier (the text after `@clint:begin`). */
|
|
12
|
-
name: string;
|
|
13
|
-
/** Lines *between* the begin and end markers (exclusive). */
|
|
14
|
-
body: string[];
|
|
15
|
-
/** Indentation of the begin line — used when rewriting. */
|
|
16
|
-
indent: string;
|
|
17
|
-
/** Line index of the begin marker in the source. */
|
|
18
|
-
beginLine: number;
|
|
19
|
-
/** Line index of the end marker in the source. */
|
|
20
|
-
endLine: number;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Parse every `@clint:begin {name}` … `@clint:end {name}` block in `source`.
|
|
24
|
-
* Throws if markers are mismatched or unclosed.
|
|
25
|
-
*/
|
|
26
|
-
export declare function parseMarkerRegions(source: string): MarkerRegion[];
|
|
27
|
-
/**
|
|
28
|
-
* Replace every region in `target` that also exists in `source` with the
|
|
29
|
-
* `source` region's body. Leaves regions present in `target` but not `source`
|
|
30
|
-
* untouched. Ignores regions in `source` missing from `target`.
|
|
31
|
-
*
|
|
32
|
-
* Returns the rewritten file content.
|
|
33
|
-
*/
|
|
34
|
-
export declare function spliceMarkerRegions(target: string, source: string): string;
|
|
35
|
-
/** Convenience — true if `content` contains at least one `@clint:begin`. */
|
|
36
|
-
export declare function hasMarkers(content: string): boolean;
|
|
1
|
+
export { type MarkerRegion, parseMarkerRegions, spliceMarkerRegions, hasMarkers, } from '@powerhousedao/ph-clint-dev/codegen/markers';
|
package/dist/codegen/markers.js
CHANGED
|
@@ -1,86 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Marker-region parsing for `@clint:begin {name}` / `@clint:end {name}`
|
|
3
|
-
* comment blocks. Used in delta mode to splice freshly-generated regions
|
|
4
|
-
* into an existing, user-edited file while preserving everything outside
|
|
5
|
-
* the markers.
|
|
6
|
-
*
|
|
7
|
-
* The only syntactic requirement is that the markers are on their own lines,
|
|
8
|
-
* as ordinary line comments (`//`). Indentation is preserved for output.
|
|
9
|
-
*/
|
|
10
|
-
const BEGIN_RE = /^([ \t]*)\/\/\s*@clint:begin\s+(\S+)\s*$/;
|
|
11
|
-
const END_RE = /^([ \t]*)\/\/\s*@clint:end\s+(\S+)\s*$/;
|
|
12
|
-
/**
|
|
13
|
-
* Parse every `@clint:begin {name}` … `@clint:end {name}` block in `source`.
|
|
14
|
-
* Throws if markers are mismatched or unclosed.
|
|
15
|
-
*/
|
|
16
|
-
export function parseMarkerRegions(source) {
|
|
17
|
-
const lines = source.split('\n');
|
|
18
|
-
const regions = [];
|
|
19
|
-
let open = null;
|
|
20
|
-
let bodyStart = 0;
|
|
21
|
-
for (let i = 0; i < lines.length; i++) {
|
|
22
|
-
const line = lines[i];
|
|
23
|
-
const begin = BEGIN_RE.exec(line);
|
|
24
|
-
if (begin) {
|
|
25
|
-
if (open) {
|
|
26
|
-
throw new Error(`nested @clint:begin ${begin[2]} at line ${i + 1} while ${open.name} is still open`);
|
|
27
|
-
}
|
|
28
|
-
open = { name: begin[2], indent: begin[1], beginLine: i };
|
|
29
|
-
bodyStart = i + 1;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const end = END_RE.exec(line);
|
|
33
|
-
if (end) {
|
|
34
|
-
if (!open) {
|
|
35
|
-
throw new Error(`@clint:end ${end[2]} at line ${i + 1} without matching begin`);
|
|
36
|
-
}
|
|
37
|
-
if (open.name !== end[2]) {
|
|
38
|
-
throw new Error(`@clint:end ${end[2]} at line ${i + 1} does not match open @clint:begin ${open.name}`);
|
|
39
|
-
}
|
|
40
|
-
regions.push({
|
|
41
|
-
name: open.name,
|
|
42
|
-
body: lines.slice(bodyStart, i),
|
|
43
|
-
indent: open.indent,
|
|
44
|
-
beginLine: open.beginLine,
|
|
45
|
-
endLine: i,
|
|
46
|
-
});
|
|
47
|
-
open = null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (open) {
|
|
51
|
-
throw new Error(`unterminated @clint:begin ${open.name}`);
|
|
52
|
-
}
|
|
53
|
-
return regions;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Replace every region in `target` that also exists in `source` with the
|
|
57
|
-
* `source` region's body. Leaves regions present in `target` but not `source`
|
|
58
|
-
* untouched. Ignores regions in `source` missing from `target`.
|
|
59
|
-
*
|
|
60
|
-
* Returns the rewritten file content.
|
|
61
|
-
*/
|
|
62
|
-
export function spliceMarkerRegions(target, source) {
|
|
63
|
-
const sourceRegions = parseMarkerRegions(source);
|
|
64
|
-
const sourceByName = new Map();
|
|
65
|
-
for (const r of sourceRegions)
|
|
66
|
-
sourceByName.set(r.name, r);
|
|
67
|
-
const targetRegions = parseMarkerRegions(target);
|
|
68
|
-
if (targetRegions.length === 0)
|
|
69
|
-
return target;
|
|
70
|
-
const lines = target.split('\n');
|
|
71
|
-
// Apply replacements from last to first so line indices stay valid.
|
|
72
|
-
const sorted = [...targetRegions].sort((a, b) => b.beginLine - a.beginLine);
|
|
73
|
-
for (const region of sorted) {
|
|
74
|
-
const fresh = sourceByName.get(region.name);
|
|
75
|
-
if (!fresh)
|
|
76
|
-
continue;
|
|
77
|
-
// Replace the body lines (exclusive of the begin/end marker lines).
|
|
78
|
-
const bodyLen = region.endLine - region.beginLine - 1;
|
|
79
|
-
lines.splice(region.beginLine + 1, bodyLen, ...fresh.body);
|
|
80
|
-
}
|
|
81
|
-
return lines.join('\n');
|
|
82
|
-
}
|
|
83
|
-
/** Convenience — true if `content` contains at least one `@clint:begin`. */
|
|
84
|
-
export function hasMarkers(content) {
|
|
85
|
-
return /^[ \t]*\/\/\s*@clint:begin\s+\S+\s*$/m.test(content);
|
|
86
|
-
}
|
|
1
|
+
export { parseMarkerRegions, spliceMarkerRegions, hasMarkers, } from '@powerhousedao/ph-clint-dev/codegen/markers';
|
|
@@ -1,72 +1 @@
|
|
|
1
|
-
|
|
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
|
-
}>;
|
|
10
|
-
export type { RunProcess };
|
|
11
|
-
export interface PhInitOptions {
|
|
12
|
-
/** Absolute path to the project root. */
|
|
13
|
-
targetDir: string;
|
|
14
|
-
/** Absolute path to `{targetDir}/{name}-app` (exists with our placeholder). */
|
|
15
|
-
appDir: string;
|
|
16
|
-
spec: ClintProjectSpec;
|
|
17
|
-
/** Logger; defaults to noop. */
|
|
18
|
-
log?: (msg: string) => void;
|
|
19
|
-
/** Override `ph` binary name (for tests). */
|
|
20
|
-
binName?: string;
|
|
21
|
-
/** Subprocess runner — matches CommandContext.runProcess. */
|
|
22
|
-
runProcess: RunProcess;
|
|
23
|
-
/** Explicit Powerhouse version to pin `ph init` to (tag or semver). */
|
|
24
|
-
phVersion?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface PhInitResult {
|
|
27
|
-
ran: boolean;
|
|
28
|
-
exitCode?: number;
|
|
29
|
-
reason?: string;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Detect the installed `ph` CLI version by parsing `ph --version` output.
|
|
33
|
-
* Returns the version string (e.g. '6.0.0-dev.194') or undefined if
|
|
34
|
-
* detection fails.
|
|
35
|
-
*/
|
|
36
|
-
export declare function getPhVersion(binName: string): string | undefined;
|
|
37
|
-
export declare function runPhInit(options: PhInitOptions): Promise<PhInitResult>;
|
|
38
|
-
/**
|
|
39
|
-
* Patch the app `package.json` name to the scoped form (`@scope/name-app`).
|
|
40
|
-
* Called after `ph init` and after name/scope renames.
|
|
41
|
-
*
|
|
42
|
-
* Returns `true` if the file was actually changed.
|
|
43
|
-
*/
|
|
44
|
-
export declare function patchAppPackageName(appDir: string, spec: {
|
|
45
|
-
name: string;
|
|
46
|
-
scope?: string;
|
|
47
|
-
}, log?: (msg: string) => void): Promise<boolean>;
|
|
48
|
-
/**
|
|
49
|
-
* Run `ph install <packages> --local --pnpm` in the app directory to register
|
|
50
|
-
* external packages in `powerhouse.config.json`.
|
|
51
|
-
*/
|
|
52
|
-
export declare function runPhInstallPackages(options: {
|
|
53
|
-
appDir: string;
|
|
54
|
-
packages: string[];
|
|
55
|
-
log?: (msg: string) => void;
|
|
56
|
-
runProcess: RunProcess;
|
|
57
|
-
}): Promise<boolean>;
|
|
58
|
-
export interface PnpmInstallOptions {
|
|
59
|
-
/** Directories to run `pnpm install` in, in order. */
|
|
60
|
-
dirs: string[];
|
|
61
|
-
log?: (msg: string) => void;
|
|
62
|
-
/** Override `pnpm` binary name (for tests). */
|
|
63
|
-
binName?: string;
|
|
64
|
-
/** Subprocess runner — matches CommandContext.runProcess. */
|
|
65
|
-
runProcess: RunProcess;
|
|
66
|
-
}
|
|
67
|
-
export interface PnpmInstallResult {
|
|
68
|
-
ran: string[];
|
|
69
|
-
skipped: string[];
|
|
70
|
-
reason?: string;
|
|
71
|
-
}
|
|
72
|
-
export declare function runPnpmInstall(options: PnpmInstallOptions): Promise<PnpmInstallResult>;
|
|
1
|
+
export { type RunProcess, type PhInitOptions, type PhInitResult, getPhVersion, runPhInit, patchAppPackageName, type PnpmInstallOptions, type PnpmInstallResult, runPnpmInstall, runPhInstallPackages, } from '@powerhousedao/ph-clint-dev/codegen/scaffold';
|
package/dist/codegen/scaffold.js
CHANGED
|
@@ -1,150 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Post-generation side-effects: invoke `ph init` to fill in the reactor
|
|
3
|
-
* package skeleton, and run `pnpm install` in the relevant sub-projects.
|
|
4
|
-
*
|
|
5
|
-
* Both steps are opt-out (flags on the init command) and both tolerate
|
|
6
|
-
* missing binaries — they never fail the init flow, only emit warnings.
|
|
7
|
-
*/
|
|
8
|
-
import fs from 'node:fs/promises';
|
|
9
|
-
import path from 'node:path';
|
|
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
|
-
}
|
|
30
|
-
export async function runPhInit(options) {
|
|
31
|
-
const log = options.log ?? (() => { });
|
|
32
|
-
const binName = options.binName ?? 'ph';
|
|
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
|
-
}
|
|
43
|
-
if (!(await hasCommandOnPath(binName))) {
|
|
44
|
-
log(`Skipping \`${binName} init\` — \`${binName}\` is not on PATH. ` +
|
|
45
|
-
`Install @powerhousedao/ph-cli and re-run manually inside ${appFolder}/.`);
|
|
46
|
-
return { ran: false, reason: 'ph-not-on-path' };
|
|
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
|
|
57
|
-
// `ph init {name}` creates a fresh directory beside the cwd. Our generator
|
|
58
|
-
// placed a `.gitkeep` + README.md placeholder there; clear it out so
|
|
59
|
-
// ph init has a blank slate to work with.
|
|
60
|
-
await fs.rm(options.appDir, { recursive: true, force: true });
|
|
61
|
-
const args = ['init', appFolder, ...versionArgs, '--pnpm'];
|
|
62
|
-
log(`Running \`${binName} init ${appFolder}\` (version: ${phVersion ?? 'auto'}) in ${options.targetDir} …`);
|
|
63
|
-
const result = await options.runProcess(`${binName} ${args.join(' ')}`, {
|
|
64
|
-
cwd: options.targetDir,
|
|
65
|
-
timeout: 300_000,
|
|
66
|
-
});
|
|
67
|
-
const exitCode = result.success ? 0 : 1;
|
|
68
|
-
if (!result.success) {
|
|
69
|
-
log(`\`${binName} init\` failed. ` +
|
|
70
|
-
'You can re-run it manually after resolving the issue.');
|
|
71
|
-
}
|
|
72
|
-
await patchScopedName(options, appFolder, log);
|
|
73
|
-
return { ran: true, exitCode };
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Patch the app `package.json` name to the scoped form (`@scope/name-app`).
|
|
77
|
-
* Called after `ph init` and after name/scope renames.
|
|
78
|
-
*
|
|
79
|
-
* Returns `true` if the file was actually changed.
|
|
80
|
-
*/
|
|
81
|
-
export async function patchAppPackageName(appDir, spec, log) {
|
|
82
|
-
const appFolder = path.basename(appDir);
|
|
83
|
-
const scopedName = spec.scope
|
|
84
|
-
? `${spec.scope}/${appFolder}`
|
|
85
|
-
: appFolder;
|
|
86
|
-
const appPkgJsonPath = path.join(appDir, 'package.json');
|
|
87
|
-
try {
|
|
88
|
-
const raw = await fs.readFile(appPkgJsonPath, 'utf8');
|
|
89
|
-
const pkg = JSON.parse(raw);
|
|
90
|
-
if (pkg.name !== scopedName) {
|
|
91
|
-
pkg.name = scopedName;
|
|
92
|
-
if (spec.scope) {
|
|
93
|
-
pkg.publishConfig = { access: 'public' };
|
|
94
|
-
}
|
|
95
|
-
await fs.writeFile(appPkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
96
|
-
log?.(`Patched app package name to ${scopedName}`);
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
catch (err) {
|
|
102
|
-
log?.(`Warning: could not patch app package name: ${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
async function patchScopedName(options, _appFolder, log) {
|
|
107
|
-
await patchAppPackageName(options.appDir, options.spec, log);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Run `ph install <packages> --local --pnpm` in the app directory to register
|
|
111
|
-
* external packages in `powerhouse.config.json`.
|
|
112
|
-
*/
|
|
113
|
-
export async function runPhInstallPackages(options) {
|
|
114
|
-
const { appDir, packages, log = () => { }, runProcess } = options;
|
|
115
|
-
const binName = 'ph';
|
|
116
|
-
if (!(await hasCommandOnPath(binName))) {
|
|
117
|
-
log(`Skipping — \`${binName}\` is not on PATH.`);
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const args = ['install', ...packages, '--local', '--pnpm'];
|
|
121
|
-
log(`Running \`${binName} install ${packages.join(' ')} --local\` in ${path.basename(appDir)} …`);
|
|
122
|
-
const result = await runProcess(`${binName} ${args.join(' ')}`, {
|
|
123
|
-
cwd: appDir,
|
|
124
|
-
timeout: 300_000,
|
|
125
|
-
});
|
|
126
|
-
return result.success;
|
|
127
|
-
}
|
|
128
|
-
export async function runPnpmInstall(options) {
|
|
129
|
-
const log = options.log ?? (() => { });
|
|
130
|
-
const binName = options.binName ?? 'pnpm';
|
|
131
|
-
if (!(await hasCommandOnPath(binName))) {
|
|
132
|
-
log(`Skipping \`${binName} install\` — \`${binName}\` is not on PATH. ` +
|
|
133
|
-
'Install pnpm and re-run manually.');
|
|
134
|
-
return { ran: [], skipped: options.dirs, reason: 'pnpm-not-on-path' };
|
|
135
|
-
}
|
|
136
|
-
const ran = [];
|
|
137
|
-
for (const dir of options.dirs) {
|
|
138
|
-
log(`Running \`${binName} install\` in ${dir} …`);
|
|
139
|
-
const result = await options.runProcess(`${binName} install`, {
|
|
140
|
-
cwd: dir,
|
|
141
|
-
timeout: 300_000,
|
|
142
|
-
});
|
|
143
|
-
if (!result.success) {
|
|
144
|
-
log(`\`${binName} install\` failed in ${dir}. ` +
|
|
145
|
-
'Retry manually once the underlying issue is resolved.');
|
|
146
|
-
}
|
|
147
|
-
ran.push(dir);
|
|
148
|
-
}
|
|
149
|
-
return { ran, skipped: [] };
|
|
150
|
-
}
|
|
1
|
+
export { getPhVersion, runPhInit, patchAppPackageName, runPnpmInstall, runPhInstallPackages, } from '@powerhousedao/ph-clint-dev/codegen/scaffold';
|
package/dist/codegen/write.d.ts
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
/**
|
|
3
|
-
* Returns true if the directory doesn't exist or only contains ignorable
|
|
4
|
-
* entries (`.git`, `.DS_Store`, `.ph/ph-clint-cli/project-spec.json`).
|
|
5
|
-
*/
|
|
6
|
-
export declare function isDirEmptyEnough(dir: string): Promise<boolean>;
|
|
1
|
+
export { writeFileEnsuringDir, isDirEmptyEnough, } from '@powerhousedao/ph-clint-dev/codegen/write';
|