create-keboola-app 0.0.0 → 0.1.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 +43 -0
- package/dist/cli.js +27 -0
- package/dist/extract.js +11 -1
- package/dist/template/oxlint.config.ts +2 -4
- package/dist/template/package.json +5 -5
- package/dist/template/src/components/AppChrome.tsx +1 -1
- package/package.json +19 -17
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# create-keboola-app
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A generated app pins `nwsapi`, so a userEvent test does not hang.
|
|
8
|
+
|
|
9
|
+
nwsapi 2.2.26 — jsdom's selector engine — answers `:modal`, `:fullscreen` and `:open` through a helper that falls back to the element's own `matches`. Under jsdom that is nwsapi itself, so the two recurse, and `:modal` doubles the work per level through its `|| isFullscreen` branch. `@floating-ui/dom` probes `:modal` on every popper placement, which is once per dropdown or tooltip interaction: measured at 9s where 2.2.25 takes 26ms, so a test driving a menu with `userEvent` hits its timeout instead of asserting.
|
|
10
|
+
|
|
11
|
+
Generated apps now ship a `pnpm-workspace.yaml` holding `nwsapi` at 2.2.25 — the only file pnpm reads `overrides` from. It carries the reason and the condition for removing it.
|
|
12
|
+
|
|
13
|
+
- Name the generated app in its own chrome. The breadcrumb was hard-coded to the template's own
|
|
14
|
+
name, so every scaffold shipped labelled after the template rather than the name the user passed
|
|
15
|
+
— the first thing on screen, and the one place the app names itself back to the reader. The
|
|
16
|
+
extract step now parameterises it alongside the page title and the store's storage keys.
|
|
17
|
+
|
|
18
|
+
A `boilerplate-identity` sanitize pattern makes this class of leak a red build instead of a
|
|
19
|
+
silent default: any un-templated mention of the source app's name fails extraction, so a rewrite
|
|
20
|
+
that stops matching is caught rather than shipped.
|
|
21
|
+
|
|
22
|
+
## 0.1.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- Initial release. `npm create keboola-app` scaffolds a Keboola-shaped frontend app — Vite,
|
|
27
|
+
React 19, TanStack Router/Query/Form, Zustand, `@keboola/design` with
|
|
28
|
+
`@keboola/brand-registry`, oxlint/oxfmt and vitest — from a minimal shell plus the blocks you
|
|
29
|
+
pick: `kpi`, `chart`, `grid`, `crud`, `assistant`. Blocks are independent, so any combination
|
|
30
|
+
compiles.
|
|
31
|
+
|
|
32
|
+
Interactive by default, and fully flag-driven for agents and CI:
|
|
33
|
+
`--name`, `--blocks`, `--yes`, `--json`, `--workspace`. Nothing prompts without a terminal,
|
|
34
|
+
every failure exits non-zero with the reason on stderr, and nothing is fetched at scaffold
|
|
35
|
+
time. The generated app carries its own `README.md` and `AGENTS.md`, which point at
|
|
36
|
+
`node_modules/@keboola/*/AGENTS.md` so an agent reads the contract for the version actually
|
|
37
|
+
installed.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- In `--workspace` mode the generated app inherits the host repo's `engines`, and the monorepo entry point is `pnpm scaffold:app`.
|
|
42
|
+
|
|
43
|
+
The template carries a standalone `engines.node` floor for apps generated outside a monorepo. Inside one that floor is not a default but a disagreement: it was the lowest value across every workspace, so syncpack wanted to rewrite the other 21 manifests and `pnpm deps:check` went red on a freshly scaffolded app. Workspace mode now reads the root manifest's `engines` instead, and the gate asserts it.
|
package/dist/cli.js
CHANGED
|
@@ -196,6 +196,29 @@ const writeManifest = (opts) => {
|
|
|
196
196
|
};
|
|
197
197
|
writeFileSync(join(targetDir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
198
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* `pnpm-workspace.yaml` for a standalone app.
|
|
201
|
+
*
|
|
202
|
+
* - It is the only file pnpm 11 reads `overrides` from; the `pnpm` field in `package.json` is
|
|
203
|
+
* ignored with a warning.
|
|
204
|
+
* - Written only outside a monorepo. Under `--workspace` the app inherits the root's file, and a
|
|
205
|
+
* nested one would declare a second workspace root inside the first.
|
|
206
|
+
*/
|
|
207
|
+
const writeWorkspaceConfig = (targetDir) => {
|
|
208
|
+
writeFileSync(join(targetDir, "pnpm-workspace.yaml"), `# pnpm reads \`overrides\` from this file only — the \`pnpm\` field in package.json is ignored.
|
|
209
|
+
overrides:
|
|
210
|
+
# jsdom's selector engine, held at the last release that does not re-enter itself.
|
|
211
|
+
#
|
|
212
|
+
# - 2.2.26 answers \`:modal\`, \`:fullscreen\` and \`:open\` through a \`matchesNative\` helper
|
|
213
|
+
# that falls back to the element's own \`matches\` — under jsdom that is nwsapi again, so the
|
|
214
|
+
# two recurse, and \`:modal\` doubles the work per level via its \`|| isFullscreen\` branch.
|
|
215
|
+
# - \`@floating-ui/dom\` probes \`:modal\` on every popper placement, so one dropdown or tooltip
|
|
216
|
+
# interaction costs ~9s instead of ~30ms and a userEvent test hits its timeout.
|
|
217
|
+
# - Remove the pin once nwsapi stops resolving its own \`matches\` back into itself:
|
|
218
|
+
# https://github.com/dperini/nwsapi
|
|
219
|
+
nwsapi: 2.2.25
|
|
220
|
+
`);
|
|
221
|
+
};
|
|
199
222
|
const scaffold = (opts) => {
|
|
200
223
|
const { templateDir, targetDir, appName, blocks, workspace } = opts;
|
|
201
224
|
if (existsSync(targetDir)) throw new Error(`${targetDir} already exists. Choose another name or remove the directory first.`);
|
|
@@ -231,6 +254,10 @@ const scaffold = (opts) => {
|
|
|
231
254
|
packageName
|
|
232
255
|
});
|
|
233
256
|
written.push("package.json");
|
|
257
|
+
if (!workspace) {
|
|
258
|
+
writeWorkspaceConfig(targetDir);
|
|
259
|
+
written.push("pnpm-workspace.yaml");
|
|
260
|
+
}
|
|
234
261
|
return {
|
|
235
262
|
path: targetDir,
|
|
236
263
|
appName,
|
package/dist/extract.js
CHANGED
|
@@ -47,9 +47,19 @@ const decideRegion = (regionId) => {
|
|
|
47
47
|
if (id === null || !isBlockId(id)) throw new Error(`unknown region id "${regionId}" — expected "internal" or "block:<blockId>"`);
|
|
48
48
|
return "keep-with-markers";
|
|
49
49
|
};
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Per-file rewrites that turn this app's identity into placeholders.
|
|
52
|
+
*
|
|
53
|
+
* - Every rewrite has a matching assertion in `extract.test.ts`, and the sanitize scan fails
|
|
54
|
+
* the build on any surviving mention of this app's name — a rewrite that stops matching is
|
|
55
|
+
* a red build, not a scaffold that quietly names itself after the template.
|
|
56
|
+
* - Match structurally, not by the literal name. The name must not appear here: the packed
|
|
57
|
+
* tarball is scanned too, and a rewrite that spells its own needle makes the scanner match
|
|
58
|
+
* its own implementation.
|
|
59
|
+
*/
|
|
51
60
|
const parameterise = (file, text) => {
|
|
52
61
|
if (file === "index.html") return text.replace(/<title>[^<]*<\/title>/, "<title>__APP_TITLE__</title>");
|
|
62
|
+
if (file === "src/components/AppChrome.tsx") return text.replace(/(<Link to="\/">)[^<]*(<\/Link>)/, "$1__APP_TITLE__$2");
|
|
53
63
|
if (file === "src/store.ts") return text.replaceAll(/'boilerplate\/([^']+)'/g, "'__APP_NAME__/$1'");
|
|
54
64
|
return text;
|
|
55
65
|
};
|
|
@@ -19,10 +19,8 @@ export default defineConfig({
|
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
21
|
jsPlugins: [
|
|
22
|
-
// Local design-system + brand-safety lint rules
|
|
23
|
-
//
|
|
24
|
-
// `design/<rule>` in lint output. Boilerplate is the canonical scaffold
|
|
25
|
-
// cloned by `pnpm scaffold:app`, so every generated app inherits these.
|
|
22
|
+
// Local design-system + brand-safety lint rules. Aliased to `design` so rule
|
|
23
|
+
// IDs read `design/<rule>` in lint output.
|
|
26
24
|
{ name: 'design', specifier: '@keboola/eslint-plugin-design' },
|
|
27
25
|
],
|
|
28
26
|
rules: {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"type-check": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@keboola/brand-registry": "^1.9.
|
|
19
|
-
"@keboola/design": "^11.0
|
|
18
|
+
"@keboola/brand-registry": "^1.9.4",
|
|
19
|
+
"@keboola/design": "^11.3.0",
|
|
20
20
|
"@tanstack/react-form": "^1.29.1",
|
|
21
21
|
"@tanstack/react-query": "^5.96.2",
|
|
22
22
|
"@tanstack/react-router": "^1.120.4",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"zustand": "^5.0.8"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@keboola/eslint-plugin-design": "^0.4.
|
|
32
|
-
"@keboola/oxlint-config": "^10.0.
|
|
33
|
-
"@keboola/tsconfig": "^0.2.
|
|
31
|
+
"@keboola/eslint-plugin-design": "^0.4.3",
|
|
32
|
+
"@keboola/oxlint-config": "^10.0.2",
|
|
33
|
+
"@keboola/tsconfig": "^0.2.1",
|
|
34
34
|
"@tailwindcss/vite": "^4.3.3",
|
|
35
35
|
"@tanstack/react-query-devtools": "^5.96.2",
|
|
36
36
|
"@testing-library/jest-dom": "^6.8.0",
|
|
@@ -89,7 +89,7 @@ export function AppChrome({ children }: { children: ReactNode }) {
|
|
|
89
89
|
<Navbar.Menu marker="forward-slash">
|
|
90
90
|
<Navbar.MenuItem>
|
|
91
91
|
<Navbar.MenuButton asChild>
|
|
92
|
-
<Link to="/">
|
|
92
|
+
<Link to="/">__APP_TITLE__</Link>
|
|
93
93
|
</Navbar.MenuButton>
|
|
94
94
|
</Navbar.MenuItem>
|
|
95
95
|
</Navbar.Menu>
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-keboola-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Scaffold a Keboola-shaped frontend app — Vite + React 19 + TanStack Router/Query/Form + Zustand + @keboola/design — from a minimal shell plus the blocks you pick",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": "./dist/cli.js",
|
|
8
7
|
"files": [
|
|
9
8
|
"dist",
|
|
10
9
|
"README.md",
|
|
@@ -16,17 +15,6 @@
|
|
|
16
15
|
"engines": {
|
|
17
16
|
"node": ">=20"
|
|
18
17
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsdown && node dist/extract.js",
|
|
21
|
-
"lint": "pnpm lint:format && pnpm lint:oxlint",
|
|
22
|
-
"lint:fix": "oxlint --fix . && oxfmt",
|
|
23
|
-
"lint:format": "oxfmt --check",
|
|
24
|
-
"lint:oxlint": "oxlint . --quiet",
|
|
25
|
-
"knip:check": "pnpm --dir ../.. exec knip --workspace packages/create-keboola-app",
|
|
26
|
-
"knip:fix": "pnpm --dir ../.. exec knip --workspace packages/create-keboola-app --fix-type types,exports,files",
|
|
27
|
-
"test": "vitest run --passWithNoTests --reporter=dot",
|
|
28
|
-
"type-check": "tsc"
|
|
29
|
-
},
|
|
30
18
|
"repository": {
|
|
31
19
|
"type": "git",
|
|
32
20
|
"url": "https://github.com/keboola/ui.git",
|
|
@@ -50,8 +38,6 @@
|
|
|
50
38
|
"@tanstack/router-generator": "^1.120.4"
|
|
51
39
|
},
|
|
52
40
|
"devDependencies": {
|
|
53
|
-
"@keboola/oxlint-config": "workspace:^",
|
|
54
|
-
"@keboola/tsconfig": "workspace:^",
|
|
55
41
|
"@types/node": "24.12.0",
|
|
56
42
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
57
43
|
"oxfmt": "^0.57.0",
|
|
@@ -59,6 +45,22 @@
|
|
|
59
45
|
"tsdown": "^0.22.14",
|
|
60
46
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
61
47
|
"vite": "^8.1.5",
|
|
62
|
-
"vitest": "^4.1.10"
|
|
48
|
+
"vitest": "^4.1.10",
|
|
49
|
+
"@keboola/oxlint-config": "^10.0.2",
|
|
50
|
+
"@keboola/tsconfig": "^0.2.1"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsdown && node dist/extract.js",
|
|
54
|
+
"lint": "pnpm lint:format && pnpm lint:oxlint",
|
|
55
|
+
"lint:fix": "oxlint --fix . && oxfmt",
|
|
56
|
+
"lint:format": "oxfmt --check",
|
|
57
|
+
"lint:oxlint": "oxlint . --quiet",
|
|
58
|
+
"knip:check": "pnpm --dir ../.. exec knip --workspace packages/create-keboola-app",
|
|
59
|
+
"knip:fix": "pnpm --dir ../.. exec knip --workspace packages/create-keboola-app --fix-type types,exports,files",
|
|
60
|
+
"test": "vitest run --passWithNoTests --reporter=dot",
|
|
61
|
+
"type-check": "tsc"
|
|
62
|
+
},
|
|
63
|
+
"bin": {
|
|
64
|
+
"create-keboola-app": "./dist/cli.js"
|
|
63
65
|
}
|
|
64
|
-
}
|
|
66
|
+
}
|