create-koppajs 1.0.0 → 1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/LICENSE +201 -21
  3. package/README.md +169 -131
  4. package/bin/create-koppajs.js +201 -175
  5. package/package.json +54 -34
  6. package/template/AI_CONSTITUTION.md +50 -0
  7. package/template/ARCHITECTURE.md +86 -0
  8. package/template/CHANGELOG.md +34 -0
  9. package/template/CONTRIBUTING.md +92 -0
  10. package/template/DECISION_HIERARCHY.md +32 -0
  11. package/template/DEVELOPMENT_RULES.md +57 -0
  12. package/template/LICENSE +1 -1
  13. package/template/README.md +241 -49
  14. package/template/RELEASE.md +230 -0
  15. package/template/ROADMAP.md +34 -0
  16. package/template/TESTING_STRATEGY.md +93 -0
  17. package/template/_editorconfig +12 -0
  18. package/template/_gitattributes +1 -0
  19. package/template/_github/instructions/ai-workflow.md +33 -0
  20. package/template/_github/workflows/ci.yml +38 -0
  21. package/template/_github/workflows/release.yml +58 -0
  22. package/template/_gitignore +5 -0
  23. package/template/_husky/commit-msg +8 -0
  24. package/template/_husky/pre-commit +1 -0
  25. package/template/_npmrc +1 -0
  26. package/template/_prettierignore +7 -0
  27. package/template/commitlint.config.mjs +6 -0
  28. package/template/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  29. package/template/docs/adr/0002-adopt-a-living-meta-layer.md +30 -0
  30. package/template/docs/adr/0003-normalize-kpa-plugin-output.md +24 -0
  31. package/template/docs/adr/0004-adopt-an-automated-quality-baseline.md +31 -0
  32. package/template/docs/adr/0005-adopt-a-tag-driven-release-baseline.md +45 -0
  33. package/template/docs/adr/0006-adopt-commit-message-conventions.md +39 -0
  34. package/template/docs/adr/README.md +37 -0
  35. package/template/docs/adr/TEMPLATE.md +18 -0
  36. package/template/docs/architecture/module-boundaries.md +48 -0
  37. package/template/docs/meta/maintenance.md +40 -0
  38. package/template/docs/quality/quality-gates.md +39 -0
  39. package/template/docs/specs/README.md +36 -0
  40. package/template/docs/specs/TEMPLATE.md +34 -0
  41. package/template/docs/specs/app-bootstrap.md +46 -0
  42. package/template/docs/specs/counter-component.md +48 -0
  43. package/template/docs/specs/quality-workflow.md +62 -0
  44. package/template/eslint.config.mjs +54 -0
  45. package/template/package.json +57 -6
  46. package/template/playwright.config.ts +31 -0
  47. package/template/pnpm-lock.yaml +3784 -0
  48. package/template/prettier.config.mjs +6 -0
  49. package/template/src/app-view.kpa +35 -36
  50. package/template/src/counter-component.kpa +87 -87
  51. package/template/src/style.css +5 -5
  52. package/template/tests/e2e/app.spec.ts +18 -0
  53. package/template/tests/integration/main-bootstrap.test.ts +33 -0
  54. package/template/tests/unit/normalize-kpa-module-export.test.ts +46 -0
  55. package/template/tsconfig.json +13 -2
  56. package/template/vite.config.d.mts +7 -0
  57. package/template/vite.config.mjs +39 -4
  58. package/template/vitest.config.mjs +19 -0
@@ -1,175 +1,201 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, statSync } from "node:fs";
4
- import { basename, join, dirname, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { createInterface } from "node:readline";
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- const TEMPLATE_DIR = join(__dirname, "..", "template");
11
- const CLI_PKG = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
12
-
13
- // ── Args ────────────────────────────────────────────────────────────
14
-
15
- function parseArgs(argv) {
16
- const raw = argv.slice(2);
17
- return {
18
- help: raw.includes("--help") || raw.includes("-h"),
19
- version: raw.includes("--version") || raw.includes("-v"),
20
- projectName: raw.find((a) => !a.startsWith("-")) || null,
21
- };
22
- }
23
-
24
- // ── Help / Version ──────────────────────────────────────────────────
25
-
26
- function printHelp() {
27
- console.log(`
28
- create-koppajs v${CLI_PKG.version}
29
-
30
- Scaffold a new KoppaJS project.
31
-
32
- Usage:
33
- pnpm create koppajs [project-name]
34
- npm create koppajs [project-name]
35
- npx create-koppajs [project-name]
36
-
37
- Options:
38
- --help, -h Show this help message
39
- --version, -v Show version number
40
-
41
- Example:
42
- pnpm create koppajs my-app
43
- `);
44
- }
45
-
46
- function printVersion() {
47
- console.log(CLI_PKG.version);
48
- }
49
-
50
- // ── Prompt ──────────────────────────────────────────────────────────
51
-
52
- function promptProjectName() {
53
- return new Promise((res, rej) => {
54
- const rl = createInterface({ input: process.stdin, output: process.stdout });
55
- let answered = false;
56
- rl.on("close", () => {
57
- if (!answered) rej(new Error("Input closed before a project name was provided."));
58
- });
59
- rl.question(" Project name: ", (answer) => {
60
- answered = true;
61
- rl.close();
62
- res(answer.trim());
63
- });
64
- });
65
- }
66
-
67
- // ── Validation ──────────────────────────────────────────────────────
68
-
69
- function validateProjectName(name) {
70
- if (!name) {
71
- console.error("\n Error: Project name cannot be empty.\n");
72
- process.exit(1);
73
- }
74
- if (name === "." || name === "..") {
75
- console.error(`\n Error: Invalid project name "${name}".\n`);
76
- process.exit(1);
77
- }
78
- if (name.includes("/") || name.includes("\\")) {
79
- console.error("\n Error: Project name must not contain path separators.\n");
80
- process.exit(1);
81
- }
82
- }
83
-
84
- // ── Target directory ────────────────────────────────────────────────
85
-
86
- function ensureTargetDir(targetPath) {
87
- if (existsSync(targetPath) && readdirSync(targetPath).length > 0) {
88
- console.error(`\n Error: Directory "${basename(targetPath)}" already exists and is not empty.\n`);
89
- process.exit(1);
90
- }
91
- mkdirSync(targetPath, { recursive: true });
92
- }
93
-
94
- // ── Copy ────────────────────────────────────────────────────────────
95
-
96
- // npm excludes .gitignore from published packages — ship as _gitignore
97
- // and rename during scaffolding (same approach as create-vite).
98
- const RENAME_FILES = { _gitignore: ".gitignore" };
99
-
100
- function copyDirRecursive(src, dest) {
101
- mkdirSync(dest, { recursive: true });
102
- for (const entry of readdirSync(src)) {
103
- const srcPath = join(src, entry);
104
- const destName = RENAME_FILES[entry] || entry;
105
- const destPath = join(dest, destName);
106
- if (statSync(srcPath).isDirectory()) {
107
- copyDirRecursive(srcPath, destPath);
108
- } else {
109
- copyFileSync(srcPath, destPath);
110
- }
111
- }
112
- }
113
-
114
- // ── Patch package.json ──────────────────────────────────────────────
115
-
116
- function patchPackageJson(destDir, projectName) {
117
- const pkgPath = join(destDir, "package.json");
118
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
119
- pkg.name = projectName;
120
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
121
- }
122
-
123
- // ── Patch README ────────────────────────────────────────────────────
124
-
125
- function patchReadme(destDir, projectName) {
126
- const readmePath = join(destDir, "README.md");
127
- let content = readFileSync(readmePath, "utf-8");
128
- content = content.replaceAll("__PROJECT_NAME__", projectName);
129
- writeFileSync(readmePath, content);
130
- }
131
-
132
- // ── Final output ────────────────────────────────────────────────────
133
-
134
- function printNextSteps(projectName) {
135
- console.log(" Done! Next steps:\n");
136
- console.log(` cd ${projectName}`);
137
- console.log(" pnpm install");
138
- console.log(" pnpm dev\n");
139
- }
140
-
141
- // ── Main ────────────────────────────────────────────────────────────
142
-
143
- async function main() {
144
- const { help, version, projectName: argName } = parseArgs(process.argv);
145
-
146
- if (help) {
147
- printHelp();
148
- process.exit(0);
149
- }
150
-
151
- if (version) {
152
- printVersion();
153
- process.exit(0);
154
- }
155
-
156
- const projectName = argName || (await promptProjectName());
157
-
158
- validateProjectName(projectName);
159
-
160
- const targetDir = resolve(process.cwd(), projectName);
161
-
162
- ensureTargetDir(targetDir);
163
-
164
- console.log(`\n Scaffolding KoppaJS project: ${projectName}\n`);
165
-
166
- copyDirRecursive(TEMPLATE_DIR, targetDir);
167
- patchPackageJson(targetDir, projectName);
168
- patchReadme(targetDir, projectName);
169
- printNextSteps(projectName);
170
- }
171
-
172
- main().catch((err) => {
173
- console.error(`\n Error: ${err.message}\n`);
174
- process.exit(1);
175
- });
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, statSync } from "node:fs";
4
+ import { basename, join, dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createInterface } from "node:readline";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ export const TEMPLATE_DIR = join(__dirname, "..", "template");
11
+ const CLI_PKG = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
12
+
13
+ // ── Args ────────────────────────────────────────────────────────────
14
+
15
+ export function parseArgs(argv) {
16
+ const raw = argv.slice(2);
17
+ return {
18
+ help: raw.includes("--help") || raw.includes("-h"),
19
+ version: raw.includes("--version") || raw.includes("-v"),
20
+ projectName: raw.find((a) => !a.startsWith("-")) || null,
21
+ };
22
+ }
23
+
24
+ // ── Help / Version ──────────────────────────────────────────────────
25
+
26
+ export function printHelp() {
27
+ console.log(`
28
+ create-koppajs v${CLI_PKG.version}
29
+
30
+ Scaffold a new KoppaJS project.
31
+
32
+ Usage:
33
+ pnpm create koppajs [project-name]
34
+ npm create koppajs [project-name]
35
+ npx create-koppajs [project-name]
36
+
37
+ Options:
38
+ --help, -h Show this help message
39
+ --version, -v Show version number
40
+
41
+ Example:
42
+ pnpm create koppajs my-app
43
+ `);
44
+ }
45
+
46
+ export function printVersion() {
47
+ console.log(CLI_PKG.version);
48
+ }
49
+
50
+ // ── Prompt ──────────────────────────────────────────────────────────
51
+
52
+ export function promptProjectName() {
53
+ return new Promise((res, rej) => {
54
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
55
+ let answered = false;
56
+ rl.on("close", () => {
57
+ if (!answered) rej(new Error("Input closed before a project name was provided."));
58
+ });
59
+ rl.question(" Project name: ", (answer) => {
60
+ answered = true;
61
+ rl.close();
62
+ res(answer.trim());
63
+ });
64
+ });
65
+ }
66
+
67
+ // ── Validation ──────────────────────────────────────────────────────
68
+
69
+ export function validateProjectName(name) {
70
+ if (!name) {
71
+ throw new Error("Project name cannot be empty.");
72
+ }
73
+ if (name === "." || name === "..") {
74
+ throw new Error(`Invalid project name "${name}".`);
75
+ }
76
+ if (name.includes("/") || name.includes("\\")) {
77
+ throw new Error("Project name must not contain path separators.");
78
+ }
79
+ }
80
+
81
+ // ── Target directory ────────────────────────────────────────────────
82
+
83
+ export function ensureTargetDir(targetPath) {
84
+ if (existsSync(targetPath) && readdirSync(targetPath).length > 0) {
85
+ throw new Error(`Directory "${basename(targetPath)}" already exists and is not empty.`);
86
+ }
87
+ mkdirSync(targetPath, { recursive: true });
88
+ }
89
+
90
+ // ── Copy ────────────────────────────────────────────────────────────
91
+
92
+ // npm excludes .gitignore from published packages — ship as _gitignore
93
+ // and rename during scaffolding (same approach as create-vite).
94
+ const RENAME_FILES = {
95
+ _editorconfig: ".editorconfig",
96
+ _gitattributes: ".gitattributes",
97
+ _github: ".github",
98
+ _gitignore: ".gitignore",
99
+ _husky: ".husky",
100
+ _npmrc: ".npmrc",
101
+ _prettierignore: ".prettierignore",
102
+ };
103
+
104
+ export function copyDirRecursive(src, dest) {
105
+ mkdirSync(dest, { recursive: true });
106
+ for (const entry of readdirSync(src)) {
107
+ const srcPath = join(src, entry);
108
+ const destName = RENAME_FILES[entry] || entry;
109
+ const destPath = join(dest, destName);
110
+ if (statSync(srcPath).isDirectory()) {
111
+ copyDirRecursive(srcPath, destPath);
112
+ } else {
113
+ copyFileSync(srcPath, destPath);
114
+ }
115
+ }
116
+ }
117
+
118
+ // ── Patch package.json ──────────────────────────────────────────────
119
+
120
+ export function patchPackageJson(destDir, projectName) {
121
+ const pkgPath = join(destDir, "package.json");
122
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
123
+ pkg.name = projectName;
124
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
125
+ }
126
+
127
+ // ── Patch README ────────────────────────────────────────────────────
128
+
129
+ function patchTextFile(destDir, relativePath, projectName) {
130
+ const filePath = join(destDir, relativePath);
131
+ let content = readFileSync(filePath, "utf-8");
132
+ content = content.replaceAll("__PROJECT_NAME__", projectName);
133
+ writeFileSync(filePath, content);
134
+ }
135
+
136
+ export function patchReadme(destDir, projectName) {
137
+ patchTextFile(destDir, "README.md", projectName);
138
+ }
139
+
140
+ export function patchChangelog(destDir, projectName) {
141
+ patchTextFile(destDir, "CHANGELOG.md", projectName);
142
+ }
143
+
144
+ export function patchReleaseNotes(destDir, projectName) {
145
+ patchTextFile(destDir, "RELEASE.md", projectName);
146
+ }
147
+
148
+ // ── Final output ────────────────────────────────────────────────────
149
+
150
+ export function printNextSteps(projectName) {
151
+ console.log(" Done! Next steps:\n");
152
+ console.log(` cd ${projectName}`);
153
+ console.log(" pnpm install");
154
+ console.log(" pnpm dev\n");
155
+ }
156
+
157
+ // ── Main ────────────────────────────────────────────────────────────
158
+
159
+ export async function runCli(argv = process.argv, cwd = process.cwd()) {
160
+ const { help, version, projectName: argName } = parseArgs(argv);
161
+
162
+ if (help) {
163
+ printHelp();
164
+ return 0;
165
+ }
166
+
167
+ if (version) {
168
+ printVersion();
169
+ return 0;
170
+ }
171
+
172
+ const projectName = argName || (await promptProjectName());
173
+
174
+ validateProjectName(projectName);
175
+
176
+ const targetDir = resolve(cwd, projectName);
177
+
178
+ ensureTargetDir(targetDir);
179
+
180
+ console.log(`\n Scaffolding KoppaJS project: ${projectName}\n`);
181
+
182
+ copyDirRecursive(TEMPLATE_DIR, targetDir);
183
+ patchPackageJson(targetDir, projectName);
184
+ patchReadme(targetDir, projectName);
185
+ patchChangelog(targetDir, projectName);
186
+ patchReleaseNotes(targetDir, projectName);
187
+ printNextSteps(projectName);
188
+
189
+ return 0;
190
+ }
191
+
192
+ function isDirectExecution() {
193
+ return Boolean(process.argv[1]) && resolve(process.argv[1]) === __filename;
194
+ }
195
+
196
+ if (isDirectExecution()) {
197
+ runCli().catch((err) => {
198
+ console.error(`\n Error: ${err.message}\n`);
199
+ process.exit(1);
200
+ });
201
+ }
package/package.json CHANGED
@@ -1,34 +1,54 @@
1
- {
2
- "name": "create-koppajs",
3
- "version": "1.0.0",
4
- "private": false,
5
- "type": "module",
6
- "description": "Scaffold a new KoppaJS project in seconds.",
7
- "bin": {
8
- "create-koppajs": "./bin/create-koppajs.js"
9
- },
10
- "files": [
11
- "bin",
12
- "template",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "engines": {
17
- "node": ">=20"
18
- },
19
- "scripts": {
20
- "lint": "node -c bin/create-koppajs.js",
21
- "test": "node ./bin/create-koppajs.js --help",
22
- "smoke": "node scripts/smoke-test.mjs"
23
- },
24
- "author": "Bastian Bensch",
25
- "license": "MIT",
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/koppajs/create-koppajs"
29
- },
30
- "homepage": "https://github.com/koppajs/create-koppajs#readme",
31
- "bugs": {
32
- "url": "https://github.com/koppajs/create-koppajs/issues"
33
- }
34
- }
1
+ {
2
+ "name": "create-koppajs",
3
+ "version": "1.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Scaffold a new KoppaJS project in seconds.",
7
+ "bin": {
8
+ "create-koppajs": "./bin/create-koppajs.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "template",
13
+ "CHANGELOG.md",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "packageManager": "pnpm@10.12.1",
18
+ "engines": {
19
+ "node": ">=20",
20
+ "pnpm": ">=10"
21
+ },
22
+ "scripts": {
23
+ "check:meta": "node scripts/check-meta-layer.mjs",
24
+ "lint": "node scripts/lint.mjs",
25
+ "format:check": "node scripts/format-check.mjs",
26
+ "check:cli": "node ./bin/create-koppajs.js --help && node ./bin/create-koppajs.js --version",
27
+ "test:unit": "node scripts/run-unit-tests.mjs",
28
+ "test:watch": "node scripts/run-unit-tests.mjs --watch",
29
+ "test:smoke": "node scripts/smoke-test.mjs",
30
+ "test:template-build": "node scripts/template-build-test.mjs",
31
+ "test": "npm run test:unit && npm run test:smoke",
32
+ "pack:dry-run": "npm pack --dry-run",
33
+ "check": "npm run check:meta && npm run lint && npm run format:check && npm run check:cli && npm test && npm run pack:dry-run",
34
+ "release:check": "npm run check && npm run test:template-build",
35
+ "prepare": "husky",
36
+ "clean": "node scripts/clean.mjs"
37
+ },
38
+ "author": "Bastian Bensch",
39
+ "license": "Apache-2.0",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/koppajs/create-koppajs"
43
+ },
44
+ "homepage": "https://github.com/koppajs/create-koppajs#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/koppajs/create-koppajs/issues"
47
+ },
48
+ "devDependencies": {
49
+ "@commitlint/cli": "^20.1.0",
50
+ "@commitlint/config-conventional": "^20.0.0",
51
+ "husky": "^9.1.7",
52
+ "lint-staged": "^16.3.3"
53
+ }
54
+ }
@@ -0,0 +1,50 @@
1
+ # AI Constitution
2
+
3
+ ## Purpose
4
+
5
+ This repository is a KoppaJS starter scaffolded from the official minimal Vite
6
+ and TypeScript baseline. Its job is to stay small, understandable, runnable,
7
+ and trustworthy.
8
+
9
+ ## Core principles
10
+
11
+ - Optimize for clarity over feature breadth.
12
+ - Keep the bootstrap path obvious: one HTML shell, one TypeScript entrypoint, one root view.
13
+ - Prefer published npm packages over local `file:` links in this repository.
14
+ - Keep the starter close to real production usage, but do not turn it into a framework showcase.
15
+ - Treat the meta layer as part of the product. If architecture or workflow changes, the documentation must change in the same update.
16
+
17
+ ## Architectural philosophy
18
+
19
+ - `index.html` is the static document shell only.
20
+ - `src/main.ts` is the only bootstrap module and the only place that registers root-level components with `Core.take(...)`.
21
+ - `.kpa` files are the primary unit for simple UI composition, local state, and component-scoped styling.
22
+ - When logic becomes reusable, branch-heavy, or worth unit testing, extract it from `.kpa` blocks into `.ts` modules.
23
+ - Global CSS stays minimal. Component visuals belong inside component-local CSS blocks unless they are true application-wide primitives.
24
+
25
+ ## Collaboration rules for humans and AI
26
+
27
+ 1. Read [DECISION_HIERARCHY.md](./DECISION_HIERARCHY.md), [ARCHITECTURE.md](./ARCHITECTURE.md), [DEVELOPMENT_RULES.md](./DEVELOPMENT_RULES.md), [TESTING_STRATEGY.md](./TESTING_STRATEGY.md), and any relevant spec or ADR before editing code.
28
+ 2. Follow `spec -> quality plan -> implementation -> documentation` for any non-trivial change.
29
+ 3. Do not silently change public component tags, bootstrap flow, dependency sourcing, or the starter's setup instructions.
30
+ 4. Prefer existing repository patterns over inventing new structure.
31
+ 5. If a change affects architecture, module boundaries, build tooling, testing, or contributor workflow, update the meta layer in the same change.
32
+ 6. Record durable architectural decisions in `docs/adr/`.
33
+ 7. If code and documentation disagree, resolve the mismatch before merging further changes.
34
+
35
+ ## Change triggers
36
+
37
+ - New subsystem or folder: update [ARCHITECTURE.md](./ARCHITECTURE.md) and [docs/architecture/module-boundaries.md](./docs/architecture/module-boundaries.md).
38
+ - New enduring technical decision: add or update an ADR in [docs/adr](./docs/adr).
39
+ - New development convention: update [DEVELOPMENT_RULES.md](./DEVELOPMENT_RULES.md).
40
+ - New quality gate or test level: update [TESTING_STRATEGY.md](./TESTING_STRATEGY.md) and [docs/quality/quality-gates.md](./docs/quality/quality-gates.md).
41
+ - New user-visible capability: create or update a spec in [docs/specs](./docs/specs).
42
+
43
+ ## Definition of done
44
+
45
+ A change is not complete until:
46
+
47
+ - the code or docs are aligned with the actual repository state,
48
+ - applicable specs and ADRs are updated,
49
+ - required quality checks have run,
50
+ - contributor-facing guidance still matches the project.
@@ -0,0 +1,86 @@
1
+ # Architecture
2
+
3
+ This repository is a minimal KoppaJS application starter. It intentionally demonstrates a small end-to-end path: HTML shell, TypeScript bootstrap, a root KoppaJS view, and a single interactive child component. The repository also carries a small quality and release automation layer so the starter stays runnable, trustworthy, and maintainable as it evolves.
4
+
5
+ ## System overview
6
+
7
+ - `index.html` provides the document shell, loads global CSS, declares the `<app-view>` root element, and imports the TypeScript entrypoint.
8
+ - `src/main.ts` imports `@koppajs/koppajs-core`, registers local components with `Core.take(...)`, and invokes `Core()` exactly once to bootstrap the app.
9
+ - `src/app-view.kpa` is the root UI shell. It arranges the page and composes the interactive example component.
10
+ - `src/counter-component.kpa` is the example stateful leaf component. It owns its own `count` state and button event handlers.
11
+ - `src/style.css` contains the global reset only.
12
+ - `public/` contains static assets referenced by the HTML shell or components.
13
+ - `vite.config.mjs` wires the KoppaJS Vite plugin into the build and applies a small repo-local compatibility transform so `.kpa` files are emitted as valid ES modules.
14
+ - `vitest.config.mjs` reuses the Vite config so unit and integration tests exercise the same `.kpa` loading path as the application.
15
+ - `playwright.config.ts` runs a Chromium smoke test against `vite preview`, which keeps the minimal UI starter covered by one real browser path.
16
+ - `CHANGELOG.md` records official tagged milestones for the repository.
17
+ - `RELEASE.md` documents the maintainer release path across `develop`, `release/*`, and `main`.
18
+ - `commitlint.config.mjs` defines the repository's commit message convention.
19
+ - `.github/workflows/release.yml` reruns validation for `vX.Y.Z` tags, checks version alignment, and creates GitHub Releases.
20
+ - `tests/unit/` covers isolated logic such as the `.kpa` module export normalization helper.
21
+ - `tests/integration/` covers application bootstrap wiring without requiring a full browser run.
22
+ - `tests/e2e/` covers the observable starter flow in a real browser.
23
+ - `tsconfig.json` enforces strict TypeScript rules for source, tests, and TypeScript config files. `.kpa` compilation is still handled by the KoppaJS Vite plugin.
24
+
25
+ More detailed boundaries live in [docs/architecture/module-boundaries.md](./docs/architecture/module-boundaries.md).
26
+
27
+ ## Runtime flow
28
+
29
+ 1. The browser loads [`index.html`](./index.html).
30
+ 2. The document loads [`src/style.css`](./src/style.css) and [`src/main.ts`](./src/main.ts).
31
+ 3. [`src/main.ts`](./src/main.ts) registers `app-view` and `counter-component` with KoppaJS Core, then calls `Core()`.
32
+ 4. KoppaJS instantiates `<app-view>`.
33
+ 5. [`src/app-view.kpa`](./src/app-view.kpa) renders the starter shell and nests `<counter-component>`.
34
+ 6. [`src/counter-component.kpa`](./src/counter-component.kpa) handles button clicks, mutates local state, and re-renders the displayed value.
35
+
36
+ ## Quality automation layer
37
+
38
+ - `eslint.config.mjs` lint-checks TypeScript source plus the TypeScript- and JavaScript-based tooling files.
39
+ - `prettier.config.mjs` and `.editorconfig` keep supported text files consistent without introducing formatter-specific rules into ESLint.
40
+ - `.npmrc` enforces the declared engine floor during installs.
41
+ - `.husky/pre-commit` runs `lint-staged` so staged files get a fast local quality pass before commit.
42
+ - `.husky/commit-msg` validates commit headers with `commitlint`.
43
+ - `.github/workflows/ci.yml` runs the full repository validation flow on GitHub.
44
+ - `.github/workflows/release.yml` runs tagged release validation and creates GitHub Releases for matching versions.
45
+ - Stylelint is intentionally absent for now because the important styles live inside `.kpa` blocks and the repository does not yet have a low-friction, first-class way to lint those embedded blocks without giving a misleading sense of coverage.
46
+
47
+ ## Module responsibilities
48
+
49
+ | Module | Responsibility | Must not do |
50
+ | ------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
51
+ | `index.html` | Declare root tag and static assets | Hold business logic or feature wiring |
52
+ | `CHANGELOG.md` | Record official tagged release notes | Become a scratchpad for speculative work or duplicate repository docs |
53
+ | `RELEASE.md` | Define the maintainer release procedure | Drift away from actual branch, tag, or workflow behavior |
54
+ | `commitlint.config.mjs` | Define commit message validation rules | Expand into unrelated repository lint policy |
55
+ | `src/main.ts` | Bootstrap and component registration | Accumulate feature logic, state, or DOM manipulation |
56
+ | `src/app-view.kpa` | Root layout and composition | Own unrelated business workflows or register components |
57
+ | `src/counter-component.kpa` | Local interactive example state | Reach into global DOM or mutate app shell concerns |
58
+ | `src/style.css` | Global reset/base rules | Contain feature-specific visuals that belong to components |
59
+ | `public/` | Static assets | Store generated build output or source code |
60
+ | `tests/unit/` | Isolated tests for helper or tooling logic | Production runtime side effects or browser-only expectations |
61
+ | `tests/integration/` | Wiring tests across bootstrap boundaries | Full browser assertions already covered by Playwright |
62
+ | `tests/e2e/` | Real browser smoke coverage of the starter UI | Deep implementation-detail assertions or brittle CSS-driven flows |
63
+ | `vite.config.mjs` | Build and dev server integration, including the `.kpa` module export compatibility wrapper | Application runtime logic |
64
+ | `vitest.config.mjs` | Unit and integration test runner configuration | Runtime feature code |
65
+ | `playwright.config.ts` | Browser smoke-test orchestration against preview output | Application runtime logic |
66
+ | `.github/workflows/release.yml` | Tagged release validation and GitHub Release creation | Publish to npm while the repository remains private |
67
+
68
+ ## Invariants
69
+
70
+ - There is exactly one application bootstrap path.
71
+ - The root tag in `index.html` must match a component registered in `src/main.ts`.
72
+ - `Core()` is called once from `src/main.ts`.
73
+ - Official releases require matching versions across `package.json`, `CHANGELOG.md`, and `vX.Y.Z` tags.
74
+ - Maintainer commits are expected to follow Conventional Commits.
75
+ - No routing, persistence, network calls, or global client-side state are part of the baseline starter.
76
+ - Stateful behavior remains local unless a documented new subsystem justifies centralization.
77
+ - `pnpm check` remains fast enough for routine local use.
78
+ - Playwright coverage stays focused on critical smoke coverage unless the UI grows into a broader workflow surface.
79
+ - The repository remains `private` until an explicit decision changes the release target.
80
+
81
+ ## Extension guidance
82
+
83
+ - Add new components under `src/` and compose them from `app-view.kpa` or other components.
84
+ - Extract shared logic into `.ts` modules when it becomes reusable or deserves focused testing.
85
+ - Add a spec before introducing new user-visible behavior.
86
+ - Add an ADR before introducing architecture-changing capabilities such as routing, data fetching, persistence, state containers, SSR, or a new testing stack.
@@ -0,0 +1,34 @@
1
+ # Change Log
2
+
3
+ All notable changes to **__PROJECT_NAME__** are documented in this file.
4
+
5
+ This project uses a manual, tag-driven release process.
6
+ Only tagged versions represent official releases.
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ This section is intentionally empty.
13
+
14
+ Changes will only appear here when they:
15
+
16
+ - alter the project's observable behavior,
17
+ - change contributor or maintainer workflow,
18
+ - or modify documented repository guarantees.
19
+
20
+ ---
21
+
22
+ ## [1.0.0] — Initial Scaffold Baseline
23
+
24
+ **2026-03-17**
25
+
26
+ Initial scaffold created from the current official KoppaJS starter baseline.
27
+
28
+ ### Included
29
+
30
+ - a minimal KoppaJS app shell with `app-view` and `counter-component`
31
+ - bootstrap through `Core.take(...)` and `Core()`
32
+ - ESLint, Prettier, Vitest, Playwright, Husky, and GitHub Actions CI
33
+ - explicit architecture, ADR, spec, testing, and contributor documentation
34
+ - a tag-driven GitHub release workflow for repository snapshots