create-keeno 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +24 -2
- package/dist/chunks/scaffold-wbdjagd0.js +326 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +159 -0
- package/dist/scaffold.d.ts +31 -0
- package/dist/scaffold.js +16 -0
- package/package.json +61 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 iliesw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# create-keeno
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffolds a new [Keeno](https://github.com/iliesw/Keeno) project.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun create keeno my-app
|
|
7
|
+
npm create keeno@latest my-app
|
|
8
|
+
pnpm create keeno my-app
|
|
9
|
+
yarn create keeno my-app
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
It asks for anything not given on the command line:
|
|
13
|
+
|
|
14
|
+
| Option | |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `[dir]` | Where to create the project. Its name becomes the package name. |
|
|
17
|
+
| `-t, --template` | `router`: pages in `src/pages` with file-based routing, a layout and a 404 page (default). `minimal`: a single `App.kn`. |
|
|
18
|
+
| `--tailwind`, `--no-tailwind` | Add Tailwind CSS (default: no). |
|
|
19
|
+
| `--install`, `--no-install` | Install dependencies with the package manager that ran the command. |
|
|
20
|
+
| `-f, --force` | Write into a non-empty directory. |
|
|
21
|
+
| `-y, --yes` | Use the defaults for everything not given, without asking. |
|
|
22
|
+
|
|
23
|
+
The project uses Vite, TypeScript and `keeno-check`, with `dev`, `build`, `preview` and `check` scripts.
|
|
24
|
+
|
|
25
|
+
The Keeno package versions it writes are the ones released with this version of create-keeno.
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// ../../../../../../../tmp/keeno-build/create-keeno/src/scaffold.ts
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
var TEMPLATES = {
|
|
6
|
+
router: "Pages in src/pages with file-based routing, a layout and a 404 page",
|
|
7
|
+
minimal: "A single App.kn component"
|
|
8
|
+
};
|
|
9
|
+
var KEENO_PACKAGES = ["@inflector/keeno", "@inflector/keeno-router", "@inflector/vite-plugin-keeno", "@inflector/keeno-check"];
|
|
10
|
+
var VERSIONS = {
|
|
11
|
+
vite: "^7.0.0",
|
|
12
|
+
typescript: "^5.6.3",
|
|
13
|
+
tailwindcss: "^4.1.0",
|
|
14
|
+
"@tailwindcss/vite": "^4.1.0"
|
|
15
|
+
};
|
|
16
|
+
function keenoVersions() {
|
|
17
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
let pkg;
|
|
19
|
+
for (;; ) {
|
|
20
|
+
const file = path.join(dir, "package.json");
|
|
21
|
+
if (existsSync(file)) {
|
|
22
|
+
pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
23
|
+
if (pkg.name === "create-keeno")
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
const parent = path.dirname(dir);
|
|
27
|
+
if (parent === dir)
|
|
28
|
+
throw new Error("create-keeno: cannot find its own package.json");
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const name of KEENO_PACKAGES) {
|
|
33
|
+
const range = pkg.devDependencies?.[name];
|
|
34
|
+
if (!range)
|
|
35
|
+
throw new Error(`create-keeno: no version for ${name}`);
|
|
36
|
+
out[name] = range.startsWith("workspace:") ? `^${workspaceVersion(path.dirname(dir), name)}` : range;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function workspaceVersion(packagesDir, name) {
|
|
41
|
+
for (const entry of readdirSync(packagesDir)) {
|
|
42
|
+
const file = path.join(packagesDir, entry, "package.json");
|
|
43
|
+
if (!existsSync(file))
|
|
44
|
+
continue;
|
|
45
|
+
const pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
46
|
+
if (pkg.name === name)
|
|
47
|
+
return pkg.version;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`create-keeno: workspace package ${name} not found in ${packagesDir}`);
|
|
50
|
+
}
|
|
51
|
+
function toPackageName(dir) {
|
|
52
|
+
const name = path.basename(path.resolve(dir)).trim().toLowerCase().replace(/[^a-z0-9._~-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
53
|
+
return name || "keeno-app";
|
|
54
|
+
}
|
|
55
|
+
var sortKeys = (o) => Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
|
|
56
|
+
function projectFiles(options) {
|
|
57
|
+
const { name, template, tailwind } = options;
|
|
58
|
+
const v = options.versions ?? keenoVersions();
|
|
59
|
+
const router = template === "router";
|
|
60
|
+
const dependencies = { "@inflector/keeno": v["@inflector/keeno"] };
|
|
61
|
+
if (router)
|
|
62
|
+
dependencies["@inflector/keeno-router"] = v["@inflector/keeno-router"];
|
|
63
|
+
const devDependencies = {
|
|
64
|
+
"@inflector/keeno-check": v["@inflector/keeno-check"],
|
|
65
|
+
"@inflector/vite-plugin-keeno": v["@inflector/vite-plugin-keeno"],
|
|
66
|
+
typescript: VERSIONS.typescript,
|
|
67
|
+
vite: VERSIONS.vite
|
|
68
|
+
};
|
|
69
|
+
if (tailwind) {
|
|
70
|
+
devDependencies.tailwindcss = VERSIONS.tailwindcss;
|
|
71
|
+
devDependencies["@tailwindcss/vite"] = VERSIONS["@tailwindcss/vite"];
|
|
72
|
+
}
|
|
73
|
+
const pkg = {
|
|
74
|
+
name,
|
|
75
|
+
private: true,
|
|
76
|
+
version: "0.0.0",
|
|
77
|
+
type: "module",
|
|
78
|
+
scripts: { dev: "vite", build: "vite build", preview: "vite preview", check: "keeno-check" },
|
|
79
|
+
dependencies: sortKeys(dependencies),
|
|
80
|
+
devDependencies: sortKeys(devDependencies)
|
|
81
|
+
};
|
|
82
|
+
const files = {
|
|
83
|
+
"package.json": JSON.stringify(pkg, null, 2) + `
|
|
84
|
+
`,
|
|
85
|
+
".gitignore": `node_modules
|
|
86
|
+
dist
|
|
87
|
+
*.local
|
|
88
|
+
.DS_Store
|
|
89
|
+
`,
|
|
90
|
+
"README.md": readme(name, router),
|
|
91
|
+
"index.html": `<!doctype html>
|
|
92
|
+
<html lang="en">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="UTF-8" />
|
|
95
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
96
|
+
<title>${escapeHtml(name)}</title>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<div id="app"></div>
|
|
100
|
+
<script type="module" src="/src/main.ts"></script>
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
103
|
+
`,
|
|
104
|
+
"tsconfig.json": `{
|
|
105
|
+
"compilerOptions": {
|
|
106
|
+
"target": "ES2022",
|
|
107
|
+
"module": "ESNext",
|
|
108
|
+
"moduleResolution": "Bundler",
|
|
109
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
110
|
+
"strict": true,
|
|
111
|
+
"skipLibCheck": true,
|
|
112
|
+
"noEmit": true
|
|
113
|
+
},
|
|
114
|
+
"include": ["src", "vite.config.ts"]
|
|
115
|
+
}
|
|
116
|
+
`,
|
|
117
|
+
"vite.config.ts": viteConfig(router, tailwind),
|
|
118
|
+
"src/env.d.ts": `/// <reference types="vite/client" />
|
|
119
|
+
${router ? `/// <reference types="@inflector/vite-plugin-keeno/client" />
|
|
120
|
+
` : ""}
|
|
121
|
+
declare module '*.kn' {
|
|
122
|
+
const Component: import('@inflector/keeno').Component
|
|
123
|
+
export default Component
|
|
124
|
+
}
|
|
125
|
+
`,
|
|
126
|
+
"src/main.ts": router ? `import { mount } from '@inflector/keeno'
|
|
127
|
+
import Pages from 'virtual:keeno-pages'
|
|
128
|
+
import './app.css'
|
|
129
|
+
|
|
130
|
+
// Routes come from the files in src/pages.
|
|
131
|
+
mount(Pages, document.getElementById('app')!)
|
|
132
|
+
` : `import { mount } from '@inflector/keeno'
|
|
133
|
+
import App from './App.kn'
|
|
134
|
+
import './app.css'
|
|
135
|
+
|
|
136
|
+
mount(App, document.getElementById('app')!)
|
|
137
|
+
`,
|
|
138
|
+
"src/app.css": (tailwind ? `@import 'tailwindcss';
|
|
139
|
+
|
|
140
|
+
` : "") + GLOBAL_CSS
|
|
141
|
+
};
|
|
142
|
+
if (router) {
|
|
143
|
+
files["src/pages/_layout.kn"] = LAYOUT;
|
|
144
|
+
files["src/pages/index.kn"] = counterPage("Home");
|
|
145
|
+
files["src/pages/about.kn"] = ABOUT;
|
|
146
|
+
files["src/pages/_404.kn"] = NOT_FOUND;
|
|
147
|
+
} else {
|
|
148
|
+
files["src/App.kn"] = counterPage(name);
|
|
149
|
+
}
|
|
150
|
+
return files;
|
|
151
|
+
}
|
|
152
|
+
function scaffold(dir, options) {
|
|
153
|
+
const root = path.resolve(dir);
|
|
154
|
+
const files = projectFiles(options);
|
|
155
|
+
for (const [file, contents] of Object.entries(files)) {
|
|
156
|
+
const dest = path.join(root, file);
|
|
157
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
158
|
+
writeFileSync(dest, contents);
|
|
159
|
+
}
|
|
160
|
+
return { dir: root, files: Object.keys(files) };
|
|
161
|
+
}
|
|
162
|
+
function isEmptyDir(dir) {
|
|
163
|
+
if (!existsSync(dir))
|
|
164
|
+
return true;
|
|
165
|
+
return readdirSync(dir).every((f) => f === ".git");
|
|
166
|
+
}
|
|
167
|
+
var escapeHtml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
168
|
+
function viteConfig(router, tailwind) {
|
|
169
|
+
const imports = [`import { defineConfig } from 'vite'`, `import keeno from '@inflector/vite-plugin-keeno'`];
|
|
170
|
+
if (tailwind)
|
|
171
|
+
imports.push(`import tailwindcss from '@tailwindcss/vite'`);
|
|
172
|
+
const plugins = [router ? `keeno({ useFileRouter: true, pages: './src/pages' })` : "keeno()"];
|
|
173
|
+
if (tailwind)
|
|
174
|
+
plugins.push("tailwindcss()");
|
|
175
|
+
return `${imports.join(`
|
|
176
|
+
`)}
|
|
177
|
+
|
|
178
|
+
export default defineConfig({
|
|
179
|
+
${router ? ` // File-based routing: each .kn file in src/pages is a route.
|
|
180
|
+
` : ""} plugins: [${plugins.join(", ")}],
|
|
181
|
+
})
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
function readme(name, router) {
|
|
185
|
+
return `# ${name}
|
|
186
|
+
|
|
187
|
+
A [Keeno](https://github.com/iliesw/Keeno) app.
|
|
188
|
+
|
|
189
|
+
| Command | |
|
|
190
|
+
|---|---|
|
|
191
|
+
| \`dev\` | Start the dev server |
|
|
192
|
+
| \`build\` | Build for production into \`dist/\` |
|
|
193
|
+
| \`preview\` | Serve the production build |
|
|
194
|
+
| \`check\` | Type-check every \`.kn\` and \`.ts\` file |
|
|
195
|
+
|
|
196
|
+
${router ? "Pages live in `src/pages`: `index.kn` is `/`, `about.kn` is `/about`, `_layout.kn` wraps every page and `_404.kn` renders unknown URLs." : "Start editing `src/App.kn`."}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
var GLOBAL_CSS = `:root {
|
|
200
|
+
font-family: system-ui, sans-serif;
|
|
201
|
+
color: #1f2937;
|
|
202
|
+
background: #f5f5f4;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
body {
|
|
206
|
+
margin: 0;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
var counterPage = (title) => `<script lang="ts">
|
|
210
|
+
const count = state(0)
|
|
211
|
+
const doubled = derived(() => count * 2)
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<main>
|
|
215
|
+
<h1>${escapeHtml(title)}</h1>
|
|
216
|
+
<p>Clicked {count} times ({doubled} doubled)</p>
|
|
217
|
+
<button type="button" onClick={() => count.set(count + 1)}>+1</button>
|
|
218
|
+
</main>
|
|
219
|
+
|
|
220
|
+
<style>
|
|
221
|
+
main {
|
|
222
|
+
max-width: 32rem;
|
|
223
|
+
margin: 2rem auto;
|
|
224
|
+
padding: 1.5rem;
|
|
225
|
+
background: white;
|
|
226
|
+
border-radius: 12px;
|
|
227
|
+
box-shadow: 0 1px 4px rgb(0 0 0 / 0.1);
|
|
228
|
+
}
|
|
229
|
+
h1 {
|
|
230
|
+
margin-top: 0;
|
|
231
|
+
color: teal;
|
|
232
|
+
}
|
|
233
|
+
button {
|
|
234
|
+
background: teal;
|
|
235
|
+
color: white;
|
|
236
|
+
border: 0;
|
|
237
|
+
border-radius: 6px;
|
|
238
|
+
padding: 0.4rem 0.8rem;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
241
|
+
</style>
|
|
242
|
+
`;
|
|
243
|
+
var LAYOUT = `<script lang="ts">
|
|
244
|
+
import type { Children } from '@inflector/keeno'
|
|
245
|
+
import { Link } from '@inflector/keeno-router'
|
|
246
|
+
// Wraps every page and stays mounted while you navigate between them.
|
|
247
|
+
const { children } = props<{ children: Children }>()
|
|
248
|
+
</script>
|
|
249
|
+
|
|
250
|
+
<nav>
|
|
251
|
+
<Link href="/" exact>Home</Link>
|
|
252
|
+
<Link href="/about">About</Link>
|
|
253
|
+
</nav>
|
|
254
|
+
{children}
|
|
255
|
+
|
|
256
|
+
<style>
|
|
257
|
+
nav {
|
|
258
|
+
display: flex;
|
|
259
|
+
justify-content: center;
|
|
260
|
+
gap: 0.5rem;
|
|
261
|
+
padding: 1rem;
|
|
262
|
+
}
|
|
263
|
+
a {
|
|
264
|
+
color: teal;
|
|
265
|
+
text-decoration: none;
|
|
266
|
+
padding: 0.3rem 0.8rem;
|
|
267
|
+
border-radius: 6px;
|
|
268
|
+
}
|
|
269
|
+
.active {
|
|
270
|
+
background: rgb(0 128 128 / 0.12);
|
|
271
|
+
}
|
|
272
|
+
</style>
|
|
273
|
+
`;
|
|
274
|
+
var ABOUT = `<script lang="ts">
|
|
275
|
+
import { Link } from '@inflector/keeno-router'
|
|
276
|
+
</script>
|
|
277
|
+
|
|
278
|
+
<main>
|
|
279
|
+
<h1>About</h1>
|
|
280
|
+
<p>
|
|
281
|
+
This page is <code>src/pages/about.kn</code>. Add a file to <code>src/pages</code> to add a route.
|
|
282
|
+
</p>
|
|
283
|
+
<p><Link href="/">Back home</Link></p>
|
|
284
|
+
</main>
|
|
285
|
+
|
|
286
|
+
<style>
|
|
287
|
+
main {
|
|
288
|
+
max-width: 32rem;
|
|
289
|
+
margin: 2rem auto;
|
|
290
|
+
padding: 1.5rem;
|
|
291
|
+
background: white;
|
|
292
|
+
border-radius: 12px;
|
|
293
|
+
}
|
|
294
|
+
h1 {
|
|
295
|
+
margin-top: 0;
|
|
296
|
+
}
|
|
297
|
+
a {
|
|
298
|
+
color: teal;
|
|
299
|
+
}
|
|
300
|
+
</style>
|
|
301
|
+
`;
|
|
302
|
+
var NOT_FOUND = `<script lang="ts">
|
|
303
|
+
import { Link, useRoute } from '@inflector/keeno-router'
|
|
304
|
+
const route = useRoute()
|
|
305
|
+
</script>
|
|
306
|
+
|
|
307
|
+
<main>
|
|
308
|
+
<h1>Not found</h1>
|
|
309
|
+
<p>There is no page at <code>{route.path}</code>. <Link href="/">Go home</Link></p>
|
|
310
|
+
</main>
|
|
311
|
+
|
|
312
|
+
<style>
|
|
313
|
+
main {
|
|
314
|
+
max-width: 32rem;
|
|
315
|
+
margin: 2rem auto;
|
|
316
|
+
padding: 1.5rem;
|
|
317
|
+
background: white;
|
|
318
|
+
border-radius: 12px;
|
|
319
|
+
}
|
|
320
|
+
a {
|
|
321
|
+
color: teal;
|
|
322
|
+
}
|
|
323
|
+
</style>
|
|
324
|
+
`;
|
|
325
|
+
|
|
326
|
+
export { TEMPLATES, keenoVersions, toPackageName, projectFiles, scaffold, isEmptyDir };
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TEMPLATES,
|
|
4
|
+
isEmptyDir,
|
|
5
|
+
scaffold,
|
|
6
|
+
toPackageName
|
|
7
|
+
} from "./chunks/scaffold-wbdjagd0.js";
|
|
8
|
+
|
|
9
|
+
// ../../../../../../../tmp/keeno-build/create-keeno/src/cli.ts
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { createInterface } from "node:readline/promises";
|
|
13
|
+
var HELP = `Usage: create-keeno [dir] [options]
|
|
14
|
+
|
|
15
|
+
dir where to create the project (default: asked, or keeno-app)
|
|
16
|
+
-t, --template ${Object.entries(TEMPLATES).map(([k, d]) => `${k}: ${d}`).join(`
|
|
17
|
+
`)}
|
|
18
|
+
--tailwind add Tailwind CSS (--no-tailwind to skip)
|
|
19
|
+
--install install dependencies after scaffolding
|
|
20
|
+
-f, --force write into a non-empty directory
|
|
21
|
+
-y, --yes use the defaults for everything not given
|
|
22
|
+
`;
|
|
23
|
+
var color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
24
|
+
var paint = (code) => (s) => color ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
25
|
+
var bold = paint(1);
|
|
26
|
+
var dim = paint(2);
|
|
27
|
+
var cyan = paint(36);
|
|
28
|
+
var red = paint(31);
|
|
29
|
+
function fail(message, code = 1) {
|
|
30
|
+
process.stderr.write(`${red("create-keeno:")} ${message}
|
|
31
|
+
`);
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
var args = process.argv.slice(2);
|
|
35
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
36
|
+
process.stdout.write(HELP);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
var dirArg;
|
|
40
|
+
var template;
|
|
41
|
+
var tailwind;
|
|
42
|
+
var install;
|
|
43
|
+
var force = false;
|
|
44
|
+
var yes = false;
|
|
45
|
+
for (let i = 0;i < args.length; i++) {
|
|
46
|
+
const a = args[i];
|
|
47
|
+
if (a === "-t" || a === "--template" || a.startsWith("--template=")) {
|
|
48
|
+
const value = a.includes("=") ? a.slice(a.indexOf("=") + 1) : args[++i];
|
|
49
|
+
if (!value || !(value in TEMPLATES))
|
|
50
|
+
fail(`unknown template ${value ?? "(none)"}; use ${Object.keys(TEMPLATES).join(" or ")}`, 2);
|
|
51
|
+
template = value;
|
|
52
|
+
} else if (a === "--tailwind")
|
|
53
|
+
tailwind = true;
|
|
54
|
+
else if (a === "--no-tailwind")
|
|
55
|
+
tailwind = false;
|
|
56
|
+
else if (a === "--install")
|
|
57
|
+
install = true;
|
|
58
|
+
else if (a === "--no-install")
|
|
59
|
+
install = false;
|
|
60
|
+
else if (a === "-f" || a === "--force")
|
|
61
|
+
force = true;
|
|
62
|
+
else if (a === "-y" || a === "--yes")
|
|
63
|
+
yes = true;
|
|
64
|
+
else if (a.startsWith("-"))
|
|
65
|
+
fail(`unknown option ${a}
|
|
66
|
+
|
|
67
|
+
${HELP}`, 2);
|
|
68
|
+
else if (dirArg === undefined)
|
|
69
|
+
dirArg = a;
|
|
70
|
+
else
|
|
71
|
+
fail(`unexpected argument ${a}`, 2);
|
|
72
|
+
}
|
|
73
|
+
var interactive = !yes && !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
74
|
+
var rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;
|
|
75
|
+
rl?.on("SIGINT", () => {
|
|
76
|
+
process.stdout.write(`
|
|
77
|
+
`);
|
|
78
|
+
process.exit(130);
|
|
79
|
+
});
|
|
80
|
+
async function text(question, fallback) {
|
|
81
|
+
if (!rl)
|
|
82
|
+
return fallback;
|
|
83
|
+
const answer = (await rl.question(`${cyan("?")} ${bold(question)} ${dim(`(${fallback})`)} `)).trim();
|
|
84
|
+
return answer || fallback;
|
|
85
|
+
}
|
|
86
|
+
async function confirm(question, fallback) {
|
|
87
|
+
if (!rl)
|
|
88
|
+
return fallback;
|
|
89
|
+
for (;; ) {
|
|
90
|
+
const answer = (await rl.question(`${cyan("?")} ${bold(question)} ${dim(fallback ? "(Y/n)" : "(y/N)")} `)).trim().toLowerCase();
|
|
91
|
+
if (!answer)
|
|
92
|
+
return fallback;
|
|
93
|
+
if (answer === "y" || answer === "yes")
|
|
94
|
+
return true;
|
|
95
|
+
if (answer === "n" || answer === "no")
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function select(question, choices) {
|
|
100
|
+
const keys = Object.keys(choices);
|
|
101
|
+
if (!rl)
|
|
102
|
+
return keys[0];
|
|
103
|
+
process.stdout.write(`${cyan("?")} ${bold(question)}
|
|
104
|
+
`);
|
|
105
|
+
keys.forEach((k, i) => process.stdout.write(` ${cyan(String(i + 1))}. ${k} ${dim(`- ${choices[k]}`)}
|
|
106
|
+
`));
|
|
107
|
+
for (;; ) {
|
|
108
|
+
const answer = (await rl.question(` ${dim(`1-${keys.length}`)} ${dim("(1)")} `)).trim();
|
|
109
|
+
if (!answer)
|
|
110
|
+
return keys[0];
|
|
111
|
+
const picked = keys[Number(answer) - 1] ?? keys.find((k) => k === answer);
|
|
112
|
+
if (picked)
|
|
113
|
+
return picked;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function packageManager() {
|
|
117
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
118
|
+
for (const pm of ["pnpm", "yarn", "bun"])
|
|
119
|
+
if (agent.startsWith(pm))
|
|
120
|
+
return pm;
|
|
121
|
+
return "npm";
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(`
|
|
124
|
+
${bold(cyan("Keeno"))} ${dim("- create a new project")}
|
|
125
|
+
|
|
126
|
+
`);
|
|
127
|
+
var dir = dirArg ?? await text("Project directory", "keeno-app");
|
|
128
|
+
var target = path.resolve(dir);
|
|
129
|
+
if (!force && !isEmptyDir(target)) {
|
|
130
|
+
const proceed = await confirm(`${dir} is not empty. Write into it anyway?`, false);
|
|
131
|
+
if (!proceed)
|
|
132
|
+
fail(`${dir} is not empty (use --force to write into it)`);
|
|
133
|
+
}
|
|
134
|
+
template ??= yes ? "router" : await select("Template", TEMPLATES);
|
|
135
|
+
tailwind ??= await confirm("Add Tailwind CSS?", false);
|
|
136
|
+
var pm = packageManager();
|
|
137
|
+
install ??= await confirm(`Install dependencies with ${pm}?`, interactive);
|
|
138
|
+
rl?.close();
|
|
139
|
+
var { files } = scaffold(target, { name: toPackageName(target), template, tailwind });
|
|
140
|
+
process.stdout.write(`
|
|
141
|
+
${dim(`Created ${files.length} files in`)} ${target}
|
|
142
|
+
`);
|
|
143
|
+
if (install) {
|
|
144
|
+
process.stdout.write(`
|
|
145
|
+
${dim(`$ ${pm} install`)}
|
|
146
|
+
`);
|
|
147
|
+
const r = spawnSync(pm, ["install"], { cwd: target, stdio: "inherit", shell: process.platform === "win32" });
|
|
148
|
+
if (r.status !== 0)
|
|
149
|
+
fail(`${pm} install failed; run it yourself in ${dir}`);
|
|
150
|
+
}
|
|
151
|
+
var run = (script) => pm === "npm" || pm === "bun" ? `${pm} run ${script}` : `${pm} ${script}`;
|
|
152
|
+
var rel = path.relative(process.cwd(), target);
|
|
153
|
+
var steps = [rel && `cd ${/\s/.test(rel) ? JSON.stringify(rel) : rel}`, !install && `${pm} install`, run("dev")].filter(Boolean);
|
|
154
|
+
process.stdout.write(`
|
|
155
|
+
${bold("Next:")}
|
|
156
|
+
${steps.map((s) => ` ${cyan(s)}`).join(`
|
|
157
|
+
`)}
|
|
158
|
+
|
|
159
|
+
`);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const TEMPLATES: {
|
|
2
|
+
readonly router: "Pages in src/pages with file-based routing, a layout and a 404 page";
|
|
3
|
+
readonly minimal: "A single App.kn component";
|
|
4
|
+
};
|
|
5
|
+
export type Template = keyof typeof TEMPLATES;
|
|
6
|
+
export interface Options {
|
|
7
|
+
/** package.json `name`. */
|
|
8
|
+
name: string;
|
|
9
|
+
template: Template;
|
|
10
|
+
tailwind: boolean;
|
|
11
|
+
/** Version ranges of the Keeno packages, by package name (default: `keenoVersions()`). */
|
|
12
|
+
versions?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Ranges for the Keeno packages, from this package's devDependencies: a published create-keeno has the
|
|
16
|
+
* released versions there (`bun pm pack` rewrites `workspace:` ranges); in the monorepo they are read
|
|
17
|
+
* from the sibling packages.
|
|
18
|
+
*/
|
|
19
|
+
export declare function keenoVersions(): Record<string, string>;
|
|
20
|
+
/** A valid npm package name from a directory name. */
|
|
21
|
+
export declare function toPackageName(dir: string): string;
|
|
22
|
+
/** Every file of the new project, by path relative to its root. */
|
|
23
|
+
export declare function projectFiles(options: Options): Record<string, string>;
|
|
24
|
+
export interface ScaffoldResult {
|
|
25
|
+
dir: string;
|
|
26
|
+
files: string[];
|
|
27
|
+
}
|
|
28
|
+
/** Write the project into `dir` (created when missing). */
|
|
29
|
+
export declare function scaffold(dir: string, options: Options): ScaffoldResult;
|
|
30
|
+
/** True when `dir` is missing or has nothing but a .git directory. */
|
|
31
|
+
export declare function isEmptyDir(dir: string): boolean;
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEMPLATES,
|
|
3
|
+
isEmptyDir,
|
|
4
|
+
keenoVersions,
|
|
5
|
+
projectFiles,
|
|
6
|
+
scaffold,
|
|
7
|
+
toPackageName
|
|
8
|
+
} from "./chunks/scaffold-wbdjagd0.js";
|
|
9
|
+
export {
|
|
10
|
+
TEMPLATES,
|
|
11
|
+
isEmptyDir,
|
|
12
|
+
keenoVersions,
|
|
13
|
+
projectFiles,
|
|
14
|
+
scaffold,
|
|
15
|
+
toPackageName
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-keeno",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Scaffold a new Keeno project: npm create keeno",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/iliesw/Keeno.git",
|
|
9
|
+
"directory": "packages/create-keeno"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/iliesw/Keeno/tree/main/packages/create-keeno#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/iliesw/Keeno/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"keeno",
|
|
17
|
+
"create",
|
|
18
|
+
"scaffold",
|
|
19
|
+
"template",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"create-keeno": "./dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/scaffold.js",
|
|
27
|
+
"types": "./dist/scaffold.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"keeno-source": "./src/scaffold.ts",
|
|
31
|
+
"types": "./dist/scaffold.d.ts",
|
|
32
|
+
"import": "./dist/scaffold.js",
|
|
33
|
+
"default": "./dist/scaffold.js"
|
|
34
|
+
},
|
|
35
|
+
"./cli": {
|
|
36
|
+
"keeno-source": "./src/cli.ts",
|
|
37
|
+
"types": "./dist/cli.d.ts",
|
|
38
|
+
"import": "./dist/cli.js",
|
|
39
|
+
"default": "./dist/cli.js"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "bun ../../scripts/build-package.ts --target=node",
|
|
48
|
+
"prepack": "bun run build"
|
|
49
|
+
},
|
|
50
|
+
"//": "Not installed with the CLI: the generated project depends on these. `bun pm pack` turns the workspace ranges into the released versions, which the CLI reads from this file.",
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@inflector/keeno": "^0.4.0",
|
|
53
|
+
"@inflector/keeno-check": "^0.1.3",
|
|
54
|
+
"@inflector/keeno-router": "^0.2.0",
|
|
55
|
+
"@inflector/vite-plugin-keeno": "^0.4.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|