create-zudo-circuit-doc 0.1.0
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/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/bin/create-zudo-circuit-doc.js +6 -0
- package/dist/args.d.ts +21 -0
- package/dist/args.js +71 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.js +101 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/git.d.ts +11 -0
- package/dist/git.js +69 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.js +21 -0
- package/dist/install.d.ts +3 -0
- package/dist/install.js +17 -0
- package/dist/next-steps.d.ts +10 -0
- package/dist/next-steps.js +24 -0
- package/dist/plan.d.ts +17 -0
- package/dist/plan.js +59 -0
- package/dist/prompt.d.ts +3 -0
- package/dist/prompt.js +12 -0
- package/dist/scaffold.d.ts +22 -0
- package/dist/scaffold.js +187 -0
- package/dist/shell-quote.d.ts +2 -0
- package/dist/shell-quote.js +7 -0
- package/dist/validate.d.ts +18 -0
- package/dist/validate.js +85 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +13 -0
- package/package.json +49 -0
- package/templates/default/.claude/skills/circuit-spec-integration/SKILL.md +22 -0
- package/templates/default/.claude/skills/circuit-spec-integration/references/rules.json +4 -0
- package/templates/default/.claude/skills/component-spec-audit/SKILL.md +36 -0
- package/templates/default/.claude/skills/component-spec-audit/references/contract.md +21 -0
- package/templates/default/.claude/skills/component-spec-audit/references/direct-routing.json +5 -0
- package/templates/default/.claude/skills/component-spec-audit/references/external-vendor-qualifiers.json +4 -0
- package/templates/default/.claude/skills/component-spec-audit/references/inventory.json +11 -0
- package/templates/default/.claude/skills/component-spec-audit/references/new-component-workflow.md +113 -0
- package/templates/default/AGENTS.md +7 -0
- package/templates/default/CLAUDE.md +7 -0
- package/templates/default/README.md +49 -0
- package/templates/default/ZUDO_DEPS_PINS.md +48 -0
- package/templates/default/_gitignore +25 -0
- package/templates/default/circuit/WORKFLOW.md +361 -0
- package/templates/default/circuit/agent-task-examples.md +178 -0
- package/templates/default/circuit/checks/README.md +37 -0
- package/templates/default/circuit/generated/preflight.json +625 -0
- package/templates/default/circuit/publication/assets.json +9 -0
- package/templates/default/circuit/publication/selection.json +13 -0
- package/templates/default/circuit/templates/README.md +33 -0
- package/templates/default/circuit/templates/cad-asset-receipt.json +58 -0
- package/templates/default/circuit/templates/cad-asset-receipt.md +33 -0
- package/templates/default/circuit/templates/project-docs/architecture/interfaces.mdx +52 -0
- package/templates/default/circuit/templates/project-docs/architecture/overview.mdx +55 -0
- package/templates/default/circuit/templates/project-docs/decisions/decision.mdx +66 -0
- package/templates/default/circuit/templates/project-docs/decisions/sourcing.mdx +57 -0
- package/templates/default/circuit/templates/project-docs/project/change-impact.mdx +61 -0
- package/templates/default/circuit/templates/project-docs/project/index.mdx +62 -0
- package/templates/default/circuit/templates/project-docs/project/next-actions.mdx +58 -0
- package/templates/default/circuit/templates/project-docs/project/task-request.mdx +56 -0
- package/templates/default/circuit/templates/project-docs/research/component-candidate.mdx +60 -0
- package/templates/default/circuit/templates/project-docs/verification/bring-up.mdx +55 -0
- package/templates/default/circuit.config.ts +36 -0
- package/templates/default/doc/package.json +37 -0
- package/templates/default/doc/pages/docs/[[...slug]].tsx +68 -0
- package/templates/default/doc/pages/index.tsx +6 -0
- package/templates/default/doc/pages/lib/_circuit-doc-islands.ts +4 -0
- package/templates/default/doc/public/favicon-16x16.png +0 -0
- package/templates/default/doc/public/favicon-32x32.png +0 -0
- package/templates/default/doc/public/favicon.ico +0 -0
- package/templates/default/doc/public/favicon.svg +4 -0
- package/templates/default/doc/scripts/check-links.js +969 -0
- package/templates/default/doc/src/chrome-bindings.tsx +11 -0
- package/templates/default/doc/src/content/docs/architecture/index.mdx +11 -0
- package/templates/default/doc/src/content/docs/architecture/overview.mdx +55 -0
- package/templates/default/doc/src/content/docs/components/catalog/index.mdx +12 -0
- package/templates/default/doc/src/content/docs/components/index.mdx +49 -0
- package/templates/default/doc/src/content/docs/components/integration/index.mdx +34 -0
- package/templates/default/doc/src/content/docs/components/records/index.mdx +14 -0
- package/templates/default/doc/src/content/docs/decisions/index.mdx +9 -0
- package/templates/default/doc/src/content/docs/project/how-we-work.mdx +67 -0
- package/templates/default/doc/src/content/docs/project/index.mdx +60 -0
- package/templates/default/doc/src/content/docs/project/next-actions.mdx +58 -0
- package/templates/default/doc/src/content/docs/research/index.mdx +9 -0
- package/templates/default/doc/src/content/docs/verification/index.mdx +9 -0
- package/templates/default/doc/src/styles/global.css +31 -0
- package/templates/default/doc/tsconfig.json +13 -0
- package/templates/default/doc/zfb.config.ts +78 -0
- package/templates/default/package.json +23 -0
- package/templates/default/pnpm-workspace.yaml +9 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `create-zudo-circuit-doc` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-27
|
|
8
|
+
|
|
9
|
+
Initial release of `create-zudo-circuit-doc`, an initializer that creates a ready-to-use
|
|
10
|
+
circuit-development project with a zudo-doc site, the runtime package, canonical project workflows,
|
|
11
|
+
and evidence authoring templates.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- `pnpm create zudo-circuit-doc <destination>` scaffolds a project from a template kept in sync with
|
|
16
|
+
the `examples/empty` host, checked for parity with the upstream zudo-doc scaffold.
|
|
17
|
+
- Generated projects depend on `@takazudo/zudo-circuit-doc` through a `^0.1.0` caret range and ship a
|
|
18
|
+
`circuit.config.ts`, empty evidence data, the canonical agent workflow, skills, and authoring
|
|
19
|
+
templates.
|
|
20
|
+
- Options: `--name`, `--title`, `--library`, `--agent claude|codex|both|none`, `--yes`,
|
|
21
|
+
`--install` / `--no-install`, `--git` / `--no-git`, and `--runtime-spec` to override the runtime
|
|
22
|
+
dependency with a semver range, dist-tag, or absolute `file:` spec.
|
|
23
|
+
- A non-empty destination is rejected and left byte-for-byte unchanged; an existing empty directory
|
|
24
|
+
is accepted.
|
|
25
|
+
- Generated projects include a README titled after the site, a `contract.md`, and plain-text next
|
|
26
|
+
steps.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takeshi Takatsudo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# create-zudo-circuit-doc
|
|
2
|
+
|
|
3
|
+
Scaffolds a new [zudo-circuit-doc](https://github.com/Takazudo/zudo-circuit-doc) project: a zudo-doc-based circuit-development initializer that opens as useful documentation before any component, board, KiCad file or firmware exists.
|
|
4
|
+
|
|
5
|
+
**Status:** under construction. See the implementation epic: https://github.com/Takazudo/zudo-circuit-doc/issues/1
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm create zudo-circuit-doc@latest [destination]
|
|
11
|
+
# or, in this monorepo:
|
|
12
|
+
pnpm create zudo-circuit-doc [destination]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If `destination` is omitted, the CLI prompts for one on an interactive terminal; on a non-interactive session, or with `--yes`, a missing destination is an error.
|
|
16
|
+
|
|
17
|
+
## Options
|
|
18
|
+
|
|
19
|
+
| Option | Default | Description |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `[destination]` | prompted | Directory to create. Must not exist, or must be empty. Spaces are allowed. |
|
|
22
|
+
| `--name <npm-name>` | `destination`'s basename | The generated `package.json` name (npm grammar, 1-214 chars, no `node_modules`). |
|
|
23
|
+
| `--title <site title>` | title-cased `--name` | The site title. 1-80 printable characters, excluding `" ' \` \\ $ < > { }` and control characters — it is substituted into TS string literals and MDX frontmatter. |
|
|
24
|
+
| `--library <kicad-lib-name>` | `--name` | The KiCad library name (`^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$`). |
|
|
25
|
+
| `--agent claude\|codex\|both\|none` | `both` | Which thin agent-entry file(s) to keep: `claude` keeps `CLAUDE.md`, `codex` keeps `AGENTS.md`, `both` keeps both, `none` keeps neither. `.claude/skills/**` is always kept — it is the evidence storage (ADR-006). |
|
|
26
|
+
| `--yes`, `-y` | off | Never prompt; a missing destination is an error. |
|
|
27
|
+
| `--install` / `--no-install` | `--install` | Run `pnpm install` in the destination after scaffolding. |
|
|
28
|
+
| `--git` / `--no-git` | `--git` | Run `git init -b main` and commit the scaffold. Skipped with a note if the destination is already inside a git work tree. |
|
|
29
|
+
| `--runtime-spec <spec>` | the template's own spec | Overrides the `@takazudo/zudo-circuit-doc` dependency in both `package.json` and `doc/package.json`. Accepts a semver range/dist-tag, or a `file:` spec with an **absolute** path — a relative `file:` path is rejected, since `doc/package.json` sits one directory deeper than `package.json` and one relative string cannot be correct in both. |
|
|
30
|
+
| `--help`, `-h` | | Print usage and exit. |
|
|
31
|
+
| `--version`, `-v` | | Print the installed version and exit. |
|
|
32
|
+
|
|
33
|
+
There is no force flag: if the destination exists and is not empty, the command reports the collision, exits with status 1, and leaves the destination byte-for-byte unchanged.
|
|
34
|
+
|
|
35
|
+
## What is generated
|
|
36
|
+
|
|
37
|
+
The scaffold composes the project atomically (via a staging directory renamed into place, so the destination is never partially written) from the package's own `templates/default`. Its full contents are the epic's **Generated project contract** (https://github.com/Takazudo/zudo-circuit-doc/issues/1), summarized here:
|
|
38
|
+
|
|
39
|
+
- Root: `package.json`, `pnpm-workspace.yaml`, `circuit.config.ts`, `README.md`, `CLAUDE.md`/`AGENTS.md` (per `--agent`), `.gitignore`, `ZUDO_DEPS_PINS.md`.
|
|
40
|
+
- `circuit/`: the canonical agent workflow (`WORKFLOW.md`, A-G), authoring templates, checks, and empty publication/preflight records.
|
|
41
|
+
- `.claude/skills/`: `component-spec-audit` and `circuit-spec-integration`, the evidence-storage skill bundles.
|
|
42
|
+
- `doc/`: a zudo-doc app — route stub, chrome-bindings shim, and `zfb.config.ts`.
|
|
43
|
+
|
|
44
|
+
After scaffolding, the printed **Next steps** give the exact commands to run (`cd`, `pnpm install` if it did not already run, `pnpm circuit:doctor`, `pnpm dev`), plus a pointer to `circuit/WORKFLOW.md` for the first task.
|
|
45
|
+
|
|
46
|
+
## Create-only semantics
|
|
47
|
+
|
|
48
|
+
This tool only creates new projects. There is no `update` or "adopt an existing directory" mode — running it again against the same non-empty destination is a collision, not a merge or an upgrade.
|
|
49
|
+
|
|
50
|
+
## Tool requirements
|
|
51
|
+
|
|
52
|
+
- Node >=22.18.0
|
|
53
|
+
- pnpm (via corepack; the generated project pins `packageManager`)
|
|
54
|
+
- git, if `--git` is not disabled
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT. See [LICENSE](./LICENSE).
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const AGENT_CHOICES: readonly ["claude", "codex", "both", "none"];
|
|
2
|
+
export type AgentChoice = (typeof AGENT_CHOICES)[number];
|
|
3
|
+
export interface ParsedOptions {
|
|
4
|
+
help: boolean;
|
|
5
|
+
version: boolean;
|
|
6
|
+
yes: boolean;
|
|
7
|
+
destination: string | undefined;
|
|
8
|
+
name: string | undefined;
|
|
9
|
+
title: string | undefined;
|
|
10
|
+
library: string | undefined;
|
|
11
|
+
agent: AgentChoice;
|
|
12
|
+
install: boolean;
|
|
13
|
+
git: boolean;
|
|
14
|
+
runtimeSpec: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses argv with node:util parseArgs (strict). `--install`/`--no-install`
|
|
18
|
+
* and `--git`/`--no-git` are not natively negatable by parseArgs, so both
|
|
19
|
+
* spellings are declared and the last one seen on the command line wins.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseCliArgs(argv: string[]): ParsedOptions;
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { CliUsageError } from "./errors.js";
|
|
3
|
+
export const AGENT_CHOICES = ["claude", "codex", "both", "none"];
|
|
4
|
+
/**
|
|
5
|
+
* Parses argv with node:util parseArgs (strict). `--install`/`--no-install`
|
|
6
|
+
* and `--git`/`--no-git` are not natively negatable by parseArgs, so both
|
|
7
|
+
* spellings are declared and the last one seen on the command line wins.
|
|
8
|
+
*/
|
|
9
|
+
export function parseCliArgs(argv) {
|
|
10
|
+
let result;
|
|
11
|
+
try {
|
|
12
|
+
result = parseArgs({
|
|
13
|
+
args: argv,
|
|
14
|
+
strict: true,
|
|
15
|
+
allowPositionals: true,
|
|
16
|
+
tokens: true,
|
|
17
|
+
options: {
|
|
18
|
+
help: { type: "boolean", short: "h" },
|
|
19
|
+
version: { type: "boolean", short: "v" },
|
|
20
|
+
yes: { type: "boolean", short: "y" },
|
|
21
|
+
name: { type: "string" },
|
|
22
|
+
title: { type: "string" },
|
|
23
|
+
library: { type: "string" },
|
|
24
|
+
agent: { type: "string" },
|
|
25
|
+
install: { type: "boolean" },
|
|
26
|
+
"no-install": { type: "boolean" },
|
|
27
|
+
git: { type: "boolean" },
|
|
28
|
+
"no-git": { type: "boolean" },
|
|
29
|
+
"runtime-spec": { type: "string" },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new CliUsageError(error.message);
|
|
35
|
+
}
|
|
36
|
+
const { values, positionals, tokens } = result;
|
|
37
|
+
if (positionals.length > 1) {
|
|
38
|
+
throw new CliUsageError(`Too many arguments: expected at most one destination, got ${positionals.length} (${positionals.join(", ")}).`);
|
|
39
|
+
}
|
|
40
|
+
let install = true;
|
|
41
|
+
let git = true;
|
|
42
|
+
for (const token of tokens) {
|
|
43
|
+
if (token.kind !== "option")
|
|
44
|
+
continue;
|
|
45
|
+
if (token.name === "install")
|
|
46
|
+
install = true;
|
|
47
|
+
else if (token.name === "no-install")
|
|
48
|
+
install = false;
|
|
49
|
+
else if (token.name === "git")
|
|
50
|
+
git = true;
|
|
51
|
+
else if (token.name === "no-git")
|
|
52
|
+
git = false;
|
|
53
|
+
}
|
|
54
|
+
const agentRaw = values.agent ?? "both";
|
|
55
|
+
if (!AGENT_CHOICES.includes(agentRaw)) {
|
|
56
|
+
throw new CliUsageError(`Invalid --agent "${agentRaw}": must be one of ${AGENT_CHOICES.join(", ")}.`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
help: values.help ?? false,
|
|
60
|
+
version: values.version ?? false,
|
|
61
|
+
yes: values.yes ?? false,
|
|
62
|
+
destination: positionals[0],
|
|
63
|
+
name: values.name,
|
|
64
|
+
title: values.title,
|
|
65
|
+
library: values.library,
|
|
66
|
+
agent: agentRaw,
|
|
67
|
+
install,
|
|
68
|
+
git,
|
|
69
|
+
runtimeSpec: values["runtime-spec"],
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
import { type GitRunner } from "./git.ts";
|
|
3
|
+
import { type InstallRunner } from "./install.ts";
|
|
4
|
+
interface WritableLike {
|
|
5
|
+
write(chunk: string): unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Every external effect the CLI performs is injectable here, so tests can
|
|
9
|
+
* run the real orchestration logic against a fixture template, a fake
|
|
10
|
+
* installer/git runner, and captured streams instead of the real world.
|
|
11
|
+
*/
|
|
12
|
+
export interface CliOverrides {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
stdout?: WritableLike;
|
|
15
|
+
stderr?: WritableLike;
|
|
16
|
+
stdin?: Readable;
|
|
17
|
+
isTTY?: boolean;
|
|
18
|
+
templateDir?: URL | string;
|
|
19
|
+
runInstall?: InstallRunner;
|
|
20
|
+
runGit?: GitRunner;
|
|
21
|
+
randomSuffix?: () => string;
|
|
22
|
+
/** Test-only seam: called before each template file is copied. */
|
|
23
|
+
beforeCopyFile?: (relPath: string) => void;
|
|
24
|
+
}
|
|
25
|
+
/** Entry point. `argv` is the CLI's own arguments only (no `node`/script path). */
|
|
26
|
+
export declare function main(argv: string[], overrides?: CliOverrides): Promise<number>;
|
|
27
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseCliArgs } from "./args.js";
|
|
4
|
+
import { CliError, CliUsageError } from "./errors.js";
|
|
5
|
+
import { defaultRunGit } from "./git.js";
|
|
6
|
+
import { HELP_TEXT, USAGE_LINE } from "./help.js";
|
|
7
|
+
import { defaultRunInstall } from "./install.js";
|
|
8
|
+
import { formatNextSteps } from "./next-steps.js";
|
|
9
|
+
import { formatPlan, resolvePlan } from "./plan.js";
|
|
10
|
+
import { promptForDestination } from "./prompt.js";
|
|
11
|
+
import { checkDestinationCollision, composeProject } from "./scaffold.js";
|
|
12
|
+
import { shellQuote } from "./shell-quote.js";
|
|
13
|
+
import { readPackageVersion } from "./version.js";
|
|
14
|
+
function println(stream, text) {
|
|
15
|
+
stream.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
16
|
+
}
|
|
17
|
+
/** Entry point. `argv` is the CLI's own arguments only (no `node`/script path). */
|
|
18
|
+
export async function main(argv, overrides = {}) {
|
|
19
|
+
const cwd = overrides.cwd ?? process.cwd();
|
|
20
|
+
const stdout = overrides.stdout ?? process.stdout;
|
|
21
|
+
const stderr = overrides.stderr ?? process.stderr;
|
|
22
|
+
const stdin = overrides.stdin ?? process.stdin;
|
|
23
|
+
const isTTY = overrides.isTTY ??
|
|
24
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
25
|
+
const templateDir = overrides.templateDir ?? new URL("../templates/default/", import.meta.url);
|
|
26
|
+
const runInstall = overrides.runInstall ?? defaultRunInstall;
|
|
27
|
+
const runGit = overrides.runGit ?? defaultRunGit;
|
|
28
|
+
const randomSuffix = overrides.randomSuffix ?? (() => crypto.randomBytes(6).toString("hex"));
|
|
29
|
+
try {
|
|
30
|
+
const parsed = parseCliArgs(argv);
|
|
31
|
+
if (parsed.help) {
|
|
32
|
+
println(stdout, HELP_TEXT);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
if (parsed.version) {
|
|
36
|
+
println(stdout, readPackageVersion(import.meta.url));
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
let destinationRaw = parsed.destination;
|
|
40
|
+
if (destinationRaw === undefined) {
|
|
41
|
+
if (parsed.yes || !isTTY) {
|
|
42
|
+
throw new CliUsageError("A destination directory is required (pass one, or run on an interactive terminal without --yes to be prompted).");
|
|
43
|
+
}
|
|
44
|
+
destinationRaw = await promptForDestination(stdin, stdout);
|
|
45
|
+
if (destinationRaw.length === 0) {
|
|
46
|
+
throw new CliUsageError("A destination directory is required.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const plan = resolvePlan(parsed, destinationRaw, cwd);
|
|
50
|
+
const displayDestination = path.relative(cwd, plan.destinationPath) || ".";
|
|
51
|
+
checkDestinationCollision(plan.destinationPath);
|
|
52
|
+
println(stdout, formatPlan(plan));
|
|
53
|
+
composeProject({
|
|
54
|
+
plan,
|
|
55
|
+
templateDir,
|
|
56
|
+
randomSuffix,
|
|
57
|
+
beforeCopyFile: overrides.beforeCopyFile,
|
|
58
|
+
});
|
|
59
|
+
let installRan = false;
|
|
60
|
+
if (plan.install) {
|
|
61
|
+
try {
|
|
62
|
+
await runInstall(plan.destinationPath);
|
|
63
|
+
installRan = true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
println(stderr, `Error: ${error.message} The generated files are left in place — they form a valid project.`);
|
|
67
|
+
println(stdout, ["Recovery:", "", ` cd ${shellQuote(displayDestination)}`, " pnpm install"].join("\n"));
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let gitFailed = false;
|
|
72
|
+
if (plan.git) {
|
|
73
|
+
try {
|
|
74
|
+
const outcome = await runGit(plan.destinationPath);
|
|
75
|
+
if (outcome.note)
|
|
76
|
+
println(stdout, outcome.note);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
gitFailed = true;
|
|
80
|
+
println(stderr, `Error: git init/commit failed: ${error.message} The generated files are left in place — they form a valid project; run git init yourself.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
println(stdout, `Created ${plan.destinationPath}`);
|
|
84
|
+
println(stdout, "");
|
|
85
|
+
println(stdout, formatNextSteps({ displayDestination, installRan, runtimeSpec: plan.runtimeSpec }));
|
|
86
|
+
return gitFailed ? 1 : 0;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof CliUsageError) {
|
|
90
|
+
println(stderr, `Error: ${error.message}`);
|
|
91
|
+
println(stderr, USAGE_LINE);
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
if (error instanceof CliError) {
|
|
95
|
+
println(stderr, `Error: ${error.message}`);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
println(stderr, `Error: ${error.message}`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Two exit-code families the CLI distinguishes throughout: usage/config
|
|
2
|
+
// mistakes the caller can fix by rereading --help (exit 2), and everything
|
|
3
|
+
// else that failed after the input was understood (exit 1).
|
|
4
|
+
export class CliUsageError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export class CliError extends Error {
|
|
7
|
+
}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GitOutcome {
|
|
2
|
+
ran: boolean;
|
|
3
|
+
note?: string;
|
|
4
|
+
}
|
|
5
|
+
export type GitRunner = (cwd: string) => Promise<GitOutcome>;
|
|
6
|
+
/**
|
|
7
|
+
* Builds the default GitRunner. `env` is injectable so tests can isolate git
|
|
8
|
+
* identity discovery (HOME, GIT_CONFIG_*) without mutating process.env.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createGitRunner(env?: NodeJS.ProcessEnv): GitRunner;
|
|
11
|
+
export declare const defaultRunGit: GitRunner;
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
const COMMIT_MESSAGE = "Initial commit from create-zudo-circuit-doc";
|
|
3
|
+
function isInsideExistingWorkTree(cwd, env) {
|
|
4
|
+
try {
|
|
5
|
+
const out = execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
6
|
+
cwd,
|
|
7
|
+
env,
|
|
8
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9
|
+
})
|
|
10
|
+
.toString()
|
|
11
|
+
.trim();
|
|
12
|
+
return out === "true";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function hasGitIdentity(cwd, env) {
|
|
19
|
+
try {
|
|
20
|
+
const name = execFileSync("git", ["config", "user.name"], {
|
|
21
|
+
cwd,
|
|
22
|
+
env,
|
|
23
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
24
|
+
})
|
|
25
|
+
.toString()
|
|
26
|
+
.trim();
|
|
27
|
+
const email = execFileSync("git", ["config", "user.email"], {
|
|
28
|
+
cwd,
|
|
29
|
+
env,
|
|
30
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
31
|
+
})
|
|
32
|
+
.toString()
|
|
33
|
+
.trim();
|
|
34
|
+
return name.length > 0 && email.length > 0;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds the default GitRunner. `env` is injectable so tests can isolate git
|
|
42
|
+
* identity discovery (HOME, GIT_CONFIG_*) without mutating process.env.
|
|
43
|
+
*/
|
|
44
|
+
export function createGitRunner(env = process.env) {
|
|
45
|
+
return async (cwd) => {
|
|
46
|
+
if (isInsideExistingWorkTree(cwd, env)) {
|
|
47
|
+
return {
|
|
48
|
+
ran: false,
|
|
49
|
+
note: "Skipped git init: the destination is already inside a git work tree.",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
execFileSync("git", ["init", "-b", "main"], { cwd, env, stdio: "ignore" });
|
|
53
|
+
let note;
|
|
54
|
+
const identityArgs = [];
|
|
55
|
+
if (!hasGitIdentity(cwd, env)) {
|
|
56
|
+
identityArgs.push("-c", "user.name=create-zudo-circuit-doc", "-c", "user.email=create-zudo-circuit-doc@localhost");
|
|
57
|
+
note =
|
|
58
|
+
"No git identity was configured; used a neutral committer identity for the initial commit.";
|
|
59
|
+
}
|
|
60
|
+
execFileSync("git", ["add", "-A"], { cwd, env, stdio: "ignore" });
|
|
61
|
+
execFileSync("git", [...identityArgs, "commit", "-m", COMMIT_MESSAGE], {
|
|
62
|
+
cwd,
|
|
63
|
+
env,
|
|
64
|
+
stdio: "ignore",
|
|
65
|
+
});
|
|
66
|
+
return { ran: true, note };
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export const defaultRunGit = createGitRunner();
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const USAGE_LINE = "Usage: create-zudo-circuit-doc [destination] [--name <npm-name>] [--title <site title>] [--library <kicad-lib-name>] [--agent claude|codex|both|none] [--yes] [--install|--no-install] [--git|--no-git] [--runtime-spec <spec>] [--help] [--version]";
|
|
2
|
+
export declare const HELP_TEXT: string;
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const USAGE_LINE = "Usage: create-zudo-circuit-doc [destination] [--name <npm-name>] [--title <site title>] [--library <kicad-lib-name>] [--agent claude|codex|both|none] [--yes] [--install|--no-install] [--git|--no-git] [--runtime-spec <spec>] [--help] [--version]";
|
|
2
|
+
export const HELP_TEXT = [
|
|
3
|
+
USAGE_LINE,
|
|
4
|
+
"",
|
|
5
|
+
"Also works as: pnpm create zudo-circuit-doc <destination>",
|
|
6
|
+
"",
|
|
7
|
+
"Options:",
|
|
8
|
+
" --name <npm-name> Package name (default: the destination's basename)",
|
|
9
|
+
" --title <site title> Site title (default: title-cased --name)",
|
|
10
|
+
" --library <lib-name> KiCad library name (default: --name)",
|
|
11
|
+
" --agent <choice> claude | codex | both | none (default: both)",
|
|
12
|
+
" --yes, -y Do not prompt; a missing destination is an error",
|
|
13
|
+
" --install / --no-install Run `pnpm install` after scaffolding (default: on)",
|
|
14
|
+
" --git / --no-git Run `git init` + initial commit (default: on)",
|
|
15
|
+
" --runtime-spec <spec> Override the @takazudo/zudo-circuit-doc dependency",
|
|
16
|
+
" in package.json and doc/package.json: a semver",
|
|
17
|
+
" range/dist-tag, or a \"file:\" spec with an",
|
|
18
|
+
" absolute path (default: the template's own spec)",
|
|
19
|
+
" --help, -h Print this help and exit",
|
|
20
|
+
" --version, -v Print the package version and exit",
|
|
21
|
+
].join("\n");
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { CliError } from "./errors.js";
|
|
3
|
+
/** Runs `pnpm install` in `cwd` with stdio inherited (spec #7). */
|
|
4
|
+
export const defaultRunInstall = (cwd) => new Promise((resolve, reject) => {
|
|
5
|
+
const child = spawn("pnpm", ["install"], { cwd, stdio: "inherit" });
|
|
6
|
+
child.on("error", (error) => {
|
|
7
|
+
reject(new CliError(`Failed to run "pnpm install": ${error.message}`));
|
|
8
|
+
});
|
|
9
|
+
child.on("exit", (code, signal) => {
|
|
10
|
+
if (code === 0) {
|
|
11
|
+
resolve();
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
reject(new CliError(`"pnpm install" exited with ${signal ? `signal ${signal}` : `code ${code}`}.`));
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface NextStepsInput {
|
|
2
|
+
/** Path to print in the `cd` command — relative to the caller's cwd when possible. */
|
|
3
|
+
displayDestination: string;
|
|
4
|
+
/** Whether `pnpm install` already ran, so the manual step can be omitted. */
|
|
5
|
+
installRan: boolean;
|
|
6
|
+
/** The `--runtime-spec` value applied to package.json/doc/package.json, if any (spec #58). */
|
|
7
|
+
runtimeSpec?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Builds the exact, machine-extractable "Next steps:" block (spec #8). */
|
|
10
|
+
export declare function formatNextSteps(input: NextStepsInput): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { shellQuote } from "./shell-quote.js";
|
|
2
|
+
// #28 (pack-and-install verifier) parses this block back out and runs it
|
|
3
|
+
// verbatim, so the header text and indentation are load-bearing, not
|
|
4
|
+
// cosmetic — keep them exact. No literal ``` fences: some terminals/agents
|
|
5
|
+
// render them as literal text instead of a code block (spec #58 "no fences").
|
|
6
|
+
const HEADER = "Next steps:";
|
|
7
|
+
const HINT = "Open circuit/WORKFLOW.md → Workflow A";
|
|
8
|
+
const COMMAND_INDENT = " ";
|
|
9
|
+
/** Builds the exact, machine-extractable "Next steps:" block (spec #8). */
|
|
10
|
+
export function formatNextSteps(input) {
|
|
11
|
+
const commands = [`cd ${shellQuote(input.displayDestination)}`];
|
|
12
|
+
if (!input.installRan)
|
|
13
|
+
commands.push("pnpm install");
|
|
14
|
+
commands.push("pnpm circuit:doctor", "pnpm dev");
|
|
15
|
+
const lines = [HEADER, "", ...commands.map((command) => `${COMMAND_INDENT}${command}`)];
|
|
16
|
+
// Only surfaced when install did not already run: that is the one case
|
|
17
|
+
// where the user (or a resumed `pnpm install`) still needs to know the
|
|
18
|
+
// dependency spec in package.json/doc/package.json was overridden.
|
|
19
|
+
if (!input.installRan && input.runtimeSpec !== undefined) {
|
|
20
|
+
lines.push("", `Applied --runtime-spec ${JSON.stringify(input.runtimeSpec)} to package.json and doc/package.json.`);
|
|
21
|
+
}
|
|
22
|
+
lines.push("", HINT);
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentChoice, ParsedOptions } from "./args.ts";
|
|
2
|
+
export interface Plan {
|
|
3
|
+
/** Resolved once; every later step reuses this instead of re-resolving. */
|
|
4
|
+
destinationPath: string;
|
|
5
|
+
name: string;
|
|
6
|
+
title: string;
|
|
7
|
+
library: string;
|
|
8
|
+
agent: AgentChoice;
|
|
9
|
+
install: boolean;
|
|
10
|
+
git: boolean;
|
|
11
|
+
/** Overrides the template's `@takazudo/zudo-circuit-doc` dependency spec in package.json + doc/package.json (spec #58). `undefined` keeps the template's own spec. */
|
|
12
|
+
runtimeSpec: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Applies defaults (spec #2) and validates each value separately (spec #3). */
|
|
15
|
+
export declare function resolvePlan(options: ParsedOptions, destinationRaw: string, cwd: string): Plan;
|
|
16
|
+
/** Renders the plan for the pre-flight printout (spec #5). */
|
|
17
|
+
export declare function formatPlan(plan: Plan): string;
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { CliUsageError } from "./errors.js";
|
|
3
|
+
import { titleCaseFromName, validateLibrary, validateName, validateRuntimeSpec, validateTitle, } from "./validate.js";
|
|
4
|
+
/** Applies defaults (spec #2) and validates each value separately (spec #3). */
|
|
5
|
+
export function resolvePlan(options, destinationRaw, cwd) {
|
|
6
|
+
const destinationPath = path.resolve(cwd, destinationRaw);
|
|
7
|
+
const name = options.name ?? path.basename(destinationPath);
|
|
8
|
+
try {
|
|
9
|
+
validateName(name);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (options.name === undefined && error instanceof CliUsageError) {
|
|
13
|
+
throw new CliUsageError(`Cannot derive a valid package name from "${path.basename(destinationPath)}": ${error.message} Pass --name explicitly.`);
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
const title = options.title ?? titleCaseFromName(name);
|
|
18
|
+
validateTitle(title);
|
|
19
|
+
const library = options.library ?? libraryFromName(name);
|
|
20
|
+
try {
|
|
21
|
+
validateLibrary(library);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (options.library === undefined && error instanceof CliUsageError) {
|
|
25
|
+
throw new CliUsageError(`Cannot derive a valid library name from "${name}": ${error.message} Pass --library explicitly.`);
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
if (options.runtimeSpec !== undefined)
|
|
30
|
+
validateRuntimeSpec(options.runtimeSpec);
|
|
31
|
+
return {
|
|
32
|
+
destinationPath,
|
|
33
|
+
name,
|
|
34
|
+
title,
|
|
35
|
+
library,
|
|
36
|
+
agent: options.agent,
|
|
37
|
+
install: options.install,
|
|
38
|
+
git: options.git,
|
|
39
|
+
runtimeSpec: options.runtimeSpec,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Renders the plan for the pre-flight printout (spec #5). */
|
|
43
|
+
export function formatPlan(plan) {
|
|
44
|
+
return [
|
|
45
|
+
"Plan:",
|
|
46
|
+
` destination: ${plan.destinationPath}`,
|
|
47
|
+
` name: ${plan.name}`,
|
|
48
|
+
` title: ${plan.title}`,
|
|
49
|
+
` library: ${plan.library}`,
|
|
50
|
+
` agent: ${plan.agent}`,
|
|
51
|
+
` install: ${plan.install ? "yes" : "no"}`,
|
|
52
|
+
` git: ${plan.git ? "yes" : "no"}`,
|
|
53
|
+
` runtimeSpec: ${plan.runtimeSpec ?? "(template default)"}`,
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
/** Package names may be scoped or dotted; KiCad library names may not. */
|
|
57
|
+
function libraryFromName(name) {
|
|
58
|
+
return name.replace(/^@[^/]+\//, "").replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 64);
|
|
59
|
+
}
|
package/dist/prompt.d.ts
ADDED
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
/** Prompts on a TTY for the destination directory (spec #2). */
|
|
3
|
+
export async function promptForDestination(input, output) {
|
|
4
|
+
const rl = readline.createInterface({ input, output });
|
|
5
|
+
try {
|
|
6
|
+
const answer = await rl.question("Project directory: ");
|
|
7
|
+
return answer.trim();
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
rl.close();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Plan } from "./plan.ts";
|
|
2
|
+
export interface PlaceholderValues {
|
|
3
|
+
__PROJECT_NAME__: string;
|
|
4
|
+
__SITE_TITLE__: string;
|
|
5
|
+
__LIBRARY_NAME__: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function placeholdersFromPlan(plan: Plan): PlaceholderValues;
|
|
8
|
+
/** Throws a CliError, unchanged, if the destination exists and is not empty (spec #4). There is no force flag. */
|
|
9
|
+
export declare function checkDestinationCollision(destinationPath: string): void;
|
|
10
|
+
export interface ComposeParams {
|
|
11
|
+
plan: Plan;
|
|
12
|
+
templateDir: URL | string;
|
|
13
|
+
randomSuffix: () => string;
|
|
14
|
+
/** Test-only seam: called before each file is copied; throwing simulates a mid-copy failure. */
|
|
15
|
+
beforeCopyFile?: (relPath: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Composes the project into a sibling staging directory, then renames it
|
|
19
|
+
* onto the destination in one atomic step (spec #6). On any failure the
|
|
20
|
+
* staging directory is removed and the destination is left untouched.
|
|
21
|
+
*/
|
|
22
|
+
export declare function composeProject(params: ComposeParams): void;
|