create-koppajs 1.0.1 → 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 +127 -0
- package/README.md +166 -131
- package/bin/create-koppajs.js +346 -175
- package/package.json +55 -34
- package/template/AI_CONSTITUTION.md +50 -0
- package/template/ARCHITECTURE.md +86 -0
- package/template/CHANGELOG.md +34 -0
- package/template/CONTRIBUTING.md +92 -0
- package/template/DECISION_HIERARCHY.md +32 -0
- package/template/DEVELOPMENT_RULES.md +57 -0
- package/template/LICENSE +1 -1
- package/template/README.md +241 -49
- package/template/RELEASE.md +230 -0
- package/template/ROADMAP.md +34 -0
- package/template/TESTING_STRATEGY.md +93 -0
- package/template/_editorconfig +12 -0
- package/template/_gitattributes +1 -0
- package/template/_github/instructions/ai-workflow.md +33 -0
- package/template/_github/workflows/ci.yml +38 -0
- package/template/_github/workflows/release.yml +58 -0
- package/template/_gitignore +5 -0
- package/template/_husky/commit-msg +8 -0
- package/template/_husky/pre-commit +1 -0
- package/template/_npmrc +1 -0
- package/template/_prettierignore +7 -0
- package/template/commitlint.config.mjs +6 -0
- package/template/docs/adr/0001-keep-the-starter-minimal.md +32 -0
- package/template/docs/adr/0002-adopt-a-living-meta-layer.md +30 -0
- package/template/docs/adr/0003-normalize-kpa-plugin-output.md +24 -0
- package/template/docs/adr/0004-adopt-an-automated-quality-baseline.md +31 -0
- package/template/docs/adr/0005-adopt-a-tag-driven-release-baseline.md +45 -0
- package/template/docs/adr/0006-adopt-commit-message-conventions.md +39 -0
- package/template/docs/adr/README.md +37 -0
- package/template/docs/adr/TEMPLATE.md +18 -0
- package/template/docs/architecture/module-boundaries.md +48 -0
- package/template/docs/meta/maintenance.md +40 -0
- package/template/docs/quality/quality-gates.md +39 -0
- package/template/docs/specs/README.md +36 -0
- package/template/docs/specs/TEMPLATE.md +34 -0
- package/template/docs/specs/app-bootstrap.md +46 -0
- package/template/docs/specs/counter-component.md +48 -0
- package/template/docs/specs/quality-workflow.md +62 -0
- package/template/eslint.config.mjs +54 -0
- package/template/package.json +57 -6
- package/template/playwright.config.ts +31 -0
- package/template/pnpm-lock.yaml +3784 -0
- package/template/prettier.config.mjs +6 -0
- package/template/src/app-view.kpa +35 -36
- package/template/src/counter-component.kpa +87 -87
- package/template/src/style.css +5 -5
- package/template/tests/e2e/app.spec.ts +18 -0
- package/template/tests/integration/main-bootstrap.test.ts +33 -0
- package/template/tests/unit/normalize-kpa-module-export.test.ts +46 -0
- package/template/tsconfig.json +13 -2
- package/template/vite.config.d.mts +7 -0
- package/template/vite.config.mjs +39 -4
- package/template/vitest.config.mjs +19 -0
- 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/bin/create-koppajs.js
CHANGED
|
@@ -1,175 +1,346 @@
|
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
export const TEMPLATE_OVERLAY_DIRS = Object.freeze({
|
|
12
|
+
minimal: null,
|
|
13
|
+
router: join(__dirname, "..", "template-overlays", "router"),
|
|
14
|
+
});
|
|
15
|
+
export const DEFAULT_TEMPLATE = "minimal";
|
|
16
|
+
const CLI_PKG = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
17
|
+
|
|
18
|
+
// ── Args ────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export function parseArgs(argv) {
|
|
21
|
+
const raw = argv.slice(2);
|
|
22
|
+
const parsed = {
|
|
23
|
+
help: raw.includes("--help") || raw.includes("-h"),
|
|
24
|
+
version: raw.includes("--version") || raw.includes("-v"),
|
|
25
|
+
projectName: null,
|
|
26
|
+
templateName: null,
|
|
27
|
+
optionError: null,
|
|
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;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Help / Version ──────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
export function printHelp() {
|
|
100
|
+
console.log(`
|
|
101
|
+
create-koppajs v${CLI_PKG.version}
|
|
102
|
+
|
|
103
|
+
Scaffold a new KoppaJS project.
|
|
104
|
+
|
|
105
|
+
Usage:
|
|
106
|
+
pnpm create koppajs [project-name]
|
|
107
|
+
npm create koppajs [project-name]
|
|
108
|
+
npx create-koppajs [project-name]
|
|
109
|
+
|
|
110
|
+
Options:
|
|
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
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
pnpm create koppajs my-app
|
|
118
|
+
pnpm create koppajs my-app --template router
|
|
119
|
+
`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function printVersion() {
|
|
123
|
+
console.log(CLI_PKG.version);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Prompt ──────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function promptLine(question, closeErrorMessage, input = process.stdin, output = process.stdout) {
|
|
129
|
+
return new Promise((res, rej) => {
|
|
130
|
+
const rl = createInterface({ input, output });
|
|
131
|
+
let answered = false;
|
|
132
|
+
rl.on("close", () => {
|
|
133
|
+
if (!answered) rej(new Error(closeErrorMessage));
|
|
134
|
+
});
|
|
135
|
+
rl.question(question, (answer) => {
|
|
136
|
+
answered = true;
|
|
137
|
+
rl.close();
|
|
138
|
+
res(answer.trim());
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
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
|
+
|
|
171
|
+
// ── Validation ──────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
export function validateProjectName(name) {
|
|
174
|
+
if (!name) {
|
|
175
|
+
throw new Error("Project name cannot be empty.");
|
|
176
|
+
}
|
|
177
|
+
if (name === "." || name === "..") {
|
|
178
|
+
throw new Error(`Invalid project name "${name}".`);
|
|
179
|
+
}
|
|
180
|
+
if (name.includes("/") || name.includes("\\")) {
|
|
181
|
+
throw new Error("Project name must not contain path separators.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
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
|
+
|
|
195
|
+
// ── Target directory ────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
export function ensureTargetDir(targetPath) {
|
|
198
|
+
if (existsSync(targetPath) && readdirSync(targetPath).length > 0) {
|
|
199
|
+
throw new Error(`Directory "${basename(targetPath)}" already exists and is not empty.`);
|
|
200
|
+
}
|
|
201
|
+
mkdirSync(targetPath, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Copy ────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
// npm excludes .gitignore from published packages — ship as _gitignore
|
|
207
|
+
// and rename during scaffolding (same approach as create-vite).
|
|
208
|
+
const RENAME_FILES = {
|
|
209
|
+
_editorconfig: ".editorconfig",
|
|
210
|
+
_gitattributes: ".gitattributes",
|
|
211
|
+
_github: ".github",
|
|
212
|
+
_gitignore: ".gitignore",
|
|
213
|
+
_husky: ".husky",
|
|
214
|
+
_npmrc: ".npmrc",
|
|
215
|
+
_prettierignore: ".prettierignore",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export function copyDirRecursive(src, dest) {
|
|
219
|
+
mkdirSync(dest, { recursive: true });
|
|
220
|
+
for (const entry of readdirSync(src)) {
|
|
221
|
+
const srcPath = join(src, entry);
|
|
222
|
+
const destName = RENAME_FILES[entry] || entry;
|
|
223
|
+
const destPath = join(dest, destName);
|
|
224
|
+
if (statSync(srcPath).isDirectory()) {
|
|
225
|
+
copyDirRecursive(srcPath, destPath);
|
|
226
|
+
} else {
|
|
227
|
+
copyFileSync(srcPath, destPath);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
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
|
+
|
|
242
|
+
// ── Patch package.json ──────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
export function patchPackageJson(destDir, projectName) {
|
|
245
|
+
const pkgPath = join(destDir, "package.json");
|
|
246
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
247
|
+
pkg.name = projectName;
|
|
248
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Patch README ────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
function patchTextFile(destDir, relativePath, projectName) {
|
|
254
|
+
const filePath = join(destDir, relativePath);
|
|
255
|
+
let content = readFileSync(filePath, "utf-8");
|
|
256
|
+
content = content.replaceAll("__PROJECT_NAME__", projectName);
|
|
257
|
+
writeFileSync(filePath, content);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function patchReadme(destDir, projectName) {
|
|
261
|
+
patchTextFile(destDir, "README.md", projectName);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function patchChangelog(destDir, projectName) {
|
|
265
|
+
patchTextFile(destDir, "CHANGELOG.md", projectName);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function patchReleaseNotes(destDir, projectName) {
|
|
269
|
+
patchTextFile(destDir, "RELEASE.md", projectName);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── Final output ────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
export function printNextSteps(projectName) {
|
|
275
|
+
console.log(" Done! Next steps:\n");
|
|
276
|
+
console.log(` cd ${projectName}`);
|
|
277
|
+
console.log(" pnpm install");
|
|
278
|
+
console.log(" pnpm dev\n");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Main ────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
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);
|
|
294
|
+
|
|
295
|
+
if (help) {
|
|
296
|
+
printHelp();
|
|
297
|
+
return 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (version) {
|
|
301
|
+
printVersion();
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (optionError) {
|
|
306
|
+
throw new Error(optionError);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const projectName = argName || (await promptProjectName(io.input, io.output));
|
|
310
|
+
|
|
311
|
+
validateProjectName(projectName);
|
|
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
|
+
|
|
321
|
+
const targetDir = resolve(cwd, projectName);
|
|
322
|
+
|
|
323
|
+
ensureTargetDir(targetDir);
|
|
324
|
+
|
|
325
|
+
console.log(`\n Scaffolding KoppaJS project: ${projectName} (${templateName} starter)\n`);
|
|
326
|
+
|
|
327
|
+
copyStarterTemplate(templateName, targetDir);
|
|
328
|
+
patchPackageJson(targetDir, projectName);
|
|
329
|
+
patchReadme(targetDir, projectName);
|
|
330
|
+
patchChangelog(targetDir, projectName);
|
|
331
|
+
patchReleaseNotes(targetDir, projectName);
|
|
332
|
+
printNextSteps(projectName);
|
|
333
|
+
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function isDirectExecution() {
|
|
338
|
+
return Boolean(process.argv[1]) && resolve(process.argv[1]) === __filename;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (isDirectExecution()) {
|
|
342
|
+
runCli().catch((err) => {
|
|
343
|
+
console.error(`\n Error: ${err.message}\n`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
});
|
|
346
|
+
}
|
package/package.json
CHANGED
|
@@ -1,34 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-koppajs",
|
|
3
|
-
"version": "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
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "create-koppajs",
|
|
3
|
+
"version": "1.2.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
|
+
"template-overlays",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"packageManager": "pnpm@10.12.1",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20",
|
|
21
|
+
"pnpm": ">=10"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"check:meta": "node scripts/check-meta-layer.mjs",
|
|
25
|
+
"lint": "node scripts/lint.mjs",
|
|
26
|
+
"format:check": "node scripts/format-check.mjs",
|
|
27
|
+
"check:cli": "node ./bin/create-koppajs.js --help && node ./bin/create-koppajs.js --version",
|
|
28
|
+
"test:unit": "node scripts/run-unit-tests.mjs",
|
|
29
|
+
"test:watch": "node scripts/run-unit-tests.mjs --watch",
|
|
30
|
+
"test:smoke": "node scripts/smoke-test.mjs",
|
|
31
|
+
"test:template-build": "node scripts/template-build-test.mjs",
|
|
32
|
+
"test": "npm run test:unit && npm run test:smoke",
|
|
33
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
34
|
+
"check": "npm run check:meta && npm run lint && npm run format:check && npm run check:cli && npm test && npm run pack:dry-run",
|
|
35
|
+
"release:check": "npm run check && npm run test:template-build",
|
|
36
|
+
"prepare": "husky",
|
|
37
|
+
"clean": "node scripts/clean.mjs"
|
|
38
|
+
},
|
|
39
|
+
"author": "Bastian Bensch",
|
|
40
|
+
"license": "Apache-2.0",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/koppajs/create-koppajs"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/koppajs/create-koppajs#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/koppajs/create-koppajs/issues"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@commitlint/cli": "^20.1.0",
|
|
51
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
52
|
+
"husky": "^9.1.7",
|
|
53
|
+
"lint-staged": "^16.3.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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.
|