create-koppajs 1.1.0 → 1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +119 -122
- package/bin/create-koppajs.js +158 -13
- package/package.json +2 -1
- package/template-overlays/router/ARCHITECTURE.md +86 -0
- package/template-overlays/router/CHANGELOG.md +44 -0
- package/template-overlays/router/DEVELOPMENT_RULES.md +57 -0
- package/template-overlays/router/README.md +243 -0
- package/template-overlays/router/ROADMAP.md +34 -0
- package/template-overlays/router/TESTING_STRATEGY.md +67 -0
- package/template-overlays/router/docs/adr/0001-keep-the-starter-minimal.md +32 -0
- package/template-overlays/router/docs/architecture/module-boundaries.md +39 -0
- package/template-overlays/router/docs/meta/maintenance.md +38 -0
- package/template-overlays/router/docs/specs/README.md +19 -0
- package/template-overlays/router/docs/specs/app-bootstrap.md +42 -0
- package/template-overlays/router/docs/specs/router-navigation.md +41 -0
- package/template-overlays/router/index.html +14 -0
- package/template-overlays/router/package.json +74 -0
- package/template-overlays/router/pnpm-lock.yaml +3793 -0
- package/template-overlays/router/src/app-view.kpa +128 -0
- package/template-overlays/router/src/home-page.kpa +100 -0
- package/template-overlays/router/src/main.ts +89 -0
- package/template-overlays/router/src/not-found-page.kpa +69 -0
- package/template-overlays/router/src/router-page.kpa +102 -0
- package/template-overlays/router/src/style.css +51 -0
- package/template-overlays/router/tests/e2e/app.spec.ts +38 -0
- package/template-overlays/router/tests/integration/main-bootstrap.test.ts +150 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,28 @@ _No unreleased changes yet._
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [1.2.0] — Starter Variants And Router Overlay
|
|
20
|
+
|
|
21
|
+
**2026-03-26**
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- added an opt-in `router` starter that layers `@koppajs/koppajs-router`, a
|
|
26
|
+
two-page navigation flow, and an explicit fallback route on top of the
|
|
27
|
+
minimal baseline
|
|
28
|
+
- added overlay-based starter support through `template-overlays/` so future
|
|
29
|
+
starter variants can replace only the files that actually differ
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- extended the CLI contract with `--template` and `--router`, plus interactive
|
|
34
|
+
starter selection in TTY runs when no template flag is provided
|
|
35
|
+
- expanded smoke, unit, and generated-template build coverage to validate both
|
|
36
|
+
the default minimal starter and the opt-in router starter
|
|
37
|
+
- updated architecture docs, specs, and ADRs to reflect multi-starter support
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
19
41
|
## [1.1.0] — Starter & Release Baseline Upgrade
|
|
20
42
|
|
|
21
43
|
**2026-03-17**
|
package/README.md
CHANGED
|
@@ -1,71 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
# create-koppajs
|
|
2
|
+
|
|
3
|
+
`create-koppajs` is the official KoppaJS CLI scaffolder. It creates a new
|
|
4
|
+
project by copying the versioned base starter in `template/`, optionally
|
|
5
|
+
applying a supported starter overlay from `template-overlays/`, and patching a
|
|
6
|
+
small, explicit set of identity files.
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
This repository exists to do one job well:
|
|
11
|
+
|
|
12
|
+
- create a fresh project directory
|
|
13
|
+
- copy the current supported KoppaJS starter baseline
|
|
14
|
+
- optionally add a supported starter variant such as `router`
|
|
15
|
+
- preserve a stable, inspectable bootstrap path for new KoppaJS applications
|
|
16
|
+
|
|
17
|
+
It is not a runtime package and it does not own application behavior after
|
|
18
|
+
generation.
|
|
19
|
+
|
|
20
|
+
## Repository Classification
|
|
21
|
+
|
|
22
|
+
- Repo type: CLI scaffolding package with a bundled starter family
|
|
23
|
+
- Runtime responsibility: one-shot filesystem scaffolding through
|
|
24
|
+
`bin/create-koppajs.js`
|
|
25
|
+
- Build-time responsibility: publish the starter assets, protect the contract,
|
|
26
|
+
and validate tagged releases
|
|
27
|
+
- UI surface: none at the repository root; the generated starter owns the UI
|
|
28
|
+
- Maturity level: stable, contract-governed, maintenance-first
|
|
29
|
+
|
|
30
|
+
## Ownership Boundaries
|
|
31
|
+
|
|
32
|
+
- `bin/create-koppajs.js` owns argument parsing, prompting, validation, starter
|
|
33
|
+
selection, template copy, placeholder patching, and next-step output.
|
|
34
|
+
- `template/` owns the default `minimal` starter baseline.
|
|
35
|
+
- `template-overlays/` owns the files that differ for opt-in starter variants.
|
|
36
|
+
- `scripts/` and `.github/workflows/` own repository-quality and release
|
|
37
|
+
verification.
|
|
38
|
+
- Root governance files own the repository doctrine and must stay aligned with
|
|
39
|
+
code and workflows.
|
|
40
|
+
|
|
41
|
+
The root package must not take on runtime concerns that belong in generated
|
|
42
|
+
applications, and generated applications must not depend on unpublished root
|
|
43
|
+
files after scaffold completion.
|
|
44
|
+
|
|
45
|
+
## Public Contract
|
|
46
|
+
|
|
47
|
+
The stable public contract of this repository is:
|
|
48
|
+
|
|
49
|
+
- the `create-koppajs` command and its `--help` / `--version` flags
|
|
50
|
+
- the optional project-name argument and prompt fallback when omitted
|
|
51
|
+
- the optional `--template <name>` and `--router` starter-selection flags
|
|
52
|
+
- the interactive starter-template prompt when no template flag is provided in
|
|
53
|
+
an interactive terminal
|
|
54
|
+
- rejection of invalid project names, invalid template names, and non-empty
|
|
55
|
+
target directories
|
|
56
|
+
- recursive copying of the bundled `template/` directory plus any selected
|
|
57
|
+
overlay
|
|
58
|
+
- restoration of publish-safe dotfiles and dotdirectories during copy
|
|
59
|
+
- patching of generated `package.json`, `README.md`, `CHANGELOG.md`, and
|
|
60
|
+
`RELEASE.md`
|
|
61
|
+
- the generated starter baselines defined by `template/` and
|
|
62
|
+
`template-overlays/`
|
|
63
|
+
- the npm package payload: `bin/`, `template/`, `template-overlays/`,
|
|
64
|
+
`README.md`, `CHANGELOG.md`, and `LICENSE`
|
|
65
|
+
|
|
66
|
+
The governing specs for that contract are:
|
|
67
|
+
|
|
68
|
+
- [docs/specs/cli-scaffolding.md](./docs/specs/cli-scaffolding.md)
|
|
69
|
+
- [docs/specs/template-starter-contract.md](./docs/specs/template-starter-contract.md)
|
|
2
70
|
|
|
3
|
-
|
|
4
|
-
<img src="https://public-assets-1b57ca06-687a-4142-a525-0635f7649a5c.s3.eu-central-1.amazonaws.com/koppajs/koppajs-logo-text-900x226.png" width="500" alt="KoppaJS Logo">
|
|
5
|
-
</div>
|
|
6
|
-
|
|
7
|
-
<br>
|
|
8
|
-
|
|
9
|
-
<div align="center">
|
|
10
|
-
<a href="https://www.npmjs.com/package/create-koppajs"><img src="https://img.shields.io/npm/v/create-koppajs?style=flat-square" alt="npm version"></a>
|
|
11
|
-
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License"></a>
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
<br>
|
|
15
|
-
|
|
16
|
-
<div align="center">
|
|
17
|
-
<h1 align="center">create-koppajs</h1>
|
|
18
|
-
<h3 align="center">Scaffold a new KoppaJS project in seconds</h3>
|
|
19
|
-
<p align="center">
|
|
20
|
-
<i>The fastest way to start building with KoppaJS.</i>
|
|
21
|
-
</p>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<br>
|
|
25
|
-
|
|
26
|
-
<div align="center">
|
|
27
|
-
<p align="center">
|
|
28
|
-
<a href="https://github.com/koppajs/koppajs-documentation">Documentation</a>
|
|
29
|
-
·
|
|
30
|
-
<a href="https://github.com/koppajs/koppajs-core">KoppaJS Core</a>
|
|
31
|
-
·
|
|
32
|
-
<a href="https://github.com/koppajs/koppajs-vite-plugin">Vite Plugin</a>
|
|
33
|
-
·
|
|
34
|
-
<a href="https://github.com/koppajs/create-koppajs/issues">Issues</a>
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<br>
|
|
39
|
-
|
|
40
|
-
<details>
|
|
41
|
-
<summary>Table of Contents</summary>
|
|
42
|
-
<ol>
|
|
43
|
-
<li><a href="#what-is-this">What is this?</a></li>
|
|
44
|
-
<li><a href="#usage">Usage</a></li>
|
|
45
|
-
<li><a href="#what-gets-generated">What gets generated</a></li>
|
|
46
|
-
<li><a href="#requirements">Requirements</a></li>
|
|
47
|
-
<li><a href="#release--governance">Release & Governance</a></li>
|
|
48
|
-
<li><a href="#community--contribution">Community & Contribution</a></li>
|
|
49
|
-
<li><a href="#license">License</a></li>
|
|
50
|
-
</ol>
|
|
51
|
-
</details>
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## What is this?
|
|
56
|
-
|
|
57
|
-
`create-koppajs` is the **official project scaffolder** for KoppaJS.
|
|
71
|
+
## Usage
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
Default starter:
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
```bash
|
|
76
|
+
pnpm create koppajs my-app
|
|
77
|
+
```
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
Router starter:
|
|
64
80
|
|
|
65
81
|
```bash
|
|
66
|
-
pnpm create koppajs my-app
|
|
82
|
+
pnpm create koppajs my-app --template router
|
|
67
83
|
```
|
|
68
84
|
|
|
85
|
+
Alternative entrypoints:
|
|
86
|
+
|
|
69
87
|
```bash
|
|
70
88
|
npm create koppajs my-app
|
|
71
89
|
```
|
|
@@ -74,7 +92,11 @@ npm create koppajs my-app
|
|
|
74
92
|
npx create-koppajs my-app
|
|
75
93
|
```
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
If the target directory name is omitted, the CLI prompts for one. If no
|
|
96
|
+
template flag is provided in an interactive terminal, the CLI also prompts for
|
|
97
|
+
starter selection. Non-interactive runs default to `minimal`.
|
|
98
|
+
|
|
99
|
+
After generation:
|
|
78
100
|
|
|
79
101
|
```bash
|
|
80
102
|
cd my-app
|
|
@@ -82,88 +104,63 @@ pnpm install
|
|
|
82
104
|
pnpm dev
|
|
83
105
|
```
|
|
84
106
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## What gets generated
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
my-app/
|
|
93
|
-
├── .github/
|
|
94
|
-
├── .husky/
|
|
95
|
-
├── docs/
|
|
96
|
-
├── tests/
|
|
97
|
-
├── CHANGELOG.md
|
|
98
|
-
├── RELEASE.md
|
|
99
|
-
├── AI_CONSTITUTION.md
|
|
100
|
-
├── ARCHITECTURE.md
|
|
101
|
-
├── package.json
|
|
102
|
-
├── .gitignore
|
|
103
|
-
├── README.md
|
|
104
|
-
├── vite.config.mjs
|
|
105
|
-
├── vitest.config.mjs
|
|
106
|
-
├── playwright.config.ts
|
|
107
|
-
├── tsconfig.json
|
|
108
|
-
├── pnpm-lock.yaml
|
|
109
|
-
├── LICENSE
|
|
110
|
-
├── public/
|
|
111
|
-
│ └── favicon.svg
|
|
112
|
-
└── src/
|
|
113
|
-
├── main.ts
|
|
114
|
-
├── style.css
|
|
115
|
-
├── app-view.kpa
|
|
116
|
-
└── counter-component.kpa
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
- **Vite** as dev server and bundler
|
|
120
|
-
- **TypeScript**, **ESLint**, **Prettier**, **Vitest**, and **Playwright**
|
|
121
|
-
- **GitHub workflows**, **Husky**, **lint-staged**, and **Conventional Commits**
|
|
122
|
-
- **Meta-layer docs**, ADRs, specs, and a release baseline
|
|
123
|
-
- **Two sample components** (app view + counter) aligned with the current
|
|
124
|
-
`koppajs-example` starter
|
|
107
|
+
## Requirements
|
|
125
108
|
|
|
126
|
-
|
|
109
|
+
- for `create-koppajs`: Node.js `>=20`
|
|
110
|
+
- for generated starter projects: pnpm `>=10` and a starter-supported Node.js
|
|
111
|
+
line, currently `20.19+`, `22.13+`, or `24+`
|
|
127
112
|
|
|
128
|
-
##
|
|
113
|
+
## Generated Starters
|
|
129
114
|
|
|
130
|
-
|
|
131
|
-
- Generated starter workflow: pnpm >= 10 plus a starter-supported Node.js line
|
|
132
|
-
(currently 20.19+, 22.13+, or 24+)
|
|
115
|
+
The generated project includes one of two supported starters:
|
|
133
116
|
|
|
134
|
-
|
|
117
|
+
- `minimal` by default: a small KoppaJS application built on Vite and
|
|
118
|
+
TypeScript
|
|
119
|
+
- `router` as opt-in: the same baseline plus `@koppajs/koppajs-router`, a
|
|
120
|
+
simple two-page navigation flow, and an explicit fallback route
|
|
135
121
|
|
|
136
|
-
|
|
122
|
+
Every starter also includes:
|
|
137
123
|
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
- quality tooling through ESLint, Prettier, Vitest, and Playwright
|
|
125
|
+
- local workflow guards through Husky, lint-staged, and commitlint
|
|
126
|
+
- starter governance files, ADR/spec structure, and release-process documents
|
|
127
|
+
- GitHub workflows for CI and tagged releases
|
|
140
128
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- Conventional Commit enforcement via `commitlint`
|
|
144
|
-
- a generated-project CI/release baseline under `.github/workflows`
|
|
129
|
+
The root repository treats those starters as versioned product surface, not
|
|
130
|
+
test data.
|
|
145
131
|
|
|
146
|
-
|
|
132
|
+
## Ecosystem Fit
|
|
147
133
|
|
|
148
|
-
|
|
134
|
+
`create-koppajs` is the canonical entry point for starting a new KoppaJS
|
|
135
|
+
application. It complements:
|
|
149
136
|
|
|
150
|
-
|
|
137
|
+
- `@koppajs/koppajs-core` for runtime behavior
|
|
138
|
+
- `@koppajs/koppajs-router` for optional route orchestration in scaffolded apps
|
|
139
|
+
- `@koppajs/koppajs-vite-plugin` for build integration
|
|
140
|
+
- the maintained KoppaJS starter conventions reflected in `template/` and
|
|
141
|
+
`template-overlays/`
|
|
151
142
|
|
|
152
|
-
|
|
143
|
+
The repository stays intentionally narrow so the CLI, starter contract, and
|
|
144
|
+
governance baseline can evolve together without hidden behavior.
|
|
153
145
|
|
|
154
|
-
## Governance
|
|
146
|
+
## Governance
|
|
155
147
|
|
|
156
|
-
|
|
148
|
+
The root meta layer defines how this repository changes:
|
|
157
149
|
|
|
158
150
|
- [AI_CONSTITUTION.md](./AI_CONSTITUTION.md)
|
|
159
151
|
- [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
152
|
+
- [DECISION_HIERARCHY.md](./DECISION_HIERARCHY.md)
|
|
160
153
|
- [DEVELOPMENT_RULES.md](./DEVELOPMENT_RULES.md)
|
|
161
154
|
- [TESTING_STRATEGY.md](./TESTING_STRATEGY.md)
|
|
162
155
|
- [RELEASE.md](./RELEASE.md)
|
|
156
|
+
- [ROADMAP.md](./ROADMAP.md)
|
|
163
157
|
- [docs/meta/README.md](./docs/meta/README.md)
|
|
158
|
+
- [docs/architecture/README.md](./docs/architecture/README.md)
|
|
159
|
+
- [docs/quality/README.md](./docs/quality/README.md)
|
|
164
160
|
|
|
165
|
-
|
|
161
|
+
Tagged releases are documented in [CHANGELOG.md](./CHANGELOG.md). Contributor
|
|
162
|
+
workflow rules live in [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
166
163
|
|
|
167
164
|
## License
|
|
168
165
|
|
|
169
|
-
Apache
|
|
166
|
+
Apache License 2.0 — © 2026 KoppaJS, Bastian Bensch
|
package/bin/create-koppajs.js
CHANGED
|
@@ -8,17 +8,90 @@ import { createInterface } from "node:readline";
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
10
|
export const TEMPLATE_DIR = join(__dirname, "..", "template");
|
|
11
|
+
export const TEMPLATE_OVERLAY_DIRS = Object.freeze({
|
|
12
|
+
minimal: null,
|
|
13
|
+
router: join(__dirname, "..", "template-overlays", "router"),
|
|
14
|
+
});
|
|
15
|
+
export const DEFAULT_TEMPLATE = "minimal";
|
|
11
16
|
const CLI_PKG = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
12
17
|
|
|
13
18
|
// ── Args ────────────────────────────────────────────────────────────
|
|
14
19
|
|
|
15
20
|
export function parseArgs(argv) {
|
|
16
21
|
const raw = argv.slice(2);
|
|
17
|
-
|
|
22
|
+
const parsed = {
|
|
18
23
|
help: raw.includes("--help") || raw.includes("-h"),
|
|
19
24
|
version: raw.includes("--version") || raw.includes("-v"),
|
|
20
|
-
projectName:
|
|
25
|
+
projectName: null,
|
|
26
|
+
templateName: null,
|
|
27
|
+
optionError: null,
|
|
21
28
|
};
|
|
29
|
+
|
|
30
|
+
const setTemplateName = (templateName) => {
|
|
31
|
+
if (!templateName) {
|
|
32
|
+
parsed.optionError = "Option --template requires a value.";
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (parsed.templateName && parsed.templateName !== templateName) {
|
|
37
|
+
parsed.optionError = "Choose either --router or one --template value.";
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
parsed.templateName = templateName;
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
for (let index = 0; index < raw.length; index++) {
|
|
46
|
+
const arg = raw[index];
|
|
47
|
+
|
|
48
|
+
if (arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (arg === "--router") {
|
|
53
|
+
if (!setTemplateName("router")) {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (arg === "--template" || arg === "-t") {
|
|
60
|
+
const templateName = raw[index + 1];
|
|
61
|
+
|
|
62
|
+
if (!templateName || templateName.startsWith("-")) {
|
|
63
|
+
parsed.optionError = "Option --template requires a value.";
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!setTemplateName(templateName)) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
index++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (arg.startsWith("--template=")) {
|
|
76
|
+
if (!setTemplateName(arg.slice("--template=".length))) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (arg.startsWith("-t=")) {
|
|
83
|
+
if (!setTemplateName(arg.slice("-t=".length))) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!arg.startsWith("-") && parsed.projectName === null) {
|
|
90
|
+
parsed.projectName = arg;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return parsed;
|
|
22
95
|
}
|
|
23
96
|
|
|
24
97
|
// ── Help / Version ──────────────────────────────────────────────────
|
|
@@ -35,11 +108,14 @@ export function printHelp() {
|
|
|
35
108
|
npx create-koppajs [project-name]
|
|
36
109
|
|
|
37
110
|
Options:
|
|
38
|
-
--help, -h
|
|
39
|
-
--version, -v
|
|
111
|
+
--help, -h Show this help message
|
|
112
|
+
--version, -v Show version number
|
|
113
|
+
--template, -t <name> Starter template: minimal | router
|
|
114
|
+
--router Shortcut for --template router
|
|
40
115
|
|
|
41
116
|
Example:
|
|
42
117
|
pnpm create koppajs my-app
|
|
118
|
+
pnpm create koppajs my-app --template router
|
|
43
119
|
`);
|
|
44
120
|
}
|
|
45
121
|
|
|
@@ -49,14 +125,14 @@ export function printVersion() {
|
|
|
49
125
|
|
|
50
126
|
// ── Prompt ──────────────────────────────────────────────────────────
|
|
51
127
|
|
|
52
|
-
|
|
128
|
+
function promptLine(question, closeErrorMessage, input = process.stdin, output = process.stdout) {
|
|
53
129
|
return new Promise((res, rej) => {
|
|
54
|
-
const rl = createInterface({ input
|
|
130
|
+
const rl = createInterface({ input, output });
|
|
55
131
|
let answered = false;
|
|
56
132
|
rl.on("close", () => {
|
|
57
|
-
if (!answered) rej(new Error(
|
|
133
|
+
if (!answered) rej(new Error(closeErrorMessage));
|
|
58
134
|
});
|
|
59
|
-
rl.question(
|
|
135
|
+
rl.question(question, (answer) => {
|
|
60
136
|
answered = true;
|
|
61
137
|
rl.close();
|
|
62
138
|
res(answer.trim());
|
|
@@ -64,6 +140,34 @@ export function promptProjectName() {
|
|
|
64
140
|
});
|
|
65
141
|
}
|
|
66
142
|
|
|
143
|
+
export function promptProjectName(input = process.stdin, output = process.stdout) {
|
|
144
|
+
return promptLine(
|
|
145
|
+
" Project name: ",
|
|
146
|
+
"Input closed before a project name was provided.",
|
|
147
|
+
input,
|
|
148
|
+
output,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function promptStarterTemplate(input = process.stdin, output = process.stdout) {
|
|
153
|
+
const answer = (await promptLine(
|
|
154
|
+
` Starter template (minimal/router) [${DEFAULT_TEMPLATE}]: `,
|
|
155
|
+
"Input closed before a starter template was provided.",
|
|
156
|
+
input,
|
|
157
|
+
output,
|
|
158
|
+
)).toLowerCase();
|
|
159
|
+
|
|
160
|
+
if (answer === "" || answer === "m") {
|
|
161
|
+
return DEFAULT_TEMPLATE;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (answer === "r") {
|
|
165
|
+
return "router";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return answer;
|
|
169
|
+
}
|
|
170
|
+
|
|
67
171
|
// ── Validation ──────────────────────────────────────────────────────
|
|
68
172
|
|
|
69
173
|
export function validateProjectName(name) {
|
|
@@ -78,6 +182,16 @@ export function validateProjectName(name) {
|
|
|
78
182
|
}
|
|
79
183
|
}
|
|
80
184
|
|
|
185
|
+
export function validateStarterTemplate(templateName) {
|
|
186
|
+
if (Object.hasOwn(TEMPLATE_OVERLAY_DIRS, templateName)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Unknown starter template "${templateName}". Supported templates: ${Object.keys(TEMPLATE_OVERLAY_DIRS).join(", ")}.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
81
195
|
// ── Target directory ────────────────────────────────────────────────
|
|
82
196
|
|
|
83
197
|
export function ensureTargetDir(targetPath) {
|
|
@@ -115,6 +229,16 @@ export function copyDirRecursive(src, dest) {
|
|
|
115
229
|
}
|
|
116
230
|
}
|
|
117
231
|
|
|
232
|
+
export function copyStarterTemplate(templateName, dest) {
|
|
233
|
+
copyDirRecursive(TEMPLATE_DIR, dest);
|
|
234
|
+
|
|
235
|
+
const overlayDir = TEMPLATE_OVERLAY_DIRS[templateName];
|
|
236
|
+
|
|
237
|
+
if (overlayDir) {
|
|
238
|
+
copyDirRecursive(overlayDir, dest);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
118
242
|
// ── Patch package.json ──────────────────────────────────────────────
|
|
119
243
|
|
|
120
244
|
export function patchPackageJson(destDir, projectName) {
|
|
@@ -156,8 +280,17 @@ export function printNextSteps(projectName) {
|
|
|
156
280
|
|
|
157
281
|
// ── Main ────────────────────────────────────────────────────────────
|
|
158
282
|
|
|
159
|
-
export
|
|
160
|
-
|
|
283
|
+
export function shouldPromptForTemplateSelection(input = process.stdin, output = process.stdout) {
|
|
284
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function runCli(
|
|
288
|
+
argv = process.argv,
|
|
289
|
+
cwd = process.cwd(),
|
|
290
|
+
io = { input: process.stdin, output: process.stdout },
|
|
291
|
+
) {
|
|
292
|
+
const { help, version, projectName: argName, templateName: argTemplateName, optionError } =
|
|
293
|
+
parseArgs(argv);
|
|
161
294
|
|
|
162
295
|
if (help) {
|
|
163
296
|
printHelp();
|
|
@@ -169,17 +302,29 @@ export async function runCli(argv = process.argv, cwd = process.cwd()) {
|
|
|
169
302
|
return 0;
|
|
170
303
|
}
|
|
171
304
|
|
|
172
|
-
|
|
305
|
+
if (optionError) {
|
|
306
|
+
throw new Error(optionError);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const projectName = argName || (await promptProjectName(io.input, io.output));
|
|
173
310
|
|
|
174
311
|
validateProjectName(projectName);
|
|
175
312
|
|
|
313
|
+
const templateName =
|
|
314
|
+
argTemplateName ||
|
|
315
|
+
(shouldPromptForTemplateSelection(io.input, io.output)
|
|
316
|
+
? await promptStarterTemplate(io.input, io.output)
|
|
317
|
+
: DEFAULT_TEMPLATE);
|
|
318
|
+
|
|
319
|
+
validateStarterTemplate(templateName);
|
|
320
|
+
|
|
176
321
|
const targetDir = resolve(cwd, projectName);
|
|
177
322
|
|
|
178
323
|
ensureTargetDir(targetDir);
|
|
179
324
|
|
|
180
|
-
console.log(`\n Scaffolding KoppaJS project: ${projectName}\n`);
|
|
325
|
+
console.log(`\n Scaffolding KoppaJS project: ${projectName} (${templateName} starter)\n`);
|
|
181
326
|
|
|
182
|
-
|
|
327
|
+
copyStarterTemplate(templateName, targetDir);
|
|
183
328
|
patchPackageJson(targetDir, projectName);
|
|
184
329
|
patchReadme(targetDir, projectName);
|
|
185
330
|
patchChangelog(targetDir, projectName);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-koppajs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Scaffold a new KoppaJS project in seconds.",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"template",
|
|
13
|
+
"template-overlays",
|
|
13
14
|
"CHANGELOG.md",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This repository is a small KoppaJS router starter. It demonstrates a clear
|
|
4
|
+
bootstrap path: HTML shell, TypeScript entrypoint, one root app shell
|
|
5
|
+
component, one router instance, two primary routes, and one explicit not-found
|
|
6
|
+
route. The repository also carries the same quality and release automation
|
|
7
|
+
baseline as the minimal starter so the example remains production-like.
|
|
8
|
+
|
|
9
|
+
## System overview
|
|
10
|
+
|
|
11
|
+
- `index.html` provides the document shell, loads global CSS, declares the
|
|
12
|
+
`<app-view>` root element, and imports the TypeScript entrypoint.
|
|
13
|
+
- `src/main.ts` registers local components with `Core.take(...)`, calls
|
|
14
|
+
`Core()` once, waits for the route outlet to exist, and initializes one
|
|
15
|
+
`KoppajsRouter` instance.
|
|
16
|
+
- `src/app-view.kpa` is the root shell. It renders the hero area, the primary
|
|
17
|
+
navigation, and the `#app-outlet` container that receives route content.
|
|
18
|
+
- `src/home-page.kpa` is the default route and composes `counter-component`.
|
|
19
|
+
- `src/router-page.kpa` is the second route and explains the router wiring.
|
|
20
|
+
- `src/not-found-page.kpa` is the explicit catch-all route.
|
|
21
|
+
- `src/counter-component.kpa` remains the example local-state component.
|
|
22
|
+
- `src/style.css` defines global tokens, base layout, and the background.
|
|
23
|
+
- `tests/integration/` verifies bootstrap wiring and router startup.
|
|
24
|
+
- `tests/e2e/` verifies the user-visible route flow and counter behavior.
|
|
25
|
+
|
|
26
|
+
More detailed boundaries live in [docs/architecture/module-boundaries.md](./docs/architecture/module-boundaries.md).
|
|
27
|
+
|
|
28
|
+
## Runtime flow
|
|
29
|
+
|
|
30
|
+
1. The browser loads [`index.html`](./index.html).
|
|
31
|
+
2. The document loads [`src/style.css`](./src/style.css) and [`src/main.ts`](./src/main.ts).
|
|
32
|
+
3. [`src/main.ts`](./src/main.ts) registers `app-view`, `home-page`,
|
|
33
|
+
`router-page`, `not-found-page`, and `counter-component`, then calls
|
|
34
|
+
`Core()`.
|
|
35
|
+
4. KoppaJS instantiates `<app-view>`.
|
|
36
|
+
5. [`src/app-view.kpa`](./src/app-view.kpa) renders the app shell, navigation,
|
|
37
|
+
and `#app-outlet`.
|
|
38
|
+
6. `src/main.ts` initializes `KoppajsRouter` with the route table and outlet.
|
|
39
|
+
7. The router renders the matching route component into `#app-outlet`,
|
|
40
|
+
synchronizes active links, and updates the current browser URL.
|
|
41
|
+
|
|
42
|
+
## Quality automation layer
|
|
43
|
+
|
|
44
|
+
- `eslint.config.mjs` lint-checks TypeScript source plus tooling files.
|
|
45
|
+
- `prettier.config.mjs` and `.editorconfig` keep supported text files consistent.
|
|
46
|
+
- `.npmrc` enforces the declared engine floor during installs.
|
|
47
|
+
- `.husky/pre-commit` runs `lint-staged`.
|
|
48
|
+
- `.husky/commit-msg` validates commit headers with `commitlint`.
|
|
49
|
+
- `.github/workflows/ci.yml` runs the full repository validation flow on GitHub.
|
|
50
|
+
- `.github/workflows/release.yml` runs tagged release validation and creates GitHub Releases.
|
|
51
|
+
|
|
52
|
+
## Module responsibilities
|
|
53
|
+
|
|
54
|
+
| Module | Responsibility | Must not do |
|
|
55
|
+
| --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------ |
|
|
56
|
+
| `index.html` | Declare the root tag and static assets | Hold feature logic or router setup |
|
|
57
|
+
| `src/main.ts` | Bootstrap the app, register components, and start one router | Accumulate page copy or unrelated UI state |
|
|
58
|
+
| `src/app-view.kpa` | Render the shared shell, nav, and router outlet | Own route resolution or register components |
|
|
59
|
+
| `src/home-page.kpa` | Render the landing route and compose the counter example | Reach into router internals or mutate global DOM state |
|
|
60
|
+
| `src/router-page.kpa` | Render the second route and explain the router baseline | Own bootstrap logic or global navigation state |
|
|
61
|
+
| `src/not-found-page.kpa` | Render the explicit fallback route | Replace route matching or bootstrap responsibilities |
|
|
62
|
+
| `src/counter-component.kpa` | Demonstrate local interactive state | Reach into route orchestration or app-shell concerns |
|
|
63
|
+
| `src/style.css` | Hold global tokens and truly global base styles | Duplicate component-local visuals |
|
|
64
|
+
| `tests/integration/` | Verify bootstrap and router-start boundaries | Duplicate full browser smoke expectations |
|
|
65
|
+
| `tests/e2e/` | Verify visible navigation and counter behavior in a real browser | Assert brittle implementation details |
|
|
66
|
+
|
|
67
|
+
## Invariants
|
|
68
|
+
|
|
69
|
+
- There is exactly one bootstrap entrypoint.
|
|
70
|
+
- The root tag in `index.html` must match a component registered in `src/main.ts`.
|
|
71
|
+
- `Core()` is called once from `src/main.ts`.
|
|
72
|
+
- The starter owns exactly one router instance.
|
|
73
|
+
- The route table contains an explicit `*` fallback route.
|
|
74
|
+
- Route rendering happens through `#app-outlet`.
|
|
75
|
+
- Official releases require matching versions across `package.json`,
|
|
76
|
+
`CHANGELOG.md`, and `vX.Y.Z` tags.
|
|
77
|
+
|
|
78
|
+
## Extension guidance
|
|
79
|
+
|
|
80
|
+
- Add new route components under `src/` and register them from `src/main.ts`.
|
|
81
|
+
- Keep route definitions in one obvious place.
|
|
82
|
+
- Extract reusable route helpers into `.ts` modules only when logic becomes
|
|
83
|
+
shared or branch-heavy.
|
|
84
|
+
- Add a spec before changing visible navigation behavior.
|
|
85
|
+
- Add an ADR before changing the routing model, adding data fetching,
|
|
86
|
+
persistence, or another major subsystem.
|