create-nx-archweave 0.1.1 → 0.2.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.
@@ -1,71 +0,0 @@
1
- // This file exists in packages/shared and packages/create-nx-archweave (which imports
2
- // nothing); keep the two identical — the CLI's spec compares them.
3
- import { statSync } from 'node:fs';
4
- import { win32 } from 'node:path';
5
- function isFile(path) {
6
- try {
7
- return statSync(path).isFile();
8
- }
9
- catch {
10
- return false;
11
- }
12
- }
13
- const DEFAULT_ENV = { platform: process.platform, env: process.env, isFile };
14
- /** Windows keeps PATH and PATHEXT under any letter case. */
15
- function envValue(env, name) {
16
- const key = Object.keys(env).find((candidate) => candidate.toUpperCase() === name);
17
- return key === undefined ? undefined : env[key];
18
- }
19
- /** Where Windows would find `command`: as given when it has a path, else on PATH, trying
20
- * each PATHEXT extension unless it already has one. Undefined when nothing matches. */
21
- export function resolveWindowsCommand(command, environment) {
22
- const exts = (envValue(environment.env, 'PATHEXT') ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean);
23
- const candidates = (base) => (win32.extname(base) !== '' ? [base] : exts.map((ext) => base + ext));
24
- if (win32.isAbsolute(command) || /[\\/]/.test(command)) {
25
- return candidates(command).find(environment.isFile);
26
- }
27
- const dirs = (envValue(environment.env, 'PATH') ?? '').split(';').filter(Boolean);
28
- for (const dir of dirs) {
29
- const found = candidates(win32.join(dir, command)).find(environment.isFile);
30
- if (found !== undefined)
31
- return found;
32
- }
33
- return undefined;
34
- }
35
- // cmd.exe's metacharacters, escaped with ^ (the algorithm cross-spawn uses — MIT,
36
- // https://github.com/moxystudio/node-cross-spawn — after https://qntm.org/cmd).
37
- const META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
38
- /** A project-local `node_modules\.bin\*.cmd` shim hands its arguments to cmd.exe a second
39
- * time, so they are escaped twice (cross-spawn does the same). */
40
- const BIN_SHIM = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
41
- /** One argument for `cmd.exe /d /s /c "…"`: quoted for the program's own argv parsing,
42
- * then every cmd.exe metacharacter escaped (twice for a node_modules\.bin shim). `%VAR%`
43
- * is not fully protected from expansion — the arguments passed here are the program's own,
44
- * never user text, as with cross-spawn. */
45
- export function escapeCmdArgument(arg, doubleEscape = false) {
46
- let quoted = arg.replace(/(\\*)"/g, '$1$1\\"');
47
- quoted = quoted.replace(/(\\*)$/, '$1$1');
48
- const escaped = `"${quoted}"`.replace(META_CHARS, '^$1');
49
- return doubleEscape ? escaped.replace(META_CHARS, '^$1') : escaped;
50
- }
51
- /**
52
- * How to spawn `command args` without a shell, on every platform. Node cannot start a
53
- * Windows `.cmd` / `.bat` shim itself (ENOENT without the extension; EINVAL with it, since
54
- * the CVE-2024-27980 fix) — and npm, npx, pnpm, yarn and an npm-installed claude are all
55
- * such shims there. On Windows those run through `cmd.exe /d /s /c` with each argument
56
- * escaped; everything else (a real .exe, any other platform) is spawned unchanged.
57
- */
58
- export function spawnableCommand(command, args, environment = DEFAULT_ENV) {
59
- if (environment.platform !== 'win32')
60
- return { command, args: [...args] };
61
- const resolved = resolveWindowsCommand(command, environment);
62
- if (resolved === undefined || !/\.(cmd|bat)$/i.test(resolved))
63
- return { command, args: [...args] };
64
- const doubleEscape = BIN_SHIM.test(resolved);
65
- const line = [resolved.replace(META_CHARS, '^$1'), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(' ');
66
- return {
67
- command: envValue(environment.env, 'COMSPEC') ?? 'cmd.exe',
68
- args: ['/d', '/s', '/c', `"${line}"`],
69
- windowsVerbatimArguments: true,
70
- };
71
- }
package/dist/cli/types.js DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * Literal option types shared by the parse/prompt/plan layers.
3
- *
4
- * These are intentionally NOT imported from `@nx-archweave/shared` or
5
- * `@nx-archweave/template-engine`: the CLI stream owns none of that policy (no content
6
- * or policy knowledge), it only forwards option values — so the literals here are
7
- * copied from `packages/nx-plugin/src/generators/init/schema.json`'s `preset`,
8
- * `frontend`, and `locale` enums, not derived from them. If the schema's enum ever
9
- * changes, this file must be updated to match by hand.
10
- */
11
- export const PRESET_VALUES = ['quick-start', 'solo', 'team'];
12
- export const FRONTEND_VALUES = ['react-vite', 'next', 'none'];
13
- export const BACKEND_VALUES = ['express', 'nest', 'none'];
14
- export const LOCALE_VALUES = ['ko', 'en'];
15
- /** `reviewProvider` is the host's default review provider — a copy of template-engine's
16
- * AI host table (types.spec.ts keeps it equal), shown as the review question's default. */
17
- export const AI_HOST_CHOICES = [
18
- { id: 'claude-code', label: 'Claude Code', nxAgent: 'claude', reviewProvider: 'claude' },
19
- { id: 'codex', label: 'Codex', nxAgent: 'codex', reviewProvider: 'gpt' },
20
- { id: 'cursor', label: 'Cursor', nxAgent: 'cursor', reviewProvider: 'cursor' },
21
- { id: 'gemini-cli', label: 'Gemini CLI', nxAgent: 'gemini', reviewProvider: 'gemini' },
22
- ];
23
- /** init's `reviewProvider` enum (types.spec.ts keeps it equal), with display labels. */
24
- export const REVIEW_PROVIDER_CHOICES = [
25
- { id: 'claude', label: 'Claude' },
26
- { id: 'gemini', label: 'Gemini' },
27
- { id: 'gpt', label: 'GPT' },
28
- { id: 'cursor', label: 'Cursor' },
29
- ];
30
- export const REVIEW_PROVIDER_VALUES = REVIEW_PROVIDER_CHOICES.map((provider) => provider.id);
31
- export const AI_HOST_VALUES = AI_HOST_CHOICES.map((host) => host.id);
32
- /** Used when nothing picked a host: a non-interactive run without --ai-hosts, or Enter. */
33
- export const DEFAULT_AI_HOSTS = ['claude-code'];
34
- /** The command each host's `--version` probe runs, measured
35
- * 2026-10-15: claude → "2.1.300 (Claude Code)", codex → "codex-cli 0.162.1", gemini →
36
- * "0.64.0". Cursor's installer links `cursor-agent` and a generic `agent`; only the
37
- * unambiguous name is probed, as Cursor's install guides name it. */
38
- export const AI_HOST_PROBES = {
39
- 'claude-code': 'claude',
40
- codex: 'codex',
41
- cursor: 'cursor-agent',
42
- 'gemini-cli': 'gemini',
43
- };
44
- export const PLUGIN_CHOICES = [
45
- {
46
- id: 'ponytail',
47
- label: 'ponytail',
48
- description: 'over-engineering review rules and skills — "makes your AI agent think like the laziest senior dev in the room"',
49
- license: 'MIT',
50
- host: 'claude-code',
51
- },
52
- ];
53
- export const PLUGIN_VALUES = PLUGIN_CHOICES.map((plugin) => plugin.id);
@@ -1,29 +0,0 @@
1
- /** npm's limit on a package name, which the workspace name becomes. */
2
- const MAX_LENGTH = 214;
3
- const EXAMPLE = 'my-app';
4
- /**
5
- * Why `name` can't be a workspace name, or undefined when it can. The name becomes the
6
- * directory and the root package.json name, and it reaches create-nx-workspace as an
7
- * argument — on Windows through cmd.exe — so only npm's package-name characters pass:
8
- * lowercase letters, digits, "-", "_" and ".", not starting with "." or "_".
9
- */
10
- export function workspaceNameProblem(name) {
11
- if (name.length === 0)
12
- return 'it is empty';
13
- if (/\s/.test(name))
14
- return 'it contains whitespace';
15
- if (name.length > MAX_LENGTH)
16
- return `it is ${name.length} characters long, and npm allows at most ${MAX_LENGTH}`;
17
- if (name.startsWith('.') || name.startsWith('_'))
18
- return `it starts with "${name[0]}"`;
19
- if (/[A-Z]/.test(name))
20
- return 'it contains uppercase letters, and npm package names are lowercase';
21
- const others = [...new Set(name.replace(/[a-z0-9._-]/g, ''))];
22
- if (others.length > 0) {
23
- return `it contains ${others.map((char) => `"${char}"`).join(', ')} — only lowercase letters, digits, "-", "_" and "." are allowed`;
24
- }
25
- return undefined;
26
- }
27
- export function invalidWorkspaceNameMessage(name, problem) {
28
- return `Invalid workspace name "${name}": ${problem}. For example: ${EXAMPLE}`;
29
- }
@@ -1,71 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { AI_HOST_CHOICES, PLUGIN_CHOICES } from "./cli/types.js";
5
- // Keep in sync with packages/nx-plugin/generators.json (same order).
6
- const AVAILABLE_GENERATORS = [
7
- 'doctor',
8
- 'init',
9
- 'add-integration',
10
- 'add-review',
11
- 'add-backend',
12
- 'add-agent-rules',
13
- 'add-github-templates',
14
- 'add-fsd-lite',
15
- 'page',
16
- 'widget',
17
- 'feature',
18
- 'entity',
19
- 'shared',
20
- 'install-integration',
21
- 'record-integration',
22
- 'allow-builds',
23
- ];
24
- /** This package's own version, read from its package.json — the nearest one up from this file
25
- * that is ours, which holds from source, from the tsc output and from the bundles alike. */
26
- function ownVersion() {
27
- let dir = dirname(fileURLToPath(import.meta.url));
28
- for (;;) {
29
- const manifest = join(dir, 'package.json');
30
- if (existsSync(manifest)) {
31
- const pkg = JSON.parse(readFileSync(manifest, 'utf-8'));
32
- if (pkg.name === 'create-nx-archweave' && pkg.version)
33
- return pkg.version;
34
- }
35
- const parent = dirname(dir);
36
- if (parent === dir)
37
- throw new Error('create-nx-archweave: its own package.json was not found');
38
- dir = parent;
39
- }
40
- }
41
- export const CLI_VERSION = ownVersion();
42
- const PLUGIN_LIST = PLUGIN_CHOICES.map((plugin) => `${plugin.id} (${AI_HOST_CHOICES.find((host) => host.id === plugin.host)?.label ?? plugin.host})`).join(', ');
43
- export function renderUsage() {
44
- return [
45
- 'Usage: create-nx-archweave <name> [options]',
46
- '',
47
- 'Options:',
48
- ' --preset <quick-start|solo|team> Preset that configures the generated workspace',
49
- ' --frontend <react-vite|next|none> Target frontend (none: a backend-only workspace — needs --backend)',
50
- ' --backend <express|nest|none> Backend app, wired with shared-types and enforced boundaries (asked if omitted)',
51
- ' --locale <ko|en> Generated content language (asked if omitted; quick-start: en)',
52
- ' --ai-hosts <list> AI coding tools, comma-separated: claude-code, codex, cursor, gemini-cli',
53
- ' (asked if omitted, showing which are installed; without a terminal: claude-code)',
54
- ' --plugins <list|none> Optional plugins for the picked AI hosts, comma-separated: ' + PLUGIN_LIST,
55
- ' (asked if omitted, for hosts installed here; without a terminal: none)',
56
- ' --review, --no-review Force AI PR review on/off (quick-start asks y/N; solo/team: on)',
57
- ' --review-provider <name> claude, gemini, gpt or cursor (asked when review is on; default: the first AI tool\'s)',
58
- ' --plugin-source <spec> Override the nx-archweave plugin package/version to install',
59
- ' --package-manager <name> npm, pnpm, yarn or bun (asked if omitted; without a terminal: the one that ran this command, else npm)',
60
- ' -h, --help Show this help',
61
- ' -v, --version Show the CLI version',
62
- '',
63
- 'Any required option left unset above is asked interactively.',
64
- '',
65
- 'Available generators (once installed):',
66
- ` ${AVAILABLE_GENERATORS.join(', ')}`,
67
- ].join('\n');
68
- }
69
- export function renderVersion() {
70
- return CLI_VERSION;
71
- }