create-chanom-app 0.2.0 → 0.4.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/README.md +7 -2
- package/dist/index.js +127 -10
- package/dist/templates/README.md.template +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -31,7 +31,8 @@ Run with no flags, the CLI walks you through:
|
|
|
31
31
|
1. **Project name** - the target directory (kebab-cased). Defaults to `my-chanom-app`.
|
|
32
32
|
2. **Initialize a git repository?** - `git init` plus an initial commit.
|
|
33
33
|
3. **Add commit hooks?** - husky + lint-staged + commitlint (only asked when git is enabled).
|
|
34
|
-
4. **
|
|
34
|
+
4. **Add agent coding standards?** - the `coding-standards` skill in `.agents/skills`, linked into `.claude/skills`.
|
|
35
|
+
5. **Install dependencies now?** - runs `<pm> install` after scaffolding.
|
|
35
36
|
|
|
36
37
|
Every prompt has a matching flag so you can pre-answer any of them (or all, with `--yes`).
|
|
37
38
|
|
|
@@ -43,6 +44,8 @@ Every prompt has a matching flag so you can pre-answer any of them (or all, with
|
|
|
43
44
|
| `--git` / `--no-git` | Initialize a git repository (default: yes) |
|
|
44
45
|
| `--commit-hooks` | Add husky + lint-staged + commitlint (requires git) |
|
|
45
46
|
| `--no-commit-hooks` | Skip the commit-hooks topping |
|
|
47
|
+
| `--agent-skills` | Add the coding-standards skill (default: yes) |
|
|
48
|
+
| `--no-agent-skills` | Skip the agent-skills topping |
|
|
46
49
|
| `--install` / `--no-install` | Install dependencies after scaffolding (default: install) |
|
|
47
50
|
| `--pm <pnpm\|npm\|yarn\|bun>` | Package manager to record (default: detected) |
|
|
48
51
|
| `-y`, `--yes` | Accept all defaults, no prompts (CI-friendly) |
|
|
@@ -65,7 +68,7 @@ pnpm create chanom-app my-app --yes --no-install
|
|
|
65
68
|
|
|
66
69
|
A minimal Vite + React + TS app wired to the chanom shared configs:
|
|
67
70
|
|
|
68
|
-
- `package.json` - dependencies pinned to the versions the scaffolder bundles, plus `dev` / `build` / `preview` / `lint` / `format` / `format:check` / `test` / `test:watch` scripts
|
|
71
|
+
- `package.json` - dependencies pinned to the versions the scaffolder bundles, plus `dev` / `build` / `preview` / `typecheck` / `lint` / `format` / `format:check` / `test` / `test:watch` scripts
|
|
69
72
|
- `vite.config.ts` - using `@vitejs/plugin-react` and [`@chanom/vite-config`](../vite-config)
|
|
70
73
|
- `tsconfig.json` - extending `@tsconfig/vite-react`
|
|
71
74
|
- `oxlint.config.ts` / `oxfmt.config.ts` - re-exporting the [`@chanom/dev-config`](../dev-config) presets
|
|
@@ -76,6 +79,8 @@ A minimal Vite + React + TS app wired to the chanom shared configs:
|
|
|
76
79
|
|
|
77
80
|
With the **commit-hooks** topping, it also adds husky, lint-staged, and commitlint as dev dependencies plus their config files (`.husky/pre-commit`, `.husky/commit-msg`, `.lintstagedrc.json`, `.commitlintrc.json`). The hooks materialize on the first install via the generated `prepare: "husky"` script.
|
|
78
81
|
|
|
82
|
+
With the **agent-skills** topping, it adds the `coding-standards` agent skill - a router `SKILL.md` plus TypeScript, React, and Vitest references written for exactly the stack scaffolded here. The real files land in `.agents/skills/coding-standards/` so any agent tool can read them, and `.claude/skills/coding-standards` is a relative symlink pointing at that directory rather than a second copy. It contributes no packages and no scripts, so skipping it leaves both directories out entirely.
|
|
83
|
+
|
|
79
84
|
## Development
|
|
80
85
|
|
|
81
86
|
```sh
|
package/dist/index.js
CHANGED
|
@@ -206,6 +206,58 @@ var Prompter = class extends Effect.Service()("internal/Prompter", { succeed: {
|
|
|
206
206
|
})
|
|
207
207
|
} }) {};
|
|
208
208
|
//#endregion
|
|
209
|
+
//#region ../internal/src/skills/index.ts
|
|
210
|
+
/**
|
|
211
|
+
* The coding-standards skill, targeting the stack `create-chanom-app` scaffolds
|
|
212
|
+
* (Vite + React + TypeScript, oxlint, oxfmt, vitest). Its markdown is authored
|
|
213
|
+
* as real `.md` files under `skills/coding-standards/` and inlined here at
|
|
214
|
+
* build time through the `__CODING_STANDARDS_FILES__` define, so `chanom brew`
|
|
215
|
+
* and `create-chanom-app` install byte-identical content from one source.
|
|
216
|
+
*/
|
|
217
|
+
const codingStandardsSkill = {
|
|
218
|
+
name: "coding-standards",
|
|
219
|
+
files: [
|
|
220
|
+
{
|
|
221
|
+
"path": "references/react.md",
|
|
222
|
+
"contents": "# React\n\nReact 19 with the Vite plugin. Entry is `src/main.tsx`, which mounts `App` from\n`src/app.tsx` in `StrictMode`.\n\n## Structure\n\n`src/` grows by role as the app does: `components/`, `hooks/`, `utils/`,\n`pages/`, `providers/`. Feature-scoped code lives under `features/<name>/` and\nmirrors that same layout. Put code in the feature folder unless two features\nshare it.\n\n## Components\n\n- **Container / presentational**: containers fetch data and hold logic;\n presentational components take props and return JSX with no side effects.\n- Props are wrapped in `Readonly<>`:\n `export function UserCard({ name }: Readonly<UserCardProps>)`.\n- Conditional rendering uses a ternary (`isOpen ? <Panel /> : null`), never\n `&&` — a falsy number renders as `0`.\n- Every component exports inline. `default` export is reserved for route-level\n page components.\n- Shared state lives in a context provider under `providers/`. Reach for\n context only once prop-passing spans three levels.\n\n## Hooks\n\nCustom hooks are named `use-<thing>.ts` and return an object, not a positional\ntuple, once they return more than two values. Every effect declares its full\ndependency array; an effect with no cleanup and no async work is usually a\nrender-time computation instead.\n"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"path": "references/typescript.md",
|
|
226
|
+
"contents": "# TypeScript\n\n## Types\n\n- Never use `any`. Reach for generics and utility types; when you must escape\n the type system use `unknown` and narrow explicitly.\n- Type-only imports use `import type { ... }`. A mixed import splits its type\n part into a separate `import type` line.\n- Derive types from their source rather than restating them — `z.infer` for a\n schema, `ReturnType`/`Parameters` for a function, `typeof` for a const object.\n A hand-written duplicate drifts.\n- Props and params objects are declared as `interface`, named after their owner:\n `interface UserCardProps`, `interface UseCurrentUserParams`.\n\n## Modules\n\n- Functions, not classes. Hooks, utils, factories, and components are plain\n exported functions.\n- Export inline (`export function formatDate(...)`) rather than a trailing\n `export { ... }` block.\n- Import order is enforced by oxlint — run `pnpm lint --fix` rather than\n hand-sorting.\n\n## Errors\n\nThrow `Error` (or a subclass) with a message naming what failed and the value\nthat caused it. An unused catch binding is omitted: `catch {`, not\n`catch (_e) {`.\n"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"path": "references/vitest.md",
|
|
230
|
+
"contents": "# Vitest\n\nTests run on jsdom with globals enabled — `describe`, `it`, `expect`, and `vi`\nneed no import. `src/test-setup.ts` loads `@testing-library/jest-dom/vitest`, so\nits matchers are available everywhere. Run with `pnpm test`, or `pnpm test:watch`\nwhile iterating.\n\n## Layout\n\nTests are colocated and named `*.test.ts` / `*.test.tsx`, sitting beside the\nfile they cover: `src/app.tsx` → `src/app.test.tsx`.\n\n## Component tests\n\nTesting Library with `user-event`, querying by role and accessible name:\n\n```tsx\ndescribe('UserCard', () => {\n it('should render the user name correctly', () => {\n render(<UserCard name=\"Ada\" />);\n expect(screen.getByRole('heading', { name: 'Ada' })).toBeInTheDocument();\n });\n\n it('should call onSelect when clicked', async () => {\n const user = userEvent.setup();\n const onSelect = vi.fn();\n render(<UserCard name=\"Ada\" onSelect={onSelect} />);\n await user.click(screen.getByRole('button', { name: 'Select' }));\n expect(onSelect).toHaveBeenCalledTimes(1);\n });\n});\n```\n\n- Interactions go through `userEvent.setup()` and `await user.click(...)`, not\n `fireEvent`.\n- Handlers are `vi.fn()` asserted by call count, including the negative case —\n a disabled control asserts `toHaveBeenCalledTimes(0)`.\n- Query by `getByRole` / `getByText`. Reach for `getByTestId` only when no\n accessible query exists, such as a decorative spinner.\n- Assert on what the user observes, not on state or implementation details.\n\n## Naming\n\nTest names read `should <behaviour> correctly` or `should <effect> when\n<action>`. Group a unit's tests in one `describe` named for the unit.\n"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"path": "SKILL.md",
|
|
234
|
+
"contents": "---\nname: coding-standards\ndescription: Coding standards for this Vite + React + TypeScript app. Use when writing or changing components, hooks, utils, or tests, and before committing.\n---\n\n# Coding standards\n\nVite + React 19 + TypeScript, scaffolded with `create-chanom-app`. oxlint lints,\noxfmt formats, vitest tests. Read the reference for what you touch **before**\nwriting, so new code matches the code already here.\n\n## Which reference to load\n\n| You are writing… | Load |\n| -------------------------------------------- | -------------------------- |\n| Any `.ts` / `.tsx` file | `references/typescript.md` |\n| A component, hook, or anything rendering JSX | `references/react.md` |\n| A `*.test.ts` / `*.test.tsx` file | `references/vitest.md` |\n\nThese layer: a new component with a test reads all three. Load every row that\napplies.\n\n## Universal rules\n\n- Filenames are kebab-case: `user-card.tsx`, `use-current-user.ts`, `format-date.ts`.\n- Before adding a file, open two neighbouring files of the same kind and match\n their shape — import order, export style, test naming.\n- Source lives under `src/`. Colocate a test beside the code it covers:\n `src/utils/format-date.ts` → `src/utils/format-date.test.ts`.\n\n## Verify\n\nAfter implementing, from the project root:\n\n```sh\npnpm lint\npnpm test\npnpm build\npnpm typecheck\n```\n\nDone only when all four exit clean. On failure, fix and rerun until they do.\nRun `pnpm format` before committing. Comment- or doc-only changes may skip all\nfour.\n\n## Committing\n\nAsk before committing. Use conventional commits under 100 characters:\n`<type>(<scope>): <subject>`, e.g. `feat(auth): add sign-out button`. Scope is\nthe area you touched, not the repo name.\n"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
/** Directory holding the real skill files, agent-agnostic. */
|
|
239
|
+
const SKILLS_ROOT = ".agents/skills";
|
|
240
|
+
/** Directory Claude Code reads skills from; its entries are links into {@link SKILLS_ROOT}. */
|
|
241
|
+
const CLAUDE_SKILLS_ROOT = ".claude/skills";
|
|
242
|
+
/** Files a skill writes, rooted at `.agents/skills/<name>/`. */
|
|
243
|
+
function skillFiles(skill) {
|
|
244
|
+
return skill.files.map((file) => ({
|
|
245
|
+
path: `${SKILLS_ROOT}/${skill.name}/${file.path}`,
|
|
246
|
+
contents: file.contents
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The `.claude/skills/<name>` link for a skill. Real files live under
|
|
251
|
+
* `.agents/skills/` so any agent tool can read them; Claude Code only looks in
|
|
252
|
+
* `.claude/skills/`, so it gets a link rather than a second copy.
|
|
253
|
+
*/
|
|
254
|
+
function skillLink(skill) {
|
|
255
|
+
return {
|
|
256
|
+
path: `${CLAUDE_SKILLS_ROOT}/${skill.name}`,
|
|
257
|
+
target: `../../${SKILLS_ROOT}/${skill.name}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
209
261
|
//#region src/bundled-versions.ts
|
|
210
262
|
const depVersions = {
|
|
211
263
|
"react": "19.2.8",
|
|
@@ -229,10 +281,10 @@ const depVersions = {
|
|
|
229
281
|
"@commitlint/cli": "21.2.0",
|
|
230
282
|
"@commitlint/config-conventional": "21.2.0",
|
|
231
283
|
"@chanom/dev-config": "0.0.3",
|
|
232
|
-
"@chanom/vite-config": "0.1.
|
|
284
|
+
"@chanom/vite-config": "0.1.1"
|
|
233
285
|
};
|
|
234
286
|
/** This package's own version, for `--version`. */
|
|
235
|
-
const pkgVersion = "0.
|
|
287
|
+
const pkgVersion = "0.4.0";
|
|
236
288
|
//#endregion
|
|
237
289
|
//#region src/domain/args.ts
|
|
238
290
|
const PACKAGE_MANAGERS = [
|
|
@@ -280,6 +332,12 @@ function parseArgs(argv) {
|
|
|
280
332
|
case "--no-commit-hooks":
|
|
281
333
|
args.commitHooks = false;
|
|
282
334
|
break;
|
|
335
|
+
case "--agent-skills":
|
|
336
|
+
args.agentSkills = true;
|
|
337
|
+
break;
|
|
338
|
+
case "--no-agent-skills":
|
|
339
|
+
args.agentSkills = false;
|
|
340
|
+
break;
|
|
283
341
|
case "--install":
|
|
284
342
|
args.install = true;
|
|
285
343
|
break;
|
|
@@ -323,10 +381,11 @@ function parseArgs(argv) {
|
|
|
323
381
|
/**
|
|
324
382
|
* Validates a project name / target directory. Returns an error message to
|
|
325
383
|
* reject it, or `undefined` when it is acceptable. `.` (scaffold into the
|
|
326
|
-
* current directory) is allowed.
|
|
384
|
+
* current directory) is allowed. Accepts `undefined` so it can be handed
|
|
385
|
+
* straight to a prompt, where an empty submission arrives as `undefined`.
|
|
327
386
|
*/
|
|
328
387
|
function validateProjectName(name) {
|
|
329
|
-
if (name.trim() === "") return "Please enter a project name.";
|
|
388
|
+
if (name === void 0 || name.trim() === "") return "Please enter a project name.";
|
|
330
389
|
if (name === ".") return void 0;
|
|
331
390
|
if (!/^[a-z0-9._-]+$/i.test(name)) return "Use letters, numbers, dots, dashes, and underscores only.";
|
|
332
391
|
if (name.startsWith(".")) return "Name cannot start with a dot.";
|
|
@@ -342,16 +401,27 @@ function toKebabCase(name) {
|
|
|
342
401
|
}
|
|
343
402
|
//#endregion
|
|
344
403
|
//#region src/domain/contribution.ts
|
|
404
|
+
const emptyContribution = {
|
|
405
|
+
dependencies: [],
|
|
406
|
+
devDependencies: [],
|
|
407
|
+
scripts: {},
|
|
408
|
+
viteImports: [],
|
|
409
|
+
vitePlugins: [],
|
|
410
|
+
files: [],
|
|
411
|
+
links: []
|
|
412
|
+
};
|
|
345
413
|
const dedupe = (values) => [...new Set(values)];
|
|
346
414
|
/**
|
|
347
415
|
* Folds contributions in order into a single value. Package names, vite imports,
|
|
348
416
|
* and plugins are concatenated then de-duplicated (first occurrence wins its
|
|
349
417
|
* position); scripts merge with later contributions overriding earlier keys;
|
|
350
|
-
* files de-duplicate by path (first writer wins).
|
|
418
|
+
* files and links de-duplicate by path (first writer wins).
|
|
351
419
|
*/
|
|
352
420
|
function mergeContributions(contributions) {
|
|
353
421
|
const filesByPath = /* @__PURE__ */ new Map();
|
|
354
422
|
for (const contribution of contributions) for (const file of contribution.files) if (!filesByPath.has(file.path)) filesByPath.set(file.path, file);
|
|
423
|
+
const linksByPath = /* @__PURE__ */ new Map();
|
|
424
|
+
for (const contribution of contributions) for (const link of contribution.links) if (!linksByPath.has(link.path)) linksByPath.set(link.path, link);
|
|
355
425
|
return {
|
|
356
426
|
dependencies: dedupe(contributions.flatMap((c) => c.dependencies)),
|
|
357
427
|
devDependencies: dedupe(contributions.flatMap((c) => c.devDependencies)),
|
|
@@ -361,7 +431,8 @@ function mergeContributions(contributions) {
|
|
|
361
431
|
}), {}),
|
|
362
432
|
viteImports: dedupe(contributions.flatMap((c) => c.viteImports)),
|
|
363
433
|
vitePlugins: dedupe(contributions.flatMap((c) => c.vitePlugins)),
|
|
364
|
-
files: [...filesByPath.values()]
|
|
434
|
+
files: [...filesByPath.values()],
|
|
435
|
+
links: [...linksByPath.values()]
|
|
365
436
|
};
|
|
366
437
|
}
|
|
367
438
|
//#endregion
|
|
@@ -583,6 +654,7 @@ const baseline = {
|
|
|
583
654
|
dev: "vite",
|
|
584
655
|
build: "tsc -b && vite build",
|
|
585
656
|
preview: "vite preview",
|
|
657
|
+
typecheck: "tsc --noEmit",
|
|
586
658
|
lint: "oxlint",
|
|
587
659
|
format: "oxfmt",
|
|
588
660
|
"format:check": "oxfmt --check",
|
|
@@ -597,7 +669,8 @@ const baseline = {
|
|
|
597
669
|
}, {
|
|
598
670
|
path: ".vscode/extensions.json",
|
|
599
671
|
contents: asJson({ recommendations: RECOMMENDED_EXTENSIONS })
|
|
600
|
-
}]
|
|
672
|
+
}],
|
|
673
|
+
links: []
|
|
601
674
|
};
|
|
602
675
|
const LINT_STAGED_CONFIG = {
|
|
603
676
|
"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}": ["oxlint --fix --no-error-on-unmatched-pattern"],
|
|
@@ -644,12 +717,27 @@ function commitHooks(pm) {
|
|
|
644
717
|
path: ".commitlintrc.json",
|
|
645
718
|
contents: JSON.stringify(COMMITLINT_CONFIG, null, 2) + "\n"
|
|
646
719
|
}
|
|
647
|
-
]
|
|
720
|
+
],
|
|
721
|
+
links: []
|
|
648
722
|
};
|
|
649
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* The `agent-skills` topping: the chanom coding-standards skill. Real files go
|
|
726
|
+
* to `.agents/skills/` so any agent tool can read them, and `.claude/skills/`
|
|
727
|
+
* gets a symlink rather than a second copy. Contributes files and links only -
|
|
728
|
+
* no packages, no scripts - so projects that skip it get neither directory.
|
|
729
|
+
*/
|
|
730
|
+
const agentSkills = {
|
|
731
|
+
...emptyContribution,
|
|
732
|
+
files: skillFiles(codingStandardsSkill),
|
|
733
|
+
links: [skillLink(codingStandardsSkill)]
|
|
734
|
+
};
|
|
650
735
|
/** The baseline plus whichever toppings the resolved options select, in fold order. */
|
|
651
736
|
function selectContributions(selection) {
|
|
652
|
-
|
|
737
|
+
const contributions = [baseline];
|
|
738
|
+
if (selection.commitHooks) contributions.push(commitHooks(selection.pm));
|
|
739
|
+
if (selection.agentSkills) contributions.push(agentSkills);
|
|
740
|
+
return contributions;
|
|
653
741
|
}
|
|
654
742
|
//#endregion
|
|
655
743
|
//#region src/scaffold.ts
|
|
@@ -671,6 +759,25 @@ const copyTemplates = Effect.fn("scaffold.copyTemplates")(function* (plan) {
|
|
|
671
759
|
yield* writeFile(plan.targetDir, file.dest, contents);
|
|
672
760
|
}
|
|
673
761
|
});
|
|
762
|
+
/**
|
|
763
|
+
* Creates one topping-contributed symlink, making its parent directory first.
|
|
764
|
+
* Scaffolding into an existing directory (`create chanom-app .`) can meet a link
|
|
765
|
+
* an earlier run - or `chanom add-skills` - already made, and `symlink` fails
|
|
766
|
+
* with EEXIST on anything already there, so an occupied path is left alone and
|
|
767
|
+
* reported instead of aborting the scaffold after the files are written.
|
|
768
|
+
*/
|
|
769
|
+
const writeLink = Effect.fn("scaffold.writeLink")(function* (targetDir, link) {
|
|
770
|
+
const fs = yield* FileSystem.FileSystem;
|
|
771
|
+
const path = yield* Path.Path;
|
|
772
|
+
const prompter = yield* Prompter;
|
|
773
|
+
const linkPath = path.join(targetDir, link.path);
|
|
774
|
+
if (yield* fs.exists(linkPath)) {
|
|
775
|
+
yield* prompter.warn(`\`${link.path}\` already exists - skipping link`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
yield* fs.makeDirectory(path.dirname(linkPath), { recursive: true });
|
|
779
|
+
yield* fs.symlink(link.target, linkPath);
|
|
780
|
+
});
|
|
674
781
|
const writeGeneratedFiles = Effect.fn("scaffold.writeGeneratedFiles")(function* (plan, folded) {
|
|
675
782
|
const appName = toKebabCase(plan.appName);
|
|
676
783
|
yield* writeFile(plan.targetDir, "package.json", buildPackageJson({
|
|
@@ -684,6 +791,7 @@ const writeGeneratedFiles = Effect.fn("scaffold.writeGeneratedFiles")(function*
|
|
|
684
791
|
yield* writeFile(plan.targetDir, "tsconfig.json", buildTsconfig());
|
|
685
792
|
yield* writeFile(plan.targetDir, "index.html", renderIndexHtml(appName));
|
|
686
793
|
for (const file of folded.files) yield* writeFile(plan.targetDir, file.path, file.contents);
|
|
794
|
+
for (const link of folded.links) yield* writeLink(plan.targetDir, link);
|
|
687
795
|
});
|
|
688
796
|
const initGit = Effect.fn("scaffold.initGit")(function* (plan) {
|
|
689
797
|
const git = yield* Git;
|
|
@@ -709,7 +817,8 @@ const scaffold = Effect.fn("scaffold")(function* (plan) {
|
|
|
709
817
|
const fs = yield* FileSystem.FileSystem;
|
|
710
818
|
const folded = mergeContributions(selectContributions({
|
|
711
819
|
pm: plan.pm,
|
|
712
|
-
commitHooks: plan.commitHooks
|
|
820
|
+
commitHooks: plan.commitHooks,
|
|
821
|
+
agentSkills: plan.agentSkills
|
|
713
822
|
}));
|
|
714
823
|
yield* fs.makeDirectory(plan.targetDir, { recursive: true });
|
|
715
824
|
yield* copyTemplates(plan);
|
|
@@ -731,6 +840,8 @@ ${pc.bold("Options")}
|
|
|
731
840
|
${pc.cyan("--git / --no-git")} Initialize a git repository (default: yes)
|
|
732
841
|
${pc.cyan("--commit-hooks")} Add husky + lint-staged + commitlint (requires git)
|
|
733
842
|
${pc.cyan("--no-commit-hooks")} Skip the commit-hooks topping
|
|
843
|
+
${pc.cyan("--agent-skills")} Add the coding-standards skill to .claude/skills
|
|
844
|
+
${pc.cyan("--no-agent-skills")} Skip the agent-skills topping
|
|
734
845
|
${pc.cyan("--install / --no-install")} Install dependencies after scaffolding (default: install)
|
|
735
846
|
${pc.cyan("--pm <pnpm|npm|yarn|bun>")} Package manager to record (default: detected)
|
|
736
847
|
${pc.cyan("-y, --yes")} Accept all defaults, no prompts (CI-friendly)
|
|
@@ -802,6 +913,10 @@ const program$1 = (argv, cwd, options) => Effect.gen(function* () {
|
|
|
802
913
|
message: "Add commit hooks (husky + lint-staged + commitlint)?",
|
|
803
914
|
initialValue: true
|
|
804
915
|
})) : false;
|
|
916
|
+
const agentSkills = yield* resolveConfirm(args.agentSkills, args.yes, prompter.confirm({
|
|
917
|
+
message: "Add agent coding standards (.agents/skills)?",
|
|
918
|
+
initialValue: true
|
|
919
|
+
}));
|
|
805
920
|
const install = yield* resolveConfirm(args.install, args.yes, prompter.confirm({
|
|
806
921
|
message: "Install dependencies now?",
|
|
807
922
|
initialValue: true
|
|
@@ -815,6 +930,7 @@ const program$1 = (argv, cwd, options) => Effect.gen(function* () {
|
|
|
815
930
|
`${pc.dim("package mgr")} ${pm}`,
|
|
816
931
|
`${pc.dim("git")} ${git ? "yes" : "no"}`,
|
|
817
932
|
`${pc.dim("commit hooks")} ${git ? commitHooks ? "yes" : "no" : pc.dim("n/a")}`,
|
|
933
|
+
`${pc.dim("agent skills")} ${agentSkills ? "yes" : "no"}`,
|
|
818
934
|
`${pc.dim("install")} ${install ? "yes" : "no"}`
|
|
819
935
|
].join("\n"));
|
|
820
936
|
yield* scaffold({
|
|
@@ -824,6 +940,7 @@ const program$1 = (argv, cwd, options) => Effect.gen(function* () {
|
|
|
824
940
|
packageManagerField: packageManagerField(pm, options.userAgent),
|
|
825
941
|
git,
|
|
826
942
|
commitHooks,
|
|
943
|
+
agentSkills,
|
|
827
944
|
install,
|
|
828
945
|
templatesRoot: options.templatesRoot
|
|
829
946
|
});
|
|
@@ -9,6 +9,7 @@ Brewed with [create-chanom-app](https://github.com/peeranat-dan/chanom).
|
|
|
9
9
|
| `pnpm dev` | Start the Vite dev server |
|
|
10
10
|
| `pnpm build` | Type-check, then build for production |
|
|
11
11
|
| `pnpm preview` | Preview the production build |
|
|
12
|
+
| `pnpm typecheck` | Type-check the project |
|
|
12
13
|
| `pnpm lint` | Lint with oxlint |
|
|
13
14
|
| `pnpm format` | Format with oxfmt |
|
|
14
15
|
| `pnpm format:check` | Check formatting without writing |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-chanom-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Scaffold a Vite + React + TypeScript app, wired to the chanom shared configs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"dev": "tsdown --watch",
|
|
51
51
|
"test": "vitest run",
|
|
52
52
|
"test:coverage": "vitest run --coverage",
|
|
53
|
-
"test:watch": "vitest"
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"typecheck": "tsc --noEmit"
|
|
54
55
|
}
|
|
55
56
|
}
|