create-keboola-app 0.0.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.
Files changed (71) hide show
  1. package/README.md +79 -0
  2. package/dist/blocks-DYU5Ke29.js +147 -0
  3. package/dist/cli.js +451 -0
  4. package/dist/extract.js +133 -0
  5. package/dist/regions-B3r5LhXl.js +95 -0
  6. package/dist/sanitize-CKs8pAzZ.js +158 -0
  7. package/dist/sanitize.js +2 -0
  8. package/dist/template/AGENTS.md +133 -0
  9. package/dist/template/README.md +56 -0
  10. package/dist/template/_env.example +6 -0
  11. package/dist/template/_gitignore +7 -0
  12. package/dist/template/_oxfmtrc.json +18 -0
  13. package/dist/template/index.html +13 -0
  14. package/dist/template/oxlint.config.ts +43 -0
  15. package/dist/template/package.json +53 -0
  16. package/dist/template/public/favicon.svg +10 -0
  17. package/dist/template/src/components/AppChrome.tsx +201 -0
  18. package/dist/template/src/components/AssistantPanel.test.tsx +65 -0
  19. package/dist/template/src/components/AssistantPanel.tsx +96 -0
  20. package/dist/template/src/components/AssistantToggle.tsx +37 -0
  21. package/dist/template/src/components/ColorModeToggle.tsx +15 -0
  22. package/dist/template/src/components/HomePrompt.tsx +35 -0
  23. package/dist/template/src/components/KpiCard.test.tsx +20 -0
  24. package/dist/template/src/components/KpiCard.tsx +24 -0
  25. package/dist/template/src/components/SelectionFloater.test.tsx +73 -0
  26. package/dist/template/src/components/SelectionFloater.tsx +88 -0
  27. package/dist/template/src/components/SidebarModeMenu.test.tsx +68 -0
  28. package/dist/template/src/components/SidebarModeMenu.tsx +54 -0
  29. package/dist/template/src/index.css +26 -0
  30. package/dist/template/src/main.tsx +57 -0
  31. package/dist/template/src/nav.test.ts +25 -0
  32. package/dist/template/src/nav.ts +38 -0
  33. package/dist/template/src/overview-sections.test.tsx +36 -0
  34. package/dist/template/src/overview-sections.tsx +40 -0
  35. package/dist/template/src/recipes/Chart.test.tsx +24 -0
  36. package/dist/template/src/recipes/Chart.tsx +56 -0
  37. package/dist/template/src/recipes/DataGrid.test.tsx +47 -0
  38. package/dist/template/src/recipes/DataGrid.tsx +151 -0
  39. package/dist/template/src/routes/-fixtures.ts +113 -0
  40. package/dist/template/src/routes/-hooks.ts +15 -0
  41. package/dist/template/src/routes/-index.test.tsx +123 -0
  42. package/dist/template/src/routes/__root.tsx +20 -0
  43. package/dist/template/src/routes/index.tsx +95 -0
  44. package/dist/template/src/routes/tokens/$id.tsx +95 -0
  45. package/dist/template/src/routes/tokens/-fixtures.test.ts +60 -0
  46. package/dist/template/src/routes/tokens/-fixtures.ts +91 -0
  47. package/dist/template/src/routes/tokens/-hooks.ts +37 -0
  48. package/dist/template/src/routes/tokens/-index.test.tsx +75 -0
  49. package/dist/template/src/routes/tokens/-nav.ts +17 -0
  50. package/dist/template/src/routes/tokens/-types.ts +22 -0
  51. package/dist/template/src/routes/tokens/index.tsx +182 -0
  52. package/dist/template/src/routes/tokens/new.tsx +163 -0
  53. package/dist/template/src/sections/chart.tsx +34 -0
  54. package/dist/template/src/sections/grid.tsx +21 -0
  55. package/dist/template/src/sections/kpi.tsx +36 -0
  56. package/dist/template/src/slices/assistant.ts +68 -0
  57. package/dist/template/src/slices/shell.ts +54 -0
  58. package/dist/template/src/store.test.tsx +92 -0
  59. package/dist/template/src/store.ts +67 -0
  60. package/dist/template/src/test-setup.ts +11 -0
  61. package/dist/template/src/vite-env.d.ts +1 -0
  62. package/dist/template/templates/agent-safety/README.md +14 -0
  63. package/dist/template/templates/agent-safety/aiexclude +25 -0
  64. package/dist/template/templates/agent-safety/claude-code.settings.json +45 -0
  65. package/dist/template/templates/agent-safety/cursorignore +25 -0
  66. package/dist/template/tsconfig.json +10 -0
  67. package/dist/template/vercel.json +4 -0
  68. package/dist/template/vite.config.ts +21 -0
  69. package/dist/template/vitest.config.ts +21 -0
  70. package/dist/template/workspace-deps.json +7 -0
  71. package/package.json +64 -0
@@ -0,0 +1,68 @@
1
+ import type { StateCreator } from 'zustand';
2
+
3
+ import type { AppState } from '../store';
4
+
5
+ type ChatMessage = {
6
+ id: string;
7
+ role: 'user' | 'assistant';
8
+ text: string;
9
+ };
10
+
11
+ export type AssistantSlice = {
12
+ /**
13
+ * One transcript for the whole app. The home page's prompt input and the rail's
14
+ * own composer both call `send`, so sending from the page is a handoff into the
15
+ * rail rather than a second, parallel conversation.
16
+ */
17
+ messages: ChatMessage[];
18
+ send: (text: string) => void;
19
+ fullscreen: boolean;
20
+ toggleFullscreen: () => void;
21
+ };
22
+
23
+ const cannedReply = (question: string) => `**${question}**
24
+
25
+ Consumption has climbed every month since January and June is running at 8,420
26
+ of the 8,500 budgeted — a 1% margin. On this trend July goes over.
27
+
28
+ | Component | Share of budget |
29
+ | --- | --- |
30
+ | Database extractor | 55% |
31
+ | Python transformation | 28% |
32
+ | Snowflake writer | 12% |
33
+ | Daily orchestration | 5% |
34
+
35
+ \`\`\`mermaid
36
+ flowchart LR
37
+ A[Storage tables] --> B[Transformation]
38
+ B --> C[Credit ledger]
39
+ C --> D[This overview]
40
+ \`\`\``;
41
+
42
+ /** The mutator tuple mirrors `store.ts`'s middleware stack, outside-in. */
43
+ type Mutators = [['zustand/devtools', never], ['zustand/persist', unknown]];
44
+
45
+ /** The assistant's own state: the transcript, and whether it fills the viewport. */
46
+ export const createAssistantSlice: StateCreator<AppState, Mutators, [], AssistantSlice> = (
47
+ set,
48
+ ) => ({
49
+ messages: [],
50
+ send: (text) =>
51
+ set(
52
+ (s) => ({
53
+ messages: [
54
+ ...s.messages,
55
+ { id: crypto.randomUUID(), role: 'user', text },
56
+ // A canned reply, not a backend call — swap the whole branch for a
57
+ // real request. It answers in Markdown (including a ```mermaid fence)
58
+ // because that is what an assistant actually returns, and rendering
59
+ // that is the rail's job: see AssistantPanel.
60
+ { id: crypto.randomUUID(), role: 'assistant', text: cannedReply(text) },
61
+ ],
62
+ }),
63
+ false,
64
+ 'send',
65
+ ),
66
+ fullscreen: false,
67
+ toggleFullscreen: () => set((s) => ({ fullscreen: !s.fullscreen }), false, 'toggleFullscreen'),
68
+ });
@@ -0,0 +1,54 @@
1
+ import type { StateCreator } from 'zustand';
2
+
3
+ import type { SidebarMode } from '@keboola/design';
4
+
5
+ import type { AppState } from '../store';
6
+
7
+ /**
8
+ * The modes `sidebarMode` may hold, as a runtime value.
9
+ *
10
+ * - Keyed by `SidebarMode`, so adding a mode to the union fails the build here until
11
+ * it is listed — a hand-written array would silently start rejecting the new mode.
12
+ * - The values carry nothing; the keys are the whole point.
13
+ */
14
+ const SIDEBAR_MODES: Record<SidebarMode, true> = {
15
+ expanded: true,
16
+ collapsed: true,
17
+ 'expand-on-hover': true,
18
+ };
19
+
20
+ export const isSidebarMode = (value: unknown): value is SidebarMode =>
21
+ typeof value === 'string' && Object.hasOwn(SIDEBAR_MODES, value);
22
+
23
+ export type ShellSlice = {
24
+ /**
25
+ * The left rail's mode.
26
+ *
27
+ * - Lives here, not inside `SidebarProvider`, so it survives a reload; a layout
28
+ * preference that resets reads as a broken control.
29
+ * - `AppChrome` drives the provider from it (`leftMode` + `onLeftModeChange`).
30
+ * - `SidebarModeMenu` is the UI that writes it.
31
+ */
32
+ sidebarMode: SidebarMode;
33
+ setSidebarMode: (mode: SidebarMode) => void;
34
+ helpVisible: boolean;
35
+ toggleHelp: () => void;
36
+ setHelpVisible: (visible: boolean) => void;
37
+ };
38
+
39
+ /**
40
+ * The mutator tuple mirrors `store.ts`'s middleware stack, outside-in.
41
+ *
42
+ * - Without it `set` loses its third argument and every `set(partial, false, 'name')`
43
+ * call fails to type-check.
44
+ */
45
+ type Mutators = [['zustand/devtools', never], ['zustand/persist', unknown]];
46
+
47
+ /** The chrome's own state: which rail mode is active, and whether help copy shows. */
48
+ export const createShellSlice: StateCreator<AppState, Mutators, [], ShellSlice> = (set) => ({
49
+ sidebarMode: 'expanded',
50
+ setSidebarMode: (mode) => set({ sidebarMode: mode }, false, 'setSidebarMode'),
51
+ helpVisible: true,
52
+ toggleHelp: () => set((s) => ({ helpVisible: !s.helpVisible }), false, 'toggleHelp'),
53
+ setHelpVisible: (visible) => set({ helpVisible: visible }, false, 'setHelpVisible'),
54
+ });
@@ -0,0 +1,92 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { beforeEach, describe, expect, it } from 'vitest';
4
+
5
+ import { Button } from '@keboola/design';
6
+
7
+ import { UI_SETTINGS_STORAGE_KEY, useAppStore } from './store';
8
+
9
+ function HelpToggleHarness() {
10
+ const helpVisible = useAppStore((s) => s.helpVisible);
11
+ const toggleHelp = useAppStore((s) => s.toggleHelp);
12
+ return (
13
+ <div>
14
+ <span data-testid="state">{helpVisible ? 'visible' : 'hidden'}</span>
15
+ <Button onClick={toggleHelp}>Toggle</Button>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ describe('useAppStore', () => {
21
+ beforeEach(() => {
22
+ useAppStore.setState({ helpVisible: true });
23
+ // #region block:assistant
24
+ useAppStore.setState({ messages: [] });
25
+ // #endregion block:assistant
26
+ });
27
+
28
+ it('toggles help visibility on click', async () => {
29
+ const user = userEvent.setup();
30
+ render(<HelpToggleHarness />);
31
+
32
+ expect(screen.getByTestId('state')).toHaveTextContent('visible');
33
+
34
+ await user.click(screen.getByRole('button', { name: 'Toggle' }));
35
+ expect(screen.getByTestId('state')).toHaveTextContent('hidden');
36
+
37
+ await user.click(screen.getByRole('button', { name: 'Toggle' }));
38
+ expect(screen.getByTestId('state')).toHaveTextContent('visible');
39
+ });
40
+
41
+ it('setHelpVisible writes the value directly', () => {
42
+ useAppStore.getState().setHelpVisible(false);
43
+ expect(useAppStore.getState().helpVisible).toBe(false);
44
+ });
45
+
46
+ // #region block:assistant
47
+ it('appends a user message and an assistant reply per send', () => {
48
+ useAppStore.getState().send('why is June over budget?');
49
+
50
+ const { messages } = useAppStore.getState();
51
+ expect(messages).toHaveLength(2);
52
+ expect(messages[0]).toMatchObject({ role: 'user', text: 'why is June over budget?' });
53
+ expect(messages[1]?.role).toBe('assistant');
54
+ });
55
+ // #endregion block:assistant
56
+
57
+ // `partialize` is the whole reason persistence is safe here, and it is invisible
58
+ // at every call site — widening it would silently restore a stale transcript and
59
+ // pin `fullscreen` open across sessions. Assert the written shape, not just that
60
+ // `sidebarMode` survives.
61
+ it('persists the layout preference and nothing else', () => {
62
+ const { setSidebarMode } = useAppStore.getState();
63
+ // #region block:assistant
64
+ const { send, toggleFullscreen } = useAppStore.getState();
65
+ send('a prompt worth not resurrecting');
66
+ toggleFullscreen();
67
+ // #endregion block:assistant
68
+ setSidebarMode('collapsed');
69
+
70
+ const persisted = JSON.parse(localStorage.getItem(UI_SETTINGS_STORAGE_KEY) ?? '{}');
71
+
72
+ expect(persisted.state).toEqual({ sidebarMode: 'collapsed' });
73
+ });
74
+
75
+ it.each([
76
+ ['restores a mode it recognises', 'collapsed', 'collapsed'],
77
+ // A mode renamed or dropped by a later build leaves this behind in a returning
78
+ // visitor's storage. Unguarded it reaches `SidebarProvider`, which matches no
79
+ // state branch and strands the rail at icon width.
80
+ ['falls back when the stored mode is not a mode', 'expand-on-click', 'expanded'],
81
+ ])('%s', async (_name, stored, expected) => {
82
+ useAppStore.setState({ sidebarMode: 'expanded' });
83
+ localStorage.setItem(
84
+ UI_SETTINGS_STORAGE_KEY,
85
+ JSON.stringify({ state: { sidebarMode: stored }, version: 0 }),
86
+ );
87
+
88
+ await useAppStore.persist.rehydrate();
89
+
90
+ expect(useAppStore.getState().sidebarMode).toBe(expected);
91
+ });
92
+ });
@@ -0,0 +1,67 @@
1
+ import { create } from 'zustand';
2
+ import { devtools, persist } from 'zustand/middleware';
3
+
4
+ // #region block:assistant
5
+ import type { AssistantSlice } from './slices/assistant';
6
+ import { createAssistantSlice } from './slices/assistant';
7
+ // #endregion block:assistant
8
+ import type { ShellSlice } from './slices/shell';
9
+ import { createShellSlice, isSidebarMode } from './slices/shell';
10
+
11
+ /** Where the persisted slice lives. */
12
+ export const UI_SETTINGS_STORAGE_KEY = '__APP_NAME__/ui-settings';
13
+
14
+ /** Every slice the app carries, merged into one flat store. */
15
+ export type AppState = ShellSlice &
16
+ // #region block:assistant
17
+ AssistantSlice &
18
+ // #endregion block:assistant
19
+ // `unknown` is the identity for `&`, so the line above can go without leaving a dangling
20
+ // operator. The comment keeps this alias on three lines, which is what `oxfmt` expects of
21
+ // it in an app that dropped the middle term.
22
+ unknown;
23
+
24
+ export const useAppStore = create<AppState>()(
25
+ devtools(
26
+ // `persist` inside `devtools` so rehydration lands in the devtools timeline as an
27
+ // action. Only `sidebarMode` is written out — see `partialize`.
28
+ persist(
29
+ // Zustand's slice pattern: `(...a)` forwards `set`, `get` and the store API to
30
+ // each creator without naming the three of them per slice.
31
+ (...a) => ({
32
+ ...createShellSlice(...a),
33
+ // #region block:assistant
34
+ ...createAssistantSlice(...a),
35
+ // #endregion block:assistant
36
+ }),
37
+ {
38
+ name: UI_SETTINGS_STORAGE_KEY,
39
+ // No `version` here on purpose. Zustand discards a record whose version does not
40
+ // match and no `migrate` covers, so a bare bump silently resets every returning
41
+ // visitor. If the stored shape ever changes, add `version` and `migrate` in the
42
+ // same change. A value that is merely unrecognised is `merge`'s job, below.
43
+ // The layout preference and nothing else. The default is the whole store, which
44
+ // would restore a stale transcript on reload and pin `fullscreen` open across
45
+ // sessions.
46
+ partialize: (state) => ({ sidebarMode: state.sidebarMode }),
47
+ // Validate at the storage boundary instead of trusting what comes back.
48
+ // - `localStorage` is user-writable, and a value written by an older build
49
+ // outlives the code that wrote it.
50
+ // - An unrecognised mode is not inert: `SidebarProvider` derives the rail's
51
+ // `state` from it, no branch matches, so the rail sticks at icon width with
52
+ // nothing checked in the mode menu — recoverable only by picking a mode.
53
+ // - Falling back to `current` means the in-memory default, so a junk value
54
+ // reads exactly like a first visit.
55
+ merge: (persisted, current) => {
56
+ const stored: unknown =
57
+ typeof persisted === 'object' && persisted !== null
58
+ ? Reflect.get(persisted, 'sidebarMode')
59
+ : undefined;
60
+
61
+ return { ...current, sidebarMode: isSidebarMode(stored) ? stored : current.sidebarMode };
62
+ },
63
+ },
64
+ ),
65
+ { name: '__APP_NAME__/app' },
66
+ ),
67
+ );
@@ -0,0 +1,11 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+
3
+ // jsdom ships no ResizeObserver.
4
+ // - Radix's ScrollArea constructs one in a layout effect.
5
+ // - `Sidebar.Content` and several other `@keboola/design` surfaces build on it, so any
6
+ // test rendering a scrollable design surface throws without this stub.
7
+ globalThis.ResizeObserver ??= class {
8
+ observe() {}
9
+ unobserve() {}
10
+ disconnect() {}
11
+ } as unknown as typeof globalThis.ResizeObserver;
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,14 @@
1
+ # Agent-safety templates
2
+
3
+ Opt-in, per-runtime enforcement stubs for the safety policy. **The policy in [`../../AGENTS.md`](../../AGENTS.md) (section "Safety & secrets (all agents)") is the source of truth** — every agent reads it, regardless of runtime. These files are mechanical projections of the same secrets glob list into each runtime's syntax, for runtimes that support hard enforcement.
4
+
5
+ There is no single cross-agent enforcement file, so pick the one for the agent you actually use and copy it to the repo root (renaming as noted):
6
+
7
+ | Agent runtime | Mechanism | Template | Copy to |
8
+ | --------------- | -------------------------------------------------- | --------------------------- | ----------------------- |
9
+ | Claude Code | `.claude/settings.json` `permissions.{deny,ask}` | `claude-code.settings.json` | `.claude/settings.json` |
10
+ | Cursor | `.cursorignore` (read-block) | `cursorignore` | `.cursorignore` |
11
+ | Gemini CLI | `.aiexclude` | `aiexclude` | `.aiexclude` |
12
+ | (baseline, all) | `.gitignore` already keeps secrets out of the tree | — | — |
13
+
14
+ `AGENTS.md` owns the policy; these templates hold the exhaustive secrets glob list that enforces it. Keep the same set of protected paths across the templates (the glob syntax differs per runtime), and update the summary in `AGENTS.md` if you extend the list.
@@ -0,0 +1,25 @@
1
+ # Copy to .aiexclude at the repo root. Blocks Gemini CLI from reading secrets.
2
+ # Enforces the secrets policy in ../../AGENTS.md (the source of truth);
3
+ # this file is the exhaustive glob list for that policy.
4
+ .env
5
+ .env.*
6
+ *.pem
7
+ *.key
8
+ *.p12
9
+ *.pfx
10
+ *.keystore
11
+ *id_rsa*
12
+ *id_ed25519*
13
+ .ssh/
14
+ .aws/credentials
15
+ .aws/config
16
+ .kube/config
17
+ .npmrc
18
+ .git-credentials
19
+ *token
20
+ *token.*
21
+ *secret*
22
+ *credential*
23
+ *password*
24
+ *apikey*
25
+ .htpasswd
@@ -0,0 +1,45 @@
1
+ {
2
+ "permissions": {
3
+ "deny": [
4
+ "Read(./.env)",
5
+ "Read(./.env.*)",
6
+ "Read(./**/*.pem)",
7
+ "Read(./**/*.key)",
8
+ "Read(./**/*.p12)",
9
+ "Read(./**/*.pfx)",
10
+ "Read(./**/*.keystore)",
11
+ "Read(./**/*id_rsa*)",
12
+ "Read(./**/*id_ed25519*)",
13
+ "Read(./.ssh/**)",
14
+ "Read(./.aws/credentials)",
15
+ "Read(./.aws/config)",
16
+ "Read(./.kube/config)",
17
+ "Read(./.npmrc)",
18
+ "Read(./.git-credentials)",
19
+ "Read(./**/*token)",
20
+ "Read(./**/*token.*)",
21
+ "Read(./**/*secret*)",
22
+ "Read(./**/*credential*)",
23
+ "Read(./**/*password*)",
24
+ "Read(./**/*apikey*)",
25
+ "Read(./**/.htpasswd)"
26
+ ],
27
+ "ask": [
28
+ "Bash(rm:*)",
29
+ "Bash(git reset --hard:*)",
30
+ "Bash(git clean:*)",
31
+ "Bash(git push --force:*)",
32
+ "Bash(git push --force-with-lease:*)",
33
+ "Bash(npm install:*)",
34
+ "Bash(pnpm install:*)",
35
+ "Bash(pnpm add:*)",
36
+ "Bash(yarn install:*)",
37
+ "Bash(yarn add:*)",
38
+ "Edit(./package.json)",
39
+ "Edit(./**/package.json)",
40
+ "Edit(./pnpm-lock.yaml)",
41
+ "Edit(./package-lock.json)",
42
+ "Edit(./yarn.lock)"
43
+ ]
44
+ }
45
+ }
@@ -0,0 +1,25 @@
1
+ # Copy to .cursorignore at the repo root. Blocks Cursor from reading secrets.
2
+ # Enforces the secrets policy in ../../AGENTS.md (the source of truth);
3
+ # this file is the exhaustive glob list for that policy.
4
+ .env
5
+ .env.*
6
+ *.pem
7
+ *.key
8
+ *.p12
9
+ *.pfx
10
+ *.keystore
11
+ *id_rsa*
12
+ *id_ed25519*
13
+ .ssh/
14
+ .aws/credentials
15
+ .aws/config
16
+ .kube/config
17
+ .npmrc
18
+ .git-credentials
19
+ *token
20
+ *token.*
21
+ *secret*
22
+ *credential*
23
+ *password*
24
+ *apikey*
25
+ .htpasswd
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@keboola/tsconfig/react.json",
3
+ "include": ["src"],
4
+ "compilerOptions": {
5
+ "types": ["node"],
6
+ "paths": {
7
+ "@/*": ["./src/*"]
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
4
+ }
@@ -0,0 +1,21 @@
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
3
+ import react from '@vitejs/plugin-react';
4
+ import path from 'node:path';
5
+ import { defineConfig } from 'vite';
6
+
7
+ export default defineConfig({
8
+ plugins: [
9
+ TanStackRouterVite({
10
+ routesDirectory: './src/routes',
11
+ generatedRouteTree: './src/routeTree.gen.ts',
12
+ }),
13
+ react(),
14
+ tailwindcss(),
15
+ ],
16
+ resolve: {
17
+ alias: {
18
+ '@': path.resolve(import.meta.dirname, 'src'),
19
+ },
20
+ },
21
+ });
@@ -0,0 +1,21 @@
1
+ import path from 'node:path';
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: 'jsdom',
8
+ setupFiles: ['./src/test-setup.ts'],
9
+ // This suite runs inside the repo-wide `Checks` job, so it shares a 2-core
10
+ // runner with every other package's tests — ~25x slower per test than a dev
11
+ // machine here (measured: AssistantPanel's two prompt tests are 156ms/199ms
12
+ // locally, 3881ms/5189ms there). The 5s default clips whichever one loses the
13
+ // race; the userEvent-driven tests get headroom instead.
14
+ testTimeout: 15000,
15
+ },
16
+ resolve: {
17
+ alias: {
18
+ '@': path.resolve(import.meta.dirname, 'src'),
19
+ },
20
+ },
21
+ });
@@ -0,0 +1,7 @@
1
+ [
2
+ "@keboola/brand-registry",
3
+ "@keboola/design",
4
+ "@keboola/eslint-plugin-design",
5
+ "@keboola/oxlint-config",
6
+ "@keboola/tsconfig"
7
+ ]
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "create-keboola-app",
3
+ "version": "0.0.0",
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
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": "./dist/cli.js",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "CHANGELOG.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
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
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/keboola/ui.git",
33
+ "directory": "packages/create-keboola-app"
34
+ },
35
+ "homepage": "https://github.com/keboola/ui/tree/main/packages/create-keboola-app#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/keboola/ui/issues"
38
+ },
39
+ "keywords": [
40
+ "keboola",
41
+ "scaffold",
42
+ "generator",
43
+ "create",
44
+ "react",
45
+ "vite",
46
+ "tanstack",
47
+ "cli"
48
+ ],
49
+ "dependencies": {
50
+ "@tanstack/router-generator": "^1.120.4"
51
+ },
52
+ "devDependencies": {
53
+ "@keboola/oxlint-config": "workspace:^",
54
+ "@keboola/tsconfig": "workspace:^",
55
+ "@types/node": "24.12.0",
56
+ "@typescript/native": "npm:typescript@^7.0.2",
57
+ "oxfmt": "^0.57.0",
58
+ "oxlint": "^1.72.0",
59
+ "tsdown": "^0.22.14",
60
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
61
+ "vite": "^8.1.5",
62
+ "vitest": "^4.1.10"
63
+ }
64
+ }