@k8o/create 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) k8o
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 ADDED
@@ -0,0 +1,72 @@
1
+ # @k8o/create
2
+
3
+ k8o's project generator for [Vite+](https://viteplus.dev). One command spins up
4
+ a new repo with the conventions already wired in — supply-chain rules, oxlint /
5
+ oxfmt via [`@k8o/oxc-config`](https://github.com/k35o/oxc-config), Changesets,
6
+ GitHub Actions CI/release, mise, and Renovate.
7
+
8
+ ## Usage
9
+
10
+ ```sh
11
+ vp create @k8o # interactive: pick library / web, enter a name
12
+ vp create @k8o -- --kind library --name @k8o/foo --description "..."
13
+ vp create @k8o -- --kind web --name @k8o/foo
14
+ ```
15
+
16
+ `vp create @k8o` resolves this package (`@k8o/create`) and runs its generator.
17
+
18
+ ## Kinds
19
+
20
+ | `--kind` | Emits |
21
+ | --------- | -------------------------------------------------------------------------------- |
22
+ | `library` | Single-package library: `src` + `tests`, `vp pack` → ESM + `.d.mts`. |
23
+ | `web` | Monorepo: `packages/<name>` React lib + `apps/playground`, on @k8o/arte-odyssey. |
24
+
25
+ Both generated repos pass `check` (fmt + lint + types), `test`, and `build`
26
+ out of the box.
27
+
28
+ ### What every generated repo gets
29
+
30
+ - **Supply-chain rules** — `pnpm-workspace.yaml` with `blockExoticSubdeps`,
31
+ `minimumReleaseAge`, `strictDepBuilds`, `verifyDepsBeforeRun`, `saveExact`,
32
+ `autoInstallPeers: false`.
33
+ - **Lint / format** — `@k8o/oxc-config` presets via `vp check`.
34
+ - **Versioning** — Changesets + a `release.yml` that publishes to npm with
35
+ provenance using the `K35O_BOT` GitHub App.
36
+ - **CI** — `ci.yml` (lint / types / tests / changeset) on a composite
37
+ mise-based install action.
38
+ - **Toolchain** — pinned `mise.toml`, `renovate.json` extending
39
+ `github>k35o/renovate-config`.
40
+
41
+ ## Layout
42
+
43
+ ```
44
+ bin/index.ts # Bingo runTemplateCLI entry
45
+ src/
46
+ template.ts # createTemplate: { kind, name, description } → dispatch
47
+ library.ts # produceLibrary()
48
+ web.ts # produceWeb()
49
+ shared.ts # base files shared by both kinds (single source)
50
+ tests/produce.test.ts
51
+ ```
52
+
53
+ Each kind is built by a `produce()` that returns the file tree. The shared base
54
+ files (security config, CI, license, mise, …) live once in `src/shared.ts` and
55
+ are imported by both — change a convention in one place and both kinds follow.
56
+
57
+ ## Adding a kind
58
+
59
+ Add a value to the `kind` enum in [`src/template.ts`](src/template.ts), write a
60
+ `produce*()` in a new `src/<kind>.ts` reusing `src/shared.ts`, and dispatch to
61
+ it. No new package to publish.
62
+
63
+ ## Develop
64
+
65
+ ```sh
66
+ pnpm install
67
+ pnpm check # fmt + lint
68
+ pnpm typecheck
69
+ pnpm test # generator smoke tests
70
+ pnpm dev -- --kind library --name @k8o/scratch --directory /tmp/scratch --offline
71
+ pnpm changeset # describe a change before merging
72
+ ```
package/bin/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runTemplateCLI } from 'bingo';
4
+
5
+ import template from '../src/template.ts';
6
+
7
+ process.exitCode = await runTemplateCLI(template);
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@k8o/create",
3
+ "version": "0.1.0",
4
+ "description": "k8o's Vite+ project generator — run `vp create @k8o`.",
5
+ "keywords": [
6
+ "bingo-template",
7
+ "create",
8
+ "k8o",
9
+ "template",
10
+ "vite-plus-generator"
11
+ ],
12
+ "homepage": "https://github.com/k35o/templates#readme",
13
+ "bugs": "https://github.com/k35o/templates/issues",
14
+ "license": "MIT",
15
+ "author": "k8o (https://github.com/k35o)",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/k35o/templates.git"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "src",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "type": "module",
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "provenance": true
30
+ },
31
+ "dependencies": {
32
+ "bingo": "0.7.0",
33
+ "zod": "3.25.76"
34
+ },
35
+ "devDependencies": {
36
+ "@changesets/changelog-github": "0.7.0",
37
+ "@changesets/cli": "2.31.0",
38
+ "@k8o/oxc-config": "0.1.3",
39
+ "@types/node": "24.12.4",
40
+ "typescript": "6.0.3",
41
+ "vite-plus": "0.1.23"
42
+ },
43
+ "engines": {
44
+ "node": ">=24.13.0"
45
+ },
46
+ "scripts": {
47
+ "test": "vp test",
48
+ "dev": "node bin/index.ts",
49
+ "check": "vp check",
50
+ "check:write": "vp check --fix",
51
+ "typecheck": "tsc --noEmit",
52
+ "changeset": "changeset",
53
+ "version": "changeset version",
54
+ "release": "changeset publish",
55
+ "ready": "vp fmt && vp lint && vp test"
56
+ },
57
+ "bin": {
58
+ "create": "./bin/index.ts"
59
+ }
60
+ }
package/src/library.ts ADDED
@@ -0,0 +1,328 @@
1
+ import {
2
+ changesetConfig,
3
+ CHANGESET_README,
4
+ coords,
5
+ type GenerateOptions,
6
+ GITIGNORE,
7
+ INSTALL_ACTION,
8
+ LICENSE,
9
+ MISE_TOML,
10
+ NPMRC,
11
+ RELEASE_YML,
12
+ } from './shared.ts';
13
+
14
+ // Single-package repo: pnpm-workspace.yaml carries the supply-chain rules only.
15
+ const PNPM_WORKSPACE = `blockExoticSubdeps: true
16
+
17
+ minimumReleaseAge: 10080
18
+ minimumReleaseAgeExclude:
19
+ - '@k8o/*'
20
+
21
+ strictDepBuilds: true
22
+
23
+ allowBuilds:
24
+ esbuild: false
25
+
26
+ verifyDepsBeforeRun: install
27
+
28
+ saveExact: true
29
+ autoInstallPeers: false
30
+ `;
31
+
32
+ const TSCONFIG = `{
33
+ "compilerOptions": {
34
+ "target": "ESNext",
35
+ "module": "ESNext",
36
+ "moduleResolution": "bundler",
37
+ "moduleDetection": "force",
38
+ "customConditions": ["source"],
39
+ "lib": ["ES2023"],
40
+ "strict": true,
41
+ "noUncheckedIndexedAccess": true,
42
+ "exactOptionalPropertyTypes": true,
43
+ "noImplicitOverride": true,
44
+ "noImplicitReturns": true,
45
+ "noFallthroughCasesInSwitch": true,
46
+ "noUnusedLocals": true,
47
+ "noUnusedParameters": true,
48
+ "allowImportingTsExtensions": true,
49
+ "isolatedModules": true,
50
+ "verbatimModuleSyntax": true,
51
+ "skipLibCheck": true,
52
+ "declaration": true,
53
+ "noEmit": true,
54
+ "resolveJsonModule": true,
55
+ "esModuleInterop": true,
56
+ "forceConsistentCasingInFileNames": true,
57
+ "types": ["node", "vite-plus/test/globals"]
58
+ },
59
+ "include": ["src/**/*", "tests/**/*", "vite.config.ts"]
60
+ }
61
+ `;
62
+
63
+ const VITE_CONFIG = `import { fmt, test, typescript } from '@k8o/oxc-config';
64
+ import { defineConfig } from 'vite-plus';
65
+
66
+ export default defineConfig({
67
+ fmt: {
68
+ ...fmt,
69
+ ignorePatterns: ['CHANGELOG.md'],
70
+ },
71
+ lint: {
72
+ extends: [typescript],
73
+ ignorePatterns: ['CHANGELOG.md'],
74
+ options: {
75
+ typeAware: true,
76
+ },
77
+ overrides: [
78
+ {
79
+ files: ['tests/**/*.test.ts'],
80
+ plugins: [...(test.plugins ?? [])],
81
+ rules: test.rules ?? {},
82
+ },
83
+ ],
84
+ },
85
+ pack: {
86
+ entry: ['src/index.ts'],
87
+ format: 'esm',
88
+ dts: true,
89
+ outDir: 'dist',
90
+ unbundle: true,
91
+ },
92
+ test: {
93
+ globals: true,
94
+ include: ['tests/**/*.test.ts'],
95
+ },
96
+ staged: {
97
+ '*.{js,ts,cjs,mjs,jsx,tsx,json,jsonc}': 'vp check --fix',
98
+ },
99
+ });
100
+ `;
101
+
102
+ const CI_YML = `name: CI
103
+
104
+ on:
105
+ pull_request:
106
+
107
+ permissions:
108
+ contents: read
109
+
110
+ jobs:
111
+ check:
112
+ name: Lint / Format / Types
113
+ runs-on: ubuntu-latest
114
+ timeout-minutes: 10
115
+ steps:
116
+ - name: Checkout branch
117
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
118
+ with:
119
+ persist-credentials: false
120
+
121
+ - name: Install
122
+ uses: ./.github/composite-actions/install
123
+
124
+ - name: Build
125
+ run: pnpm build
126
+
127
+ - name: Run check (fmt + lint + typecheck)
128
+ run: pnpm check
129
+
130
+ tests:
131
+ name: Tests
132
+ runs-on: ubuntu-latest
133
+ timeout-minutes: 10
134
+ steps:
135
+ - name: Checkout branch
136
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
137
+ with:
138
+ persist-credentials: false
139
+
140
+ - name: Install
141
+ uses: ./.github/composite-actions/install
142
+
143
+ - name: Run tests
144
+ run: pnpm test
145
+
146
+ changeset:
147
+ name: Changeset
148
+ runs-on: ubuntu-latest
149
+ timeout-minutes: 10
150
+ steps:
151
+ - name: Checkout branch
152
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
153
+ with:
154
+ fetch-depth: 0
155
+ persist-credentials: false
156
+
157
+ - name: Install
158
+ uses: ./.github/composite-actions/install
159
+
160
+ - name: Verify a changeset is present
161
+ run: pnpm exec changeset status --since=origin/main
162
+ `;
163
+
164
+ const RENOVATE = `{
165
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
166
+ "extends": ["github>k35o/renovate-config"],
167
+ "mise": {
168
+ "enabled": true,
169
+ "managerFilePatterns": ["/^mise\\\\.toml$/"]
170
+ },
171
+ "prConcurrentLimit": 10,
172
+ "packageRules": [
173
+ {
174
+ "matchPackageNames": ["/^@changesets/"],
175
+ "groupName": "changesets dependencies"
176
+ },
177
+ {
178
+ "matchPackageNames": ["/^pnpm$/"],
179
+ "groupName": "pnpm dependencies"
180
+ },
181
+ {
182
+ "matchPackageNames": ["/^node$/"],
183
+ "groupName": "node dependencies"
184
+ },
185
+ {
186
+ "matchPackageNames": ["changesets/action"],
187
+ "enabled": false
188
+ }
189
+ ]
190
+ }
191
+ `;
192
+
193
+ const SRC_INDEX = `export const hello = (name: string): string => \`Hello, \${name}!\`;
194
+ `;
195
+
196
+ const TEST_INDEX = `import { hello } from '../src/index.ts';
197
+
198
+ test('hello greets by name', () => {
199
+ expect(hello('world')).toBe('Hello, world!');
200
+ });
201
+ `;
202
+
203
+ export const produceLibrary = (options: GenerateOptions) => {
204
+ const { repo } = coords(options.name);
205
+ const description = options.description ?? '';
206
+
207
+ const packageJson = {
208
+ name: options.name,
209
+ version: '0.0.0',
210
+ description,
211
+ keywords: [] as string[],
212
+ homepage: `https://github.com/${repo}#readme`,
213
+ bugs: `https://github.com/${repo}/issues`,
214
+ license: 'MIT',
215
+ author: 'k8o (https://github.com/k35o)',
216
+ repository: {
217
+ type: 'git',
218
+ url: `git+https://github.com/${repo}.git`,
219
+ },
220
+ files: ['dist', 'README.md', 'LICENSE', 'CHANGELOG.md'],
221
+ type: 'module',
222
+ sideEffects: false,
223
+ exports: {
224
+ '.': {
225
+ import: {
226
+ types: './dist/index.d.mts',
227
+ default: './dist/index.mjs',
228
+ },
229
+ },
230
+ './package.json': './package.json',
231
+ },
232
+ publishConfig: {
233
+ access: 'public',
234
+ provenance: true,
235
+ },
236
+ scripts: {
237
+ build: 'vp pack',
238
+ test: 'vp test',
239
+ lint: 'vp lint',
240
+ fmt: 'vp fmt',
241
+ check: 'vp check',
242
+ 'check:write': 'vp check --fix',
243
+ typecheck: 'tsc --noEmit',
244
+ changeset: 'changeset',
245
+ version: 'changeset version',
246
+ release: 'pnpm build && changeset publish',
247
+ prepublishOnly: 'pnpm build',
248
+ },
249
+ devDependencies: {
250
+ '@changesets/changelog-github': '0.7.0',
251
+ '@changesets/cli': '2.31.0',
252
+ '@k8o/oxc-config': '0.1.3',
253
+ '@types/node': '24.12.4',
254
+ typescript: '6.0.3',
255
+ 'vite-plus': '0.1.23',
256
+ },
257
+ engines: {
258
+ node: '>=24.13.0',
259
+ },
260
+ packageManager: 'pnpm@11.5.1',
261
+ };
262
+
263
+ const readme = `# ${options.name}
264
+
265
+ ${description}
266
+
267
+ ## Install
268
+
269
+ \`\`\`sh
270
+ pnpm add ${options.name}
271
+ \`\`\`
272
+
273
+ ## Develop
274
+
275
+ \`\`\`sh
276
+ pnpm install
277
+ pnpm check # fmt + lint + typecheck
278
+ pnpm test
279
+ pnpm build # vp pack -> dist/
280
+ \`\`\`
281
+
282
+ ## Release
283
+
284
+ Versioned and published with [Changesets](https://github.com/changesets/changesets).
285
+
286
+ \`\`\`sh
287
+ pnpm changeset # describe the change
288
+ \`\`\`
289
+
290
+ Merging to \`main\` lets the release workflow open a version PR and publish to npm.
291
+ `;
292
+
293
+ return {
294
+ files: {
295
+ 'package.json': `${JSON.stringify(packageJson, null, 2)}\n`,
296
+ 'pnpm-workspace.yaml': PNPM_WORKSPACE,
297
+ '.npmrc': NPMRC,
298
+ '.gitignore': GITIGNORE,
299
+ 'mise.toml': MISE_TOML,
300
+ 'tsconfig.json': TSCONFIG,
301
+ 'vite.config.ts': VITE_CONFIG,
302
+ 'renovate.json': RENOVATE,
303
+ LICENSE,
304
+ 'README.md': readme,
305
+ '.changeset': {
306
+ 'config.json': changesetConfig(repo),
307
+ 'README.md': CHANGESET_README,
308
+ },
309
+ '.github': {
310
+ 'composite-actions': {
311
+ install: {
312
+ 'action.yml': INSTALL_ACTION,
313
+ },
314
+ },
315
+ workflows: {
316
+ 'ci.yml': CI_YML,
317
+ 'release.yml': RELEASE_YML,
318
+ },
319
+ },
320
+ src: {
321
+ 'index.ts': SRC_INDEX,
322
+ },
323
+ tests: {
324
+ 'index.test.ts': TEST_INDEX,
325
+ },
326
+ },
327
+ };
328
+ };
package/src/shared.ts ADDED
@@ -0,0 +1,149 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared k8o base files — identical across the `library` and `web` templates.
3
+ // Template-specific files (pnpm-workspace, tsconfig, vite.config, CI, renovate)
4
+ // live in ./library.ts and ./web.ts.
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export type GenerateOptions = {
8
+ name: string;
9
+ description?: string | undefined;
10
+ };
11
+
12
+ /** Derive repo coordinates from a (possibly scoped) package name. */
13
+ export const coords = (name: string): { bare: string; repo: string } => {
14
+ if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(name)) {
15
+ throw new Error(
16
+ `Invalid package name "${name}" — expected an (optionally @scoped) npm package name.`,
17
+ );
18
+ }
19
+ const bare = name.replace(/^@[^/]+\//u, '');
20
+ return { bare, repo: `k35o/${bare}` };
21
+ };
22
+
23
+ export const NPMRC = `registry=https://registry.npmjs.org/
24
+ @k8o:registry=https://registry.npmjs.org/
25
+
26
+ engine-strict=true
27
+ `;
28
+
29
+ export const GITIGNORE = `node_modules
30
+ dist
31
+ coverage
32
+ *.log
33
+ .DS_Store
34
+ *.tsbuildinfo
35
+ .env*
36
+ *.local
37
+ .vscode/*
38
+ !.vscode/extensions.json
39
+ settings.local.json
40
+ `;
41
+
42
+ export const MISE_TOML = `[tools]
43
+ node = "24.16.0"
44
+ pnpm = "11.5.1"
45
+ `;
46
+
47
+ export const INSTALL_ACTION = `name: Install
48
+ description: Sets up mise and runs install
49
+
50
+ runs:
51
+ using: composite
52
+ steps:
53
+ - name: Setup mise
54
+ uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
55
+ with:
56
+ cache: true
57
+
58
+ - name: Install dependencies
59
+ shell: bash
60
+ run: pnpm install --frozen-lockfile
61
+ `;
62
+
63
+ export const RELEASE_YML = `name: Release
64
+
65
+ on:
66
+ push:
67
+ branches:
68
+ - main
69
+
70
+ concurrency:
71
+ group: \${{ github.workflow }}-\${{ github.ref }}
72
+
73
+ permissions:
74
+ contents: write
75
+ id-token: write
76
+ pull-requests: write
77
+
78
+ jobs:
79
+ release:
80
+ runs-on: ubuntu-latest
81
+ steps:
82
+ - name: Generate token
83
+ id: generate-token
84
+ uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
85
+ with:
86
+ client-id: \${{ secrets.K35O_BOT_CLIENT_ID }}
87
+ private-key: \${{ secrets.K35O_BOT_PRIVATE_KEY }}
88
+
89
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
90
+ with:
91
+ fetch-depth: 0
92
+ token: \${{ steps.generate-token.outputs.token }}
93
+
94
+ - name: Install
95
+ uses: ./.github/composite-actions/install
96
+
97
+ - name: Install latest npm
98
+ run: npm install -g npm@latest
99
+
100
+ - name: Create release Pull Request or publish to NPM
101
+ uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1.7.0
102
+ with:
103
+ publish: pnpm release
104
+ env:
105
+ GITHUB_TOKEN: \${{ steps.generate-token.outputs.token }}
106
+ `;
107
+
108
+ export const CHANGESET_README = `# Changesets
109
+
110
+ Hello and welcome! This folder has been automatically generated by \`@changesets/cli\`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets).
111
+
112
+ We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
113
+ `;
114
+
115
+ export const LICENSE = `MIT License
116
+
117
+ Copyright (c) k8o
118
+
119
+ Permission is hereby granted, free of charge, to any person obtaining a copy
120
+ of this software and associated documentation files (the "Software"), to deal
121
+ in the Software without restriction, including without limitation the rights
122
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
123
+ copies of the Software, and to permit persons to whom the Software is
124
+ furnished to do so, subject to the following conditions:
125
+
126
+ The above copyright notice and this permission notice shall be included in all
127
+ copies or substantial portions of the Software.
128
+
129
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
130
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
131
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
132
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
133
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
134
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
135
+ SOFTWARE.
136
+ `;
137
+
138
+ export const changesetConfig = (repo: string): string => `{
139
+ "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
140
+ "changelog": ["@changesets/changelog-github", { "repo": "${repo}" }],
141
+ "commit": false,
142
+ "fixed": [],
143
+ "linked": [],
144
+ "access": "public",
145
+ "baseBranch": "main",
146
+ "updateInternalDependencies": "patch",
147
+ "ignore": []
148
+ }
149
+ `;
@@ -0,0 +1,33 @@
1
+ import { createTemplate } from 'bingo';
2
+ import { z } from 'zod';
3
+
4
+ import pkgJson from '../package.json' with { type: 'json' };
5
+ import { produceLibrary } from './library.ts';
6
+ import { produceWeb } from './web.ts';
7
+
8
+ export default createTemplate({
9
+ about: {
10
+ name: pkgJson.name,
11
+ description: pkgJson.description,
12
+ },
13
+
14
+ options: {
15
+ kind: z
16
+ .enum(['library', 'web'])
17
+ .describe('What to scaffold: a library or a web (React) package'),
18
+ name: z
19
+ .string()
20
+ .regex(
21
+ /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u,
22
+ 'Must be a valid (optionally @scoped) npm package name',
23
+ )
24
+ .describe('Package name, e.g. @k8o/foo'),
25
+ description: z.string().optional().describe('One-line package description'),
26
+ },
27
+
28
+ produce({ options }) {
29
+ return options.kind === 'web'
30
+ ? produceWeb(options)
31
+ : produceLibrary(options);
32
+ },
33
+ });
package/src/web.ts ADDED
@@ -0,0 +1,585 @@
1
+ import {
2
+ changesetConfig,
3
+ CHANGESET_README,
4
+ coords,
5
+ type GenerateOptions,
6
+ GITIGNORE,
7
+ INSTALL_ACTION,
8
+ LICENSE,
9
+ MISE_TOML,
10
+ NPMRC,
11
+ RELEASE_YML,
12
+ } from './shared.ts';
13
+
14
+ const COMMITLINT = `export default { extends: ['@commitlint/config-conventional'] };
15
+ `;
16
+
17
+ // Monorepo: packages/* (publishable libs) + apps/* (private apps/examples).
18
+ const PNPM_WORKSPACE = `packages:
19
+ - packages/*
20
+ - apps/*
21
+
22
+ blockExoticSubdeps: true
23
+
24
+ minimumReleaseAge: 10080
25
+ minimumReleaseAgeExclude:
26
+ - '@k8o/*'
27
+
28
+ strictDepBuilds: true
29
+
30
+ allowBuilds:
31
+ esbuild: false
32
+
33
+ verifyDepsBeforeRun: install
34
+
35
+ saveExact: true
36
+ autoInstallPeers: false
37
+
38
+ catalog:
39
+ '@changesets/changelog-github': 0.7.0
40
+ '@changesets/cli': 2.31.0
41
+ '@commitlint/cli': 21.0.2
42
+ '@commitlint/config-conventional': 21.0.2
43
+ '@k8o/arte-odyssey': 10.1.0
44
+ '@k8o/oxc-config': 0.1.3
45
+ '@tailwindcss/vite': 4.3.0
46
+ '@types/node': 24.12.4
47
+ '@types/react': 19.2.15
48
+ '@types/react-dom': 19.2.3
49
+ '@vitejs/plugin-react': 6.0.2
50
+ react: 19.2.6
51
+ react-dom: 19.2.6
52
+ tailwindcss: 4.3.0
53
+ typescript: 6.0.3
54
+ vite: 8.0.14
55
+ vite-plus: 0.1.23
56
+ `;
57
+
58
+ // Root tsconfig for a React monorepo (DOM + JSX). Package tsconfigs extend it.
59
+ const TSCONFIG = `{
60
+ "compilerOptions": {
61
+ "target": "ESNext",
62
+ "module": "ESNext",
63
+ "moduleResolution": "bundler",
64
+ "moduleDetection": "force",
65
+ "customConditions": ["source"],
66
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
67
+ "jsx": "react-jsx",
68
+ "strict": true,
69
+ "noUncheckedIndexedAccess": true,
70
+ "exactOptionalPropertyTypes": true,
71
+ "noImplicitOverride": true,
72
+ "noImplicitReturns": true,
73
+ "noFallthroughCasesInSwitch": true,
74
+ "noUnusedLocals": true,
75
+ "noUnusedParameters": true,
76
+ "allowImportingTsExtensions": true,
77
+ "isolatedModules": true,
78
+ "verbatimModuleSyntax": true,
79
+ "skipLibCheck": true,
80
+ "declaration": true,
81
+ "noEmit": true,
82
+ "resolveJsonModule": true,
83
+ "esModuleInterop": true,
84
+ "forceConsistentCasingInFileNames": true,
85
+ "types": ["node", "vite-plus/test/globals"]
86
+ }
87
+ }
88
+ `;
89
+
90
+ // Root config drives fmt + lint for the whole monorepo.
91
+ const ROOT_VITE_CONFIG = `import { fmt, react, test } from '@k8o/oxc-config';
92
+ import { defineConfig } from 'vite-plus';
93
+
94
+ export default defineConfig({
95
+ fmt: {
96
+ ...fmt,
97
+ ignorePatterns: ['CHANGELOG.md', '**/CHANGELOG.md'],
98
+ },
99
+ lint: {
100
+ extends: [react],
101
+ ignorePatterns: ['CHANGELOG.md', '**/CHANGELOG.md'],
102
+ options: {
103
+ typeAware: true,
104
+ },
105
+ settings: {
106
+ react: { version: '19.2.6' },
107
+ },
108
+ overrides: [
109
+ {
110
+ files: ['**/*.test.{ts,tsx}'],
111
+ plugins: [...(test.plugins ?? [])],
112
+ rules: test.rules ?? {},
113
+ },
114
+ ],
115
+ },
116
+ staged: {
117
+ '*.{js,ts,cjs,mjs,jsx,tsx,json,jsonc}': 'vp check --fix',
118
+ },
119
+ });
120
+ `;
121
+
122
+ const CI_YML = `name: CI
123
+
124
+ on:
125
+ pull_request:
126
+
127
+ permissions:
128
+ contents: read
129
+
130
+ jobs:
131
+ check:
132
+ name: Build / Lint / Types
133
+ runs-on: ubuntu-latest
134
+ timeout-minutes: 10
135
+ steps:
136
+ - name: Checkout branch
137
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
138
+ with:
139
+ persist-credentials: false
140
+
141
+ - name: Install
142
+ uses: ./.github/composite-actions/install
143
+
144
+ - name: Build
145
+ run: pnpm build
146
+
147
+ - name: Run check (fmt + lint)
148
+ run: pnpm check
149
+
150
+ - name: Typecheck
151
+ run: pnpm typecheck
152
+
153
+ tests:
154
+ name: Tests
155
+ runs-on: ubuntu-latest
156
+ timeout-minutes: 10
157
+ steps:
158
+ - name: Checkout branch
159
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
160
+ with:
161
+ persist-credentials: false
162
+
163
+ - name: Install
164
+ uses: ./.github/composite-actions/install
165
+
166
+ - name: Run tests
167
+ run: pnpm test
168
+
169
+ changeset:
170
+ name: Changeset
171
+ runs-on: ubuntu-latest
172
+ timeout-minutes: 10
173
+ steps:
174
+ - name: Checkout branch
175
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
176
+ with:
177
+ fetch-depth: 0
178
+ persist-credentials: false
179
+
180
+ - name: Install
181
+ uses: ./.github/composite-actions/install
182
+
183
+ - name: Verify a changeset is present
184
+ run: pnpm exec changeset status --since=origin/main
185
+ `;
186
+
187
+ const RENOVATE = `{
188
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
189
+ "extends": ["github>k35o/renovate-config"],
190
+ "mise": {
191
+ "enabled": true,
192
+ "managerFilePatterns": ["/^mise\\\\.toml$/"]
193
+ },
194
+ "prConcurrentLimit": 10,
195
+ "packageRules": [
196
+ {
197
+ "matchPackageNames": ["react", "react-dom", "/^@types/react/"],
198
+ "groupName": "react dependencies"
199
+ },
200
+ {
201
+ "matchPackageNames": ["/tailwind/"],
202
+ "groupName": "tailwind dependencies"
203
+ },
204
+ {
205
+ "matchPackageNames": ["/^@changesets/"],
206
+ "groupName": "changesets dependencies"
207
+ },
208
+ {
209
+ "matchPackageNames": ["/^@commitlint/"],
210
+ "groupName": "commitlint dependencies"
211
+ },
212
+ {
213
+ "matchPackageNames": ["/^pnpm$/"],
214
+ "groupName": "pnpm dependencies"
215
+ },
216
+ {
217
+ "matchPackageNames": ["/^node$/"],
218
+ "groupName": "node dependencies"
219
+ },
220
+ {
221
+ "matchPackageNames": ["changesets/action"],
222
+ "enabled": false
223
+ },
224
+ {
225
+ "matchDepTypes": ["peerDependencies"],
226
+ "enabled": false
227
+ }
228
+ ]
229
+ }
230
+ `;
231
+
232
+ // --- packages/<pkg> : publishable React component library --------------------
233
+
234
+ const PKG_TSCONFIG = `{
235
+ "extends": "../../tsconfig.json",
236
+ "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"]
237
+ }
238
+ `;
239
+
240
+ const PKG_VITE_CONFIG = `import react from '@vitejs/plugin-react';
241
+ import { defineConfig } from 'vite-plus';
242
+
243
+ export default defineConfig({
244
+ plugins: [react()],
245
+ pack: {
246
+ entry: ['src/**/*.{ts,tsx}', '!src/**/*.test.{ts,tsx}'],
247
+ format: 'esm',
248
+ dts: true,
249
+ outDir: 'dist',
250
+ unbundle: true,
251
+ },
252
+ test: {
253
+ globals: true,
254
+ include: ['src/**/*.test.{ts,tsx}'],
255
+ },
256
+ staged: {
257
+ '*': 'vp check --fix',
258
+ },
259
+ });
260
+ `;
261
+
262
+ const PKG_CN = `export const cn = (
263
+ ...classes: Array<string | false | null | undefined>
264
+ ): string => classes.filter(Boolean).join(' ');
265
+ `;
266
+
267
+ const PKG_CN_TEST = `import { cn } from './cn.ts';
268
+
269
+ test('cn joins truthy class names', () => {
270
+ expect(cn('a', false, 'b', null, undefined, 'c')).toBe('a b c');
271
+ });
272
+ `;
273
+
274
+ const PKG_HELLO = `import { Button } from '@k8o/arte-odyssey';
275
+ import type { FC } from 'react';
276
+
277
+ export type HelloProps = {
278
+ /** Who to greet. */
279
+ name: string;
280
+ };
281
+
282
+ /**
283
+ * Sample component wrapping @k8o/arte-odyssey's Button.
284
+ * Render it inside <ArteOdysseyProvider> (see apps/playground).
285
+ */
286
+ export const Hello: FC<HelloProps> = ({ name }) => (
287
+ <Button>Hello, {name}!</Button>
288
+ );
289
+ `;
290
+
291
+ const PKG_INDEX = `export { cn } from './helpers/cn.ts';
292
+ export { Hello } from './components/hello.tsx';
293
+ export type { HelloProps } from './components/hello.tsx';
294
+ `;
295
+
296
+ // --- apps/playground : private Vite + React app using arte-odyssey -----------
297
+
298
+ const APP_TSCONFIG = `{
299
+ "extends": "../../tsconfig.json",
300
+ "compilerOptions": {
301
+ "types": ["node", "vite/client"]
302
+ },
303
+ "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"]
304
+ }
305
+ `;
306
+
307
+ const APP_VITE_CONFIG = `import tailwindcss from '@tailwindcss/vite';
308
+ import react from '@vitejs/plugin-react';
309
+ import { defineConfig } from 'vite';
310
+
311
+ export default defineConfig({
312
+ plugins: [react(), tailwindcss()],
313
+ });
314
+ `;
315
+
316
+ const APP_INDEX_HTML = `<!doctype html>
317
+ <html lang="en">
318
+ <head>
319
+ <meta charset="UTF-8" />
320
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
321
+ <title>playground</title>
322
+ </head>
323
+ <body>
324
+ <div id="root"></div>
325
+ <script type="module" src="/src/main.tsx"></script>
326
+ </body>
327
+ </html>
328
+ `;
329
+
330
+ const APP_MAIN = `import { StrictMode } from 'react';
331
+ import { createRoot } from 'react-dom/client';
332
+
333
+ import { App } from './app.tsx';
334
+
335
+ import './styles.css';
336
+
337
+ const root = document.querySelector('#root');
338
+ if (root) {
339
+ createRoot(root).render(
340
+ <StrictMode>
341
+ <App />
342
+ </StrictMode>,
343
+ );
344
+ }
345
+ `;
346
+
347
+ // The playground demonstrates the @k8o/arte-odyssey stack (provider + tailwind).
348
+ // To preview your own package here, add it as a `workspace:*` dependency and
349
+ // import it — its `source` export condition resolves to TS source in dev.
350
+ const APP_APP = `import { ArteOdysseyProvider, Button } from '@k8o/arte-odyssey';
351
+
352
+ export const App = () => (
353
+ <ArteOdysseyProvider>
354
+ <main className="grid min-h-dvh place-items-center gap-4 p-8">
355
+ <h1 className="text-2xl font-bold">@PKG_NAME@ playground</h1>
356
+ <Button>Hello, world!</Button>
357
+ </main>
358
+ </ArteOdysseyProvider>
359
+ );
360
+ `;
361
+
362
+ const APP_STYLES = `@import 'tailwindcss';
363
+ @import '@k8o/arte-odyssey/styles.css';
364
+ `;
365
+
366
+ const APP_VITE_ENV = `/// <reference types="vite/client" />
367
+ `;
368
+
369
+ export const produceWeb = (options: GenerateOptions) => {
370
+ const { bare, repo } = coords(options.name);
371
+ const description = options.description ?? '';
372
+
373
+ const rootPackageJson = {
374
+ name: bare,
375
+ version: '0.0.0',
376
+ private: true,
377
+ description,
378
+ license: 'MIT',
379
+ author: 'k8o (https://github.com/k35o)',
380
+ repository: {
381
+ type: 'git',
382
+ url: `git+https://github.com/${repo}.git`,
383
+ },
384
+ type: 'module',
385
+ scripts: {
386
+ build: "vp run --filter './packages/*' --filter './apps/*' build",
387
+ dev: 'vp run --filter ./apps/playground dev',
388
+ test: 'vp run -r test',
389
+ typecheck: 'vp run -r typecheck',
390
+ check: 'vp check',
391
+ 'check:write': 'vp check --fix',
392
+ changeset: 'changeset',
393
+ version: 'changeset version',
394
+ release: 'pnpm build && changeset publish',
395
+ ready: 'vp fmt && vp lint && vp run -r test',
396
+ },
397
+ devDependencies: {
398
+ '@changesets/changelog-github': 'catalog:',
399
+ '@changesets/cli': 'catalog:',
400
+ '@commitlint/cli': 'catalog:',
401
+ '@commitlint/config-conventional': 'catalog:',
402
+ '@k8o/oxc-config': 'catalog:',
403
+ '@types/node': 'catalog:',
404
+ typescript: 'catalog:',
405
+ 'vite-plus': 'catalog:',
406
+ },
407
+ engines: {
408
+ node: '>=24.13.0',
409
+ },
410
+ packageManager: 'pnpm@11.5.1',
411
+ };
412
+
413
+ const libPackageJson = {
414
+ name: options.name,
415
+ version: '0.0.0',
416
+ description,
417
+ keywords: [] as string[],
418
+ homepage: `https://github.com/${repo}#readme`,
419
+ bugs: `https://github.com/${repo}/issues`,
420
+ license: 'MIT',
421
+ author: 'k8o (https://github.com/k35o)',
422
+ repository: {
423
+ type: 'git',
424
+ url: `git+https://github.com/${repo}.git`,
425
+ directory: `packages/${bare}`,
426
+ },
427
+ files: ['dist'],
428
+ type: 'module',
429
+ sideEffects: false,
430
+ exports: {
431
+ '.': {
432
+ source: './src/index.ts',
433
+ import: {
434
+ types: './dist/index.d.mts',
435
+ default: './dist/index.mjs',
436
+ },
437
+ },
438
+ './package.json': './package.json',
439
+ },
440
+ publishConfig: {
441
+ access: 'public',
442
+ provenance: true,
443
+ },
444
+ scripts: {
445
+ build: 'vp pack',
446
+ test: 'vp test',
447
+ typecheck: 'tsc --noEmit',
448
+ check: 'vp check',
449
+ 'check:write': 'vp check --fix',
450
+ },
451
+ devDependencies: {
452
+ '@k8o/arte-odyssey': 'catalog:',
453
+ '@types/react': 'catalog:',
454
+ '@types/react-dom': 'catalog:',
455
+ '@vitejs/plugin-react': 'catalog:',
456
+ react: 'catalog:',
457
+ 'react-dom': 'catalog:',
458
+ 'vite-plus': 'catalog:',
459
+ },
460
+ peerDependencies: {
461
+ '@k8o/arte-odyssey': '>=10',
462
+ react: '>=19',
463
+ 'react-dom': '>=19',
464
+ },
465
+ };
466
+
467
+ const appPackageJson = {
468
+ name: 'playground',
469
+ version: '0.0.0',
470
+ private: true,
471
+ type: 'module',
472
+ scripts: {
473
+ dev: 'vite',
474
+ build: 'vite build',
475
+ preview: 'vite preview',
476
+ typecheck: 'tsc --noEmit',
477
+ },
478
+ dependencies: {
479
+ '@k8o/arte-odyssey': 'catalog:',
480
+ react: 'catalog:',
481
+ 'react-dom': 'catalog:',
482
+ },
483
+ devDependencies: {
484
+ '@tailwindcss/vite': 'catalog:',
485
+ '@types/react': 'catalog:',
486
+ '@types/react-dom': 'catalog:',
487
+ '@vitejs/plugin-react': 'catalog:',
488
+ tailwindcss: 'catalog:',
489
+ typescript: 'catalog:',
490
+ vite: 'catalog:',
491
+ },
492
+ };
493
+
494
+ const readme = `# ${bare}
495
+
496
+ ${description}
497
+
498
+ A React component library built on [@k8o/arte-odyssey](https://github.com/k35o/arte-odyssey).
499
+
500
+ ## Layout
501
+
502
+ \`\`\`
503
+ packages/${bare} # the publishable library (${options.name})
504
+ apps/playground # local Vite app to preview it
505
+ \`\`\`
506
+
507
+ ## Develop
508
+
509
+ \`\`\`sh
510
+ pnpm install
511
+ pnpm dev # run the playground app
512
+ pnpm check # fmt + lint
513
+ pnpm typecheck
514
+ pnpm test
515
+ pnpm build # build every package and app
516
+ \`\`\`
517
+
518
+ ## Release
519
+
520
+ Versioned and published with [Changesets](https://github.com/changesets/changesets).
521
+ Merging to \`main\` lets the release workflow open a version PR and publish to npm.
522
+ `;
523
+
524
+ return {
525
+ files: {
526
+ 'package.json': `${JSON.stringify(rootPackageJson, null, 2)}\n`,
527
+ 'pnpm-workspace.yaml': PNPM_WORKSPACE,
528
+ '.npmrc': NPMRC,
529
+ '.gitignore': GITIGNORE,
530
+ 'mise.toml': MISE_TOML,
531
+ 'tsconfig.json': TSCONFIG,
532
+ 'vite.config.ts': ROOT_VITE_CONFIG,
533
+ 'commitlint.config.js': COMMITLINT,
534
+ 'renovate.json': RENOVATE,
535
+ LICENSE,
536
+ 'README.md': readme,
537
+ '.changeset': {
538
+ 'config.json': changesetConfig(repo),
539
+ 'README.md': CHANGESET_README,
540
+ },
541
+ '.github': {
542
+ 'composite-actions': {
543
+ install: {
544
+ 'action.yml': INSTALL_ACTION,
545
+ },
546
+ },
547
+ workflows: {
548
+ 'ci.yml': CI_YML,
549
+ 'release.yml': RELEASE_YML,
550
+ },
551
+ },
552
+ packages: {
553
+ [bare]: {
554
+ 'package.json': `${JSON.stringify(libPackageJson, null, 2)}\n`,
555
+ 'tsconfig.json': PKG_TSCONFIG,
556
+ 'vite.config.ts': PKG_VITE_CONFIG,
557
+ src: {
558
+ 'index.ts': PKG_INDEX,
559
+ components: {
560
+ 'hello.tsx': PKG_HELLO,
561
+ },
562
+ helpers: {
563
+ 'cn.ts': PKG_CN,
564
+ 'cn.test.ts': PKG_CN_TEST,
565
+ },
566
+ },
567
+ },
568
+ },
569
+ apps: {
570
+ playground: {
571
+ 'package.json': `${JSON.stringify(appPackageJson, null, 2)}\n`,
572
+ 'tsconfig.json': APP_TSCONFIG,
573
+ 'vite.config.ts': APP_VITE_CONFIG,
574
+ 'index.html': APP_INDEX_HTML,
575
+ src: {
576
+ 'main.tsx': APP_MAIN,
577
+ 'app.tsx': APP_APP.replaceAll('@PKG_NAME@', options.name),
578
+ 'styles.css': APP_STYLES,
579
+ 'vite-env.d.ts': APP_VITE_ENV,
580
+ },
581
+ },
582
+ },
583
+ },
584
+ };
585
+ };