create-koppajs 1.1.0 → 1.2.1
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 +40 -0
- package/README.md +123 -122
- package/bin/create-koppajs.js +158 -13
- package/package.json +2 -1
- package/template/package.json +2 -2
- package/template/pnpm-lock.yaml +11 -18
- 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 +3786 -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,46 @@ _No unreleased changes yet._
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [1.2.1] — Starter Source Of Truth Cleanup
|
|
20
|
+
|
|
21
|
+
**2026-03-26**
|
|
22
|
+
|
|
23
|
+
Patch release to make the bundled templates the only starter source of truth and
|
|
24
|
+
to refresh the generated starter dependency baseline.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- documented `template/` and `template-overlays/` as the only maintained source
|
|
29
|
+
of truth for generated starters
|
|
30
|
+
- removed active ecosystem references to the retired `koppajs-example` starter
|
|
31
|
+
repository
|
|
32
|
+
- updated generated starter dependencies to `@koppajs/koppajs-core@^3.0.3` and
|
|
33
|
+
`@koppajs/koppajs-vite-plugin@^1.0.1`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## [1.2.0] — Starter Variants And Router Overlay
|
|
38
|
+
|
|
39
|
+
**2026-03-26**
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- added an opt-in `router` starter that layers `@koppajs/koppajs-router`, a
|
|
44
|
+
two-page navigation flow, and an explicit fallback route on top of the
|
|
45
|
+
minimal baseline
|
|
46
|
+
- added overlay-based starter support through `template-overlays/` so future
|
|
47
|
+
starter variants can replace only the files that actually differ
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- extended the CLI contract with `--template` and `--router`, plus interactive
|
|
52
|
+
starter selection in TTY runs when no template flag is provided
|
|
53
|
+
- expanded smoke, unit, and generated-template build coverage to validate both
|
|
54
|
+
the default minimal starter and the opt-in router starter
|
|
55
|
+
- updated architecture docs, specs, and ADRs to reflect multi-starter support
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
19
59
|
## [1.1.0] — Starter & Release Baseline Upgrade
|
|
20
60
|
|
|
21
61
|
**2026-03-17**
|
package/README.md
CHANGED
|
@@ -1,71 +1,92 @@
|
|
|
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 and is the single
|
|
35
|
+
source of truth for that starter.
|
|
36
|
+
- `template-overlays/` owns the files that differ for opt-in starter variants.
|
|
37
|
+
Together with `template/`, they define the only source of truth for
|
|
38
|
+
generated starter output.
|
|
39
|
+
- `scripts/` and `.github/workflows/` own repository-quality and release
|
|
40
|
+
verification.
|
|
41
|
+
- Root governance files own the repository doctrine and must stay aligned with
|
|
42
|
+
code and workflows.
|
|
43
|
+
|
|
44
|
+
The root package must not take on runtime concerns that belong in generated
|
|
45
|
+
applications, and generated applications must not depend on unpublished root
|
|
46
|
+
files after scaffold completion.
|
|
47
|
+
|
|
48
|
+
## Public Contract
|
|
49
|
+
|
|
50
|
+
The stable public contract of this repository is:
|
|
51
|
+
|
|
52
|
+
- the `create-koppajs` command and its `--help` / `--version` flags
|
|
53
|
+
- the optional project-name argument and prompt fallback when omitted
|
|
54
|
+
- the optional `--template <name>` and `--router` starter-selection flags
|
|
55
|
+
- the interactive starter-template prompt when no template flag is provided in
|
|
56
|
+
an interactive terminal
|
|
57
|
+
- rejection of invalid project names, invalid template names, and non-empty
|
|
58
|
+
target directories
|
|
59
|
+
- recursive copying of the bundled `template/` directory plus any selected
|
|
60
|
+
overlay
|
|
61
|
+
- restoration of publish-safe dotfiles and dotdirectories during copy
|
|
62
|
+
- patching of generated `package.json`, `README.md`, `CHANGELOG.md`, and
|
|
63
|
+
`RELEASE.md`
|
|
64
|
+
- the generated starter baselines defined by `template/` and
|
|
65
|
+
`template-overlays/`
|
|
66
|
+
- the npm package payload: `bin/`, `template/`, `template-overlays/`,
|
|
67
|
+
`README.md`, `CHANGELOG.md`, and `LICENSE`
|
|
68
|
+
|
|
69
|
+
The governing specs for that contract are:
|
|
70
|
+
|
|
71
|
+
- [docs/specs/cli-scaffolding.md](./docs/specs/cli-scaffolding.md)
|
|
72
|
+
- [docs/specs/template-starter-contract.md](./docs/specs/template-starter-contract.md)
|
|
2
73
|
|
|
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.
|
|
74
|
+
## Usage
|
|
58
75
|
|
|
59
|
-
|
|
76
|
+
Default starter:
|
|
60
77
|
|
|
61
|
-
|
|
78
|
+
```bash
|
|
79
|
+
pnpm create koppajs my-app
|
|
80
|
+
```
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
Router starter:
|
|
64
83
|
|
|
65
84
|
```bash
|
|
66
|
-
pnpm create koppajs my-app
|
|
85
|
+
pnpm create koppajs my-app --template router
|
|
67
86
|
```
|
|
68
87
|
|
|
88
|
+
Alternative entrypoints:
|
|
89
|
+
|
|
69
90
|
```bash
|
|
70
91
|
npm create koppajs my-app
|
|
71
92
|
```
|
|
@@ -74,7 +95,11 @@ npm create koppajs my-app
|
|
|
74
95
|
npx create-koppajs my-app
|
|
75
96
|
```
|
|
76
97
|
|
|
77
|
-
|
|
98
|
+
If the target directory name is omitted, the CLI prompts for one. If no
|
|
99
|
+
template flag is provided in an interactive terminal, the CLI also prompts for
|
|
100
|
+
starter selection. Non-interactive runs default to `minimal`.
|
|
101
|
+
|
|
102
|
+
After generation:
|
|
78
103
|
|
|
79
104
|
```bash
|
|
80
105
|
cd my-app
|
|
@@ -82,88 +107,64 @@ pnpm install
|
|
|
82
107
|
pnpm dev
|
|
83
108
|
```
|
|
84
109
|
|
|
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
|
|
110
|
+
## Requirements
|
|
125
111
|
|
|
126
|
-
|
|
112
|
+
- for `create-koppajs`: Node.js `>=20`
|
|
113
|
+
- for generated starter projects: pnpm `>=10` and a starter-supported Node.js
|
|
114
|
+
line, currently `20.19+`, `22.13+`, or `24+`
|
|
127
115
|
|
|
128
|
-
##
|
|
116
|
+
## Generated Starters
|
|
129
117
|
|
|
130
|
-
|
|
131
|
-
- Generated starter workflow: pnpm >= 10 plus a starter-supported Node.js line
|
|
132
|
-
(currently 20.19+, 22.13+, or 24+)
|
|
118
|
+
The generated project includes one of two supported starters:
|
|
133
119
|
|
|
134
|
-
|
|
120
|
+
- `minimal` by default: a small KoppaJS application built on Vite and
|
|
121
|
+
TypeScript
|
|
122
|
+
- `router` as opt-in: the same baseline plus `@koppajs/koppajs-router`, a
|
|
123
|
+
simple two-page navigation flow, and an explicit fallback route
|
|
135
124
|
|
|
136
|
-
|
|
125
|
+
Every starter also includes:
|
|
137
126
|
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
- quality tooling through ESLint, Prettier, Vitest, and Playwright
|
|
128
|
+
- local workflow guards through Husky, lint-staged, and commitlint
|
|
129
|
+
- starter governance files, ADR/spec structure, and release-process documents
|
|
130
|
+
- GitHub workflows for CI and tagged releases
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- a generated-project CI/release baseline under `.github/workflows`
|
|
132
|
+
The root repository treats those starters as versioned product surface, not
|
|
133
|
+
test data. `template/` plus the supported overlays are the only source of truth
|
|
134
|
+
for starter behavior.
|
|
145
135
|
|
|
146
|
-
|
|
136
|
+
## Ecosystem Fit
|
|
147
137
|
|
|
148
|
-
|
|
138
|
+
`create-koppajs` is the canonical entry point for starting a new KoppaJS
|
|
139
|
+
application. It complements:
|
|
149
140
|
|
|
150
|
-
|
|
141
|
+
- `@koppajs/koppajs-core` for runtime behavior
|
|
142
|
+
- `@koppajs/koppajs-router` for optional route orchestration in scaffolded apps
|
|
143
|
+
- `@koppajs/koppajs-vite-plugin` for build integration
|
|
144
|
+
- the maintained KoppaJS starter conventions reflected in `template/` and
|
|
145
|
+
`template-overlays/`
|
|
151
146
|
|
|
152
|
-
|
|
147
|
+
The repository stays intentionally narrow so the CLI, starter contract, and
|
|
148
|
+
governance baseline can evolve together without hidden behavior.
|
|
153
149
|
|
|
154
|
-
## Governance
|
|
150
|
+
## Governance
|
|
155
151
|
|
|
156
|
-
|
|
152
|
+
The root meta layer defines how this repository changes:
|
|
157
153
|
|
|
158
154
|
- [AI_CONSTITUTION.md](./AI_CONSTITUTION.md)
|
|
159
155
|
- [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
156
|
+
- [DECISION_HIERARCHY.md](./DECISION_HIERARCHY.md)
|
|
160
157
|
- [DEVELOPMENT_RULES.md](./DEVELOPMENT_RULES.md)
|
|
161
158
|
- [TESTING_STRATEGY.md](./TESTING_STRATEGY.md)
|
|
162
159
|
- [RELEASE.md](./RELEASE.md)
|
|
160
|
+
- [ROADMAP.md](./ROADMAP.md)
|
|
163
161
|
- [docs/meta/README.md](./docs/meta/README.md)
|
|
162
|
+
- [docs/architecture/README.md](./docs/architecture/README.md)
|
|
163
|
+
- [docs/quality/README.md](./docs/quality/README.md)
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
Tagged releases are documented in [CHANGELOG.md](./CHANGELOG.md). Contributor
|
|
166
|
+
workflow rules live in [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
166
167
|
|
|
167
168
|
## License
|
|
168
169
|
|
|
169
|
-
Apache
|
|
170
|
+
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.1
|
|
3
|
+
"version": "1.2.1",
|
|
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"
|
package/template/package.json
CHANGED
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"prepare": "husky"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@koppajs/koppajs-core": "^3.0.
|
|
45
|
+
"@koppajs/koppajs-core": "^3.0.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@commitlint/cli": "^20.1.0",
|
|
49
49
|
"@commitlint/config-conventional": "^20.0.0",
|
|
50
50
|
"@eslint/js": "^10.0.1",
|
|
51
|
-
"@koppajs/koppajs-vite-plugin": "^1.0.
|
|
51
|
+
"@koppajs/koppajs-vite-plugin": "^1.0.1",
|
|
52
52
|
"@playwright/test": "^1.58.2",
|
|
53
53
|
"@types/node": "^25.4.0",
|
|
54
54
|
"@vitest/coverage-v8": "^4.0.18",
|