@powerhousedao/ph-clint-cli 0.1.0-dev.10 → 0.1.0-dev.100
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 +53 -16
- package/dist/codegen/index.d.ts +8 -36
- package/dist/codegen/index.js +6 -293
- package/dist/commands/clint-project-build.d.ts +77 -1
- package/dist/commands/clint-project-build.js +11 -28
- package/dist/commands/clint-project-init.d.ts +130 -10
- package/dist/commands/clint-project-init.js +72 -47
- package/dist/commands/clint-project-publish.d.ts +78 -1
- package/dist/commands/clint-project-publish.js +6 -4
- package/dist/commands/clint-project-regen.d.ts +77 -1
- package/dist/commands/clint-project-regen.js +26 -2
- package/dist/commands/clint-skills-sync.d.ts +86 -0
- package/dist/commands/clint-skills-sync.js +56 -0
- package/dist/framework.d.ts +322 -60
- package/dist/framework.gen.d.ts +50 -10
- package/dist/framework.js +1 -5
- 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/main.js +0 -0
- package/dist/services/clint-project.js +21 -0
- package/dist/spec/ensure-document.d.ts +17 -0
- package/dist/spec/ensure-document.js +56 -0
- package/dist/spec/file.d.ts +1 -4
- package/dist/spec/file.js +1 -34
- package/dist/spec/from-document.d.ts +4 -1
- package/dist/spec/from-document.js +4 -16
- package/dist/spec/types.d.ts +1 -62
- package/dist/spec/types.js +1 -112
- package/dist/triggers/publish-trigger.d.ts +81 -0
- package/dist/triggers/publish-trigger.js +137 -0
- package/dist/triggers/spec-change.d.ts +60 -18
- package/dist/triggers/spec-change.js +154 -101
- package/package.json +26 -31
- package/dist/codegen/builders/agent-base-md.d.ts +0 -5
- package/dist/codegen/builders/agent-base-md.js +0 -18
- package/dist/codegen/builders/agent-ts.d.ts +0 -9
- package/dist/codegen/builders/agent-ts.js +0 -34
- package/dist/codegen/builders/app-index-ts.d.ts +0 -18
- package/dist/codegen/builders/app-index-ts.js +0 -33
- package/dist/codegen/builders/build-skills-ts.d.ts +0 -4
- package/dist/codegen/builders/build-skills-ts.js +0 -37
- package/dist/codegen/builders/cli-package-json.d.ts +0 -8
- package/dist/codegen/builders/cli-package-json.js +0 -96
- package/dist/codegen/builders/cli-ts.d.ts +0 -7
- package/dist/codegen/builders/cli-ts.js +0 -132
- package/dist/codegen/builders/config-ts.d.ts +0 -8
- package/dist/codegen/builders/config-ts.js +0 -17
- package/dist/codegen/builders/eslint-config-js.d.ts +0 -4
- package/dist/codegen/builders/eslint-config-js.js +0 -13
- package/dist/codegen/builders/framework-gen-ts.d.ts +0 -20
- package/dist/codegen/builders/framework-gen-ts.js +0 -62
- package/dist/codegen/builders/framework-ts.d.ts +0 -21
- package/dist/codegen/builders/framework-ts.js +0 -74
- package/dist/codegen/builders/gitignore.d.ts +0 -4
- package/dist/codegen/builders/gitignore.js +0 -18
- package/dist/codegen/builders/index.d.ts +0 -44
- package/dist/codegen/builders/index.js +0 -61
- package/dist/codegen/builders/jest-config-js.d.ts +0 -4
- package/dist/codegen/builders/jest-config-js.js +0 -19
- package/dist/codegen/builders/main-ts.d.ts +0 -5
- package/dist/codegen/builders/main-ts.js +0 -13
- package/dist/codegen/builders/mastra-index-ts.d.ts +0 -8
- package/dist/codegen/builders/mastra-index-ts.js +0 -30
- package/dist/codegen/builders/publish-config-ts.d.ts +0 -6
- package/dist/codegen/builders/publish-config-ts.js +0 -18
- package/dist/codegen/builders/readme-md.d.ts +0 -5
- package/dist/codegen/builders/readme-md.js +0 -44
- package/dist/codegen/builders/root-package-json.d.ts +0 -10
- package/dist/codegen/builders/root-package-json.js +0 -40
- package/dist/codegen/builders/tsconfig-json.d.ts +0 -4
- package/dist/codegen/builders/tsconfig-json.js +0 -22
- package/dist/codegen/exec.d.ts +0 -20
- package/dist/codegen/exec.js +0 -41
- package/dist/codegen/hashes.d.ts +0 -6
- package/dist/codegen/hashes.js +0 -60
- package/dist/codegen/markers.d.ts +0 -36
- package/dist/codegen/markers.js +0 -86
- package/dist/codegen/migrate/flat-to-split.d.ts +0 -15
- package/dist/codegen/migrate/flat-to-split.js +0 -119
- package/dist/codegen/scaffold.d.ts +0 -34
- package/dist/codegen/scaffold.js +0 -60
- package/dist/codegen/write.d.ts +0 -6
- package/dist/codegen/write.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
- package/prompts/skills-ext/.gitkeep +0 -0
- package/prompts/skills-tpl/.gitkeep +0 -0
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the project README for a freshly-generated ph-clint project.
|
|
3
|
-
*/
|
|
4
|
-
import { getPackageName, } from '../../spec/types.js';
|
|
5
|
-
export function buildReadme(spec) {
|
|
6
|
-
const { mastra, routine, powerhouse } = spec.features;
|
|
7
|
-
const title = getPackageName(spec);
|
|
8
|
-
const lines = [];
|
|
9
|
-
lines.push(`# ${title}`);
|
|
10
|
-
lines.push('');
|
|
11
|
-
lines.push(spec.description || 'A ph-clint-based project.');
|
|
12
|
-
lines.push('');
|
|
13
|
-
lines.push('## Getting started');
|
|
14
|
-
lines.push('');
|
|
15
|
-
lines.push('```sh');
|
|
16
|
-
lines.push('pnpm install');
|
|
17
|
-
lines.push('pnpm dev');
|
|
18
|
-
lines.push('```');
|
|
19
|
-
lines.push('');
|
|
20
|
-
lines.push('## Enabled features');
|
|
21
|
-
lines.push('');
|
|
22
|
-
lines.push(`- **Powerhouse**: ${powerhouse.enabled ? 'on' : 'off'}`);
|
|
23
|
-
if (powerhouse.enabled) {
|
|
24
|
-
lines.push(` - Switchboard: ${powerhouse.switchboard ? 'on' : 'off'}`);
|
|
25
|
-
lines.push(` - Connect: ${powerhouse.connect ? 'on' : 'off'}`);
|
|
26
|
-
}
|
|
27
|
-
lines.push(`- **Mastra agent**: ${mastra.enabled ? 'on' : 'off'}`);
|
|
28
|
-
lines.push(`- **Routine loop**: ${routine.enabled ? 'on' : 'off'}`);
|
|
29
|
-
lines.push('');
|
|
30
|
-
lines.push('## Regenerate');
|
|
31
|
-
lines.push('');
|
|
32
|
-
lines.push('Toggle features or update metadata in `.ph/ph-clint-cli/project-spec.json`,');
|
|
33
|
-
lines.push('then re-run `ph-clint clint-project-regen` to regenerate.');
|
|
34
|
-
lines.push('');
|
|
35
|
-
if (powerhouse.enabled) {
|
|
36
|
-
lines.push('## Split layout');
|
|
37
|
-
lines.push('');
|
|
38
|
-
lines.push(`This project is split into \`${spec.name}-cli/\` (the CLI) and \`${spec.name}-app/\` (the Powerhouse reactor package).`);
|
|
39
|
-
lines.push('');
|
|
40
|
-
lines.push(`Run \`ph init\` inside \`${spec.name}-app/\` to scaffold the reactor package layout (document-models, editors, manifest, etc.).`);
|
|
41
|
-
lines.push('');
|
|
42
|
-
}
|
|
43
|
-
return lines.join('\n');
|
|
44
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the *root* `package.json` for split-layout projects (Powerhouse
|
|
3
|
-
* enabled). A plain passthrough — deliberately no pnpm workspaces because
|
|
4
|
-
* they interfere with `{name}-app`'s own `pnpm build`.
|
|
5
|
-
*
|
|
6
|
-
* Uses `pnpm --prefix <dir>` for all orchestration scripts — no cwd mutation.
|
|
7
|
-
* Build order: app first, then cli (cli depends on app via `file:` dep).
|
|
8
|
-
*/
|
|
9
|
-
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
10
|
-
export declare function buildRootPackageJson(spec: ClintProjectSpec): string;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the *root* `package.json` for split-layout projects (Powerhouse
|
|
3
|
-
* enabled). A plain passthrough — deliberately no pnpm workspaces because
|
|
4
|
-
* they interfere with `{name}-app`'s own `pnpm build`.
|
|
5
|
-
*
|
|
6
|
-
* Uses `pnpm --prefix <dir>` for all orchestration scripts — no cwd mutation.
|
|
7
|
-
* Build order: app first, then cli (cli depends on app via `file:` dep).
|
|
8
|
-
*/
|
|
9
|
-
import { getPackageName, } from '../../spec/types.js';
|
|
10
|
-
export function buildRootPackageJson(spec) {
|
|
11
|
-
const cli = `${spec.name}-cli`;
|
|
12
|
-
const app = `${spec.name}-app`;
|
|
13
|
-
const prefix = (dir, cmd) => `pnpm --prefix ${dir} ${cmd}`;
|
|
14
|
-
const both = (cmd) => `${prefix(app, cmd)} && ${prefix(cli, cmd)}`;
|
|
15
|
-
const connectEnabled = spec.features.powerhouse.enabled && spec.features.powerhouse.connect;
|
|
16
|
-
const buildScript = connectEnabled
|
|
17
|
-
? `${prefix(app, 'build')} && ${prefix(app, 'connect:build')} && ${prefix(cli, 'build')}`
|
|
18
|
-
: both('build');
|
|
19
|
-
const scripts = {
|
|
20
|
-
install: both('install'),
|
|
21
|
-
build: buildScript,
|
|
22
|
-
test: both('test'),
|
|
23
|
-
dev: prefix(cli, 'dev'),
|
|
24
|
-
start: prefix(cli, 'start'),
|
|
25
|
-
lint: prefix(cli, 'lint'),
|
|
26
|
-
'app:dev': prefix(app, 'dev'),
|
|
27
|
-
'cli:dev': prefix(cli, 'dev'),
|
|
28
|
-
'publish:dev': `${prefix(cli, 'exec ph-publish')} dev -c ./publish.config.ts`,
|
|
29
|
-
'publish:staging': `${prefix(cli, 'exec ph-publish')} staging -c ./publish.config.ts`,
|
|
30
|
-
'publish:production': `${prefix(cli, 'exec ph-publish')} production -c ./publish.config.ts`,
|
|
31
|
-
};
|
|
32
|
-
const pkg = {
|
|
33
|
-
name: getPackageName(spec),
|
|
34
|
-
version: spec.version,
|
|
35
|
-
private: true,
|
|
36
|
-
...(spec.description ? { description: spec.description } : {}),
|
|
37
|
-
scripts,
|
|
38
|
-
};
|
|
39
|
-
return JSON.stringify(pkg, null, 2) + '\n';
|
|
40
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds `tsconfig.json` for the CLI.
|
|
3
|
-
*/
|
|
4
|
-
export function buildTsconfigJson() {
|
|
5
|
-
const tsconfig = {
|
|
6
|
-
compilerOptions: {
|
|
7
|
-
target: 'ES2022',
|
|
8
|
-
module: 'Node16',
|
|
9
|
-
moduleResolution: 'Node16',
|
|
10
|
-
outDir: 'dist',
|
|
11
|
-
rootDir: 'src',
|
|
12
|
-
strict: true,
|
|
13
|
-
isolatedModules: true,
|
|
14
|
-
esModuleInterop: true,
|
|
15
|
-
skipLibCheck: true,
|
|
16
|
-
declaration: true,
|
|
17
|
-
types: ['node'],
|
|
18
|
-
},
|
|
19
|
-
include: ['src'],
|
|
20
|
-
};
|
|
21
|
-
return JSON.stringify(tsconfig, null, 2) + '\n';
|
|
22
|
-
}
|
package/dist/codegen/exec.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface RunCommandOptions {
|
|
2
|
-
cwd: string;
|
|
3
|
-
/** Override stdio; default `'inherit'`. Pass `'ignore'` for tests. */
|
|
4
|
-
stdio?: 'inherit' | 'ignore';
|
|
5
|
-
/** Extra env vars (merged with process.env). */
|
|
6
|
-
env?: Record<string, string>;
|
|
7
|
-
}
|
|
8
|
-
export interface RunCommandResult {
|
|
9
|
-
command: string;
|
|
10
|
-
args: string[];
|
|
11
|
-
cwd: string;
|
|
12
|
-
exitCode: number;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Run `command args` in `cwd`, forwarding stdio by default. Resolves to
|
|
16
|
-
* the exit code. Does NOT throw on non-zero — callers decide how to react.
|
|
17
|
-
*/
|
|
18
|
-
export declare function runCommand(command: string, args: string[], options: RunCommandOptions): Promise<RunCommandResult>;
|
|
19
|
-
/** Returns true if `command` is on PATH. */
|
|
20
|
-
export declare function hasCommandOnPath(command: string): Promise<boolean>;
|
package/dist/codegen/exec.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small helpers for invoking external commands during code generation.
|
|
3
|
-
*
|
|
4
|
-
* We deliberately keep this separate from `ph-clint`'s process manager:
|
|
5
|
-
* here we need synchronous-feeling prompt-forwarding (stdio inherited) so
|
|
6
|
-
* the user can respond to interactive prompts from `ph init` or from a
|
|
7
|
-
* package manager.
|
|
8
|
-
*/
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
10
|
-
/**
|
|
11
|
-
* Run `command args` in `cwd`, forwarding stdio by default. Resolves to
|
|
12
|
-
* the exit code. Does NOT throw on non-zero — callers decide how to react.
|
|
13
|
-
*/
|
|
14
|
-
export async function runCommand(command, args, options) {
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
const child = spawn(command, args, {
|
|
17
|
-
cwd: options.cwd,
|
|
18
|
-
stdio: options.stdio ?? 'inherit',
|
|
19
|
-
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
20
|
-
});
|
|
21
|
-
child.once('error', reject);
|
|
22
|
-
child.once('close', (code) => {
|
|
23
|
-
resolve({
|
|
24
|
-
command,
|
|
25
|
-
args,
|
|
26
|
-
cwd: options.cwd,
|
|
27
|
-
exitCode: code ?? 0,
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
/** Returns true if `command` is on PATH. */
|
|
33
|
-
export async function hasCommandOnPath(command) {
|
|
34
|
-
return new Promise((resolve) => {
|
|
35
|
-
const child = spawn(process.platform === 'win32' ? 'where' : 'which', [command], {
|
|
36
|
-
stdio: 'ignore',
|
|
37
|
-
});
|
|
38
|
-
child.once('error', () => resolve(false));
|
|
39
|
-
child.once('close', (code) => resolve(code === 0));
|
|
40
|
-
});
|
|
41
|
-
}
|
package/dist/codegen/hashes.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export type HashRecord = Record<string, string>;
|
|
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>;
|
package/dist/codegen/hashes.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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,36 +0,0 @@
|
|
|
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;
|
package/dist/codegen/markers.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
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,15 +0,0 @@
|
|
|
1
|
-
import { type ClintProjectSpec } from '../../spec/types.js';
|
|
2
|
-
export interface FlatToSplitOptions {
|
|
3
|
-
targetDir: string;
|
|
4
|
-
spec: ClintProjectSpec;
|
|
5
|
-
/** Bypass the git-dirty guard. */
|
|
6
|
-
force?: boolean;
|
|
7
|
-
/** Optional warn sink for non-fatal messages. */
|
|
8
|
-
onWarn?: (msg: string) => void;
|
|
9
|
-
}
|
|
10
|
-
export interface FlatToSplitResult {
|
|
11
|
-
movedEntries: string[];
|
|
12
|
-
cliDir: string;
|
|
13
|
-
appDir: string;
|
|
14
|
-
}
|
|
15
|
-
export declare function migrateFlatToSplit(options: FlatToSplitOptions): Promise<FlatToSplitResult>;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One-shot migration that runs when a project's spec flips
|
|
3
|
-
* `features.powerhouse.enabled` from `false` to `true`. It reshapes the
|
|
4
|
-
* on-disk layout from flat (everything at the project root) to split
|
|
5
|
-
* (`{name}-cli/` + `{name}-app/` siblings).
|
|
6
|
-
*
|
|
7
|
-
* The follow-up update-mode regeneration then fills in the correct content
|
|
8
|
-
* for each file in its new location — so this migration only moves bytes
|
|
9
|
-
* around. Content updates belong to the generator.
|
|
10
|
-
*
|
|
11
|
-
* `node_modules` is deliberately NOT moved — pnpm `file:` protocol uses
|
|
12
|
-
* symlinks that do not survive a cross-directory rename cleanly. Callers
|
|
13
|
-
* should re-run `pnpm install` after the migration.
|
|
14
|
-
*/
|
|
15
|
-
import fs from 'node:fs/promises';
|
|
16
|
-
import path from 'node:path';
|
|
17
|
-
import { spawn } from 'node:child_process';
|
|
18
|
-
import { getAppFolderName, getCliFolderName, } from '../../spec/types.js';
|
|
19
|
-
import { readHashes, writeHashes } from '../hashes.js';
|
|
20
|
-
/** Entries we never move during a flat → split migration. */
|
|
21
|
-
function isUnmovable(entry, cliFolder, appFolder) {
|
|
22
|
-
if (entry === '.git')
|
|
23
|
-
return true;
|
|
24
|
-
if (entry === '.ph')
|
|
25
|
-
return true;
|
|
26
|
-
if (entry === 'README.md')
|
|
27
|
-
return true;
|
|
28
|
-
if (entry === 'node_modules')
|
|
29
|
-
return true;
|
|
30
|
-
if (entry === cliFolder)
|
|
31
|
-
return true;
|
|
32
|
-
if (entry === appFolder)
|
|
33
|
-
return true;
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Guard: if the target is a git repo with a dirty working tree, refuse to
|
|
38
|
-
* migrate unless `force`. We never want to discard uncommitted edits.
|
|
39
|
-
* Resolves to `null` when the dir is not a git repo.
|
|
40
|
-
*/
|
|
41
|
-
async function gitDirty(dir) {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const child = spawn('git', ['status', '--porcelain'], {
|
|
44
|
-
cwd: dir,
|
|
45
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
-
});
|
|
47
|
-
let out = '';
|
|
48
|
-
child.stdout?.on('data', (d) => {
|
|
49
|
-
out += d.toString();
|
|
50
|
-
});
|
|
51
|
-
child.once('error', () => resolve(null));
|
|
52
|
-
child.once('close', (code) => {
|
|
53
|
-
if (code !== 0)
|
|
54
|
-
resolve(null);
|
|
55
|
-
else
|
|
56
|
-
resolve(out.trim() || null);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
export async function migrateFlatToSplit(options) {
|
|
61
|
-
const { targetDir, spec } = options;
|
|
62
|
-
const cliFolder = getCliFolderName(spec);
|
|
63
|
-
const appFolder = getAppFolderName(spec);
|
|
64
|
-
const cliDir = path.join(targetDir, cliFolder);
|
|
65
|
-
const appDir = path.join(targetDir, appFolder);
|
|
66
|
-
if (!options.force) {
|
|
67
|
-
const dirty = await gitDirty(targetDir);
|
|
68
|
-
if (dirty) {
|
|
69
|
-
throw new Error(`refusing to migrate flat → split: ${targetDir} has uncommitted changes.\n` +
|
|
70
|
-
'Commit or stash first, or re-run with --force.');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// Create the CLI destination. If it already exists and is non-empty we
|
|
74
|
-
// treat that as a malformed state and bail — a previous migration probably
|
|
75
|
-
// got interrupted.
|
|
76
|
-
await fs.mkdir(cliDir, { recursive: true });
|
|
77
|
-
const cliExisting = await fs.readdir(cliDir);
|
|
78
|
-
if (cliExisting.length > 0) {
|
|
79
|
-
throw new Error(`cannot migrate: ${cliDir} already exists and is non-empty. ` +
|
|
80
|
-
'Resolve manually before re-running.');
|
|
81
|
-
}
|
|
82
|
-
const entries = await fs.readdir(targetDir);
|
|
83
|
-
const moved = [];
|
|
84
|
-
for (const entry of entries) {
|
|
85
|
-
if (isUnmovable(entry, cliFolder, appFolder)) {
|
|
86
|
-
if (entry === 'node_modules' && options.onWarn) {
|
|
87
|
-
options.onWarn('skipped node_modules during flat → split migration; re-run `pnpm install`.');
|
|
88
|
-
}
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const src = path.join(targetDir, entry);
|
|
92
|
-
const dst = path.join(cliDir, entry);
|
|
93
|
-
await fs.rename(src, dst);
|
|
94
|
-
moved.push(entry);
|
|
95
|
-
}
|
|
96
|
-
// Rekey stored hashes so they still line up with the moved paths.
|
|
97
|
-
await rekeyHashes(targetDir, moved, cliFolder);
|
|
98
|
-
// Create the app folder with a placeholder so tooling (git, `ph init`) can
|
|
99
|
-
// find it. The generator and/or `ph init` fills in the real contents.
|
|
100
|
-
await fs.mkdir(appDir, { recursive: true });
|
|
101
|
-
return { movedEntries: moved, cliDir, appDir };
|
|
102
|
-
}
|
|
103
|
-
async function rekeyHashes(targetDir, movedEntries, cliFolder) {
|
|
104
|
-
const before = await readHashes(targetDir);
|
|
105
|
-
if (Object.keys(before).length === 0)
|
|
106
|
-
return;
|
|
107
|
-
const movedSet = new Set(movedEntries);
|
|
108
|
-
const after = {};
|
|
109
|
-
for (const [key, hash] of Object.entries(before)) {
|
|
110
|
-
const top = key.split(/[\\/]/)[0];
|
|
111
|
-
if (movedSet.has(top)) {
|
|
112
|
-
after[path.posix.join(cliFolder, key)] = hash;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
after[key] = hash;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
await writeHashes(targetDir, after);
|
|
119
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ClintProjectSpec } from '../spec/types.js';
|
|
2
|
-
export interface PhInitOptions {
|
|
3
|
-
/** Absolute path to the project root. */
|
|
4
|
-
targetDir: string;
|
|
5
|
-
/** Absolute path to `{targetDir}/{name}-app` (exists with our placeholder). */
|
|
6
|
-
appDir: string;
|
|
7
|
-
spec: ClintProjectSpec;
|
|
8
|
-
/** Logger; defaults to noop. */
|
|
9
|
-
log?: (msg: string) => void;
|
|
10
|
-
/** Override `ph` binary name (for tests). */
|
|
11
|
-
binName?: string;
|
|
12
|
-
/** Stdio passthrough (tests pass 'ignore'). */
|
|
13
|
-
stdio?: 'inherit' | 'ignore';
|
|
14
|
-
}
|
|
15
|
-
export interface PhInitResult {
|
|
16
|
-
ran: boolean;
|
|
17
|
-
exitCode?: number;
|
|
18
|
-
reason?: string;
|
|
19
|
-
}
|
|
20
|
-
export declare function runPhInit(options: PhInitOptions): Promise<PhInitResult>;
|
|
21
|
-
export interface PnpmInstallOptions {
|
|
22
|
-
/** Directories to run `pnpm install` in, in order. */
|
|
23
|
-
dirs: string[];
|
|
24
|
-
log?: (msg: string) => void;
|
|
25
|
-
/** Override `pnpm` binary name (for tests). */
|
|
26
|
-
binName?: string;
|
|
27
|
-
stdio?: 'inherit' | 'ignore';
|
|
28
|
-
}
|
|
29
|
-
export interface PnpmInstallResult {
|
|
30
|
-
ran: string[];
|
|
31
|
-
skipped: string[];
|
|
32
|
-
reason?: string;
|
|
33
|
-
}
|
|
34
|
-
export declare function runPnpmInstall(options: PnpmInstallOptions): Promise<PnpmInstallResult>;
|
package/dist/codegen/scaffold.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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 { hasCommandOnPath, runCommand } from './exec.js';
|
|
11
|
-
export async function runPhInit(options) {
|
|
12
|
-
const log = options.log ?? (() => { });
|
|
13
|
-
const binName = options.binName ?? 'ph';
|
|
14
|
-
const appFolder = path.basename(options.appDir);
|
|
15
|
-
if (!(await hasCommandOnPath(binName))) {
|
|
16
|
-
log(`Skipping \`${binName} init\` — \`${binName}\` is not on PATH. ` +
|
|
17
|
-
`Install @powerhousedao/ph-cli and re-run manually inside ${appFolder}/.`);
|
|
18
|
-
return { ran: false, reason: 'ph-not-on-path' };
|
|
19
|
-
}
|
|
20
|
-
// `ph init {name}` creates a fresh directory beside the cwd. Our generator
|
|
21
|
-
// placed a `.gitkeep` + README.md placeholder there; clear it out so
|
|
22
|
-
// ph init has a blank slate to work with.
|
|
23
|
-
await fs.rm(options.appDir, { recursive: true, force: true });
|
|
24
|
-
log(`Running \`${binName} init ${appFolder}\` in ${options.targetDir} …`);
|
|
25
|
-
const result = await runCommand(binName, [
|
|
26
|
-
'init',
|
|
27
|
-
appFolder,
|
|
28
|
-
'--pnpm',
|
|
29
|
-
'--dev',
|
|
30
|
-
], { cwd: options.targetDir, stdio: options.stdio ?? 'inherit' });
|
|
31
|
-
if (result.exitCode !== 0) {
|
|
32
|
-
log(`\`${binName} init\` exited with code ${result.exitCode}. ` +
|
|
33
|
-
'You can re-run it manually after resolving the issue.');
|
|
34
|
-
}
|
|
35
|
-
return { ran: true, exitCode: result.exitCode };
|
|
36
|
-
}
|
|
37
|
-
export async function runPnpmInstall(options) {
|
|
38
|
-
const log = options.log ?? (() => { });
|
|
39
|
-
const binName = options.binName ?? 'pnpm';
|
|
40
|
-
if (!(await hasCommandOnPath(binName))) {
|
|
41
|
-
log(`Skipping \`${binName} install\` — \`${binName}\` is not on PATH. ` +
|
|
42
|
-
'Install pnpm and re-run manually.');
|
|
43
|
-
return { ran: [], skipped: options.dirs, reason: 'pnpm-not-on-path' };
|
|
44
|
-
}
|
|
45
|
-
const ran = [];
|
|
46
|
-
for (const dir of options.dirs) {
|
|
47
|
-
log(`Running \`${binName} install\` in ${dir} …`);
|
|
48
|
-
const result = await runCommand(binName, ['install'], {
|
|
49
|
-
cwd: dir,
|
|
50
|
-
stdio: options.stdio ?? 'inherit',
|
|
51
|
-
});
|
|
52
|
-
if (result.exitCode !== 0) {
|
|
53
|
-
log(`\`${binName} install\` exited with code ${result.exitCode} in ${dir}. ` +
|
|
54
|
-
'Retry manually once the underlying issue is resolved.');
|
|
55
|
-
// Don't abort the whole chain — subsequent dirs may still work.
|
|
56
|
-
}
|
|
57
|
-
ran.push(dir);
|
|
58
|
-
}
|
|
59
|
-
return { ran, skipped: [] };
|
|
60
|
-
}
|
package/dist/codegen/write.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function writeFileEnsuringDir(filePath: string, content: string): Promise<void>;
|
|
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>;
|
package/dist/codegen/write.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Low-level file-writing helpers for the code generator.
|
|
3
|
-
*/
|
|
4
|
-
import fs from 'node:fs/promises';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
export async function writeFileEnsuringDir(filePath, content) {
|
|
7
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
8
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
9
|
-
}
|
|
10
|
-
function isEnoent(err) {
|
|
11
|
-
return (!!err &&
|
|
12
|
-
typeof err === 'object' &&
|
|
13
|
-
'code' in err &&
|
|
14
|
-
err.code === 'ENOENT');
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Returns true if the directory doesn't exist or only contains ignorable
|
|
18
|
-
* entries (`.git`, `.DS_Store`, `.ph/ph-clint-cli/project-spec.json`).
|
|
19
|
-
*/
|
|
20
|
-
export async function isDirEmptyEnough(dir) {
|
|
21
|
-
let entries;
|
|
22
|
-
try {
|
|
23
|
-
entries = await fs.readdir(dir);
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
if (isEnoent(err))
|
|
27
|
-
return true;
|
|
28
|
-
throw err;
|
|
29
|
-
}
|
|
30
|
-
const ignorable = new Set(['.git', '.DS_Store', '.ph']);
|
|
31
|
-
return entries.every((entry) => ignorable.has(entry));
|
|
32
|
-
}
|