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.
- package/README.md +79 -0
- package/dist/blocks-DYU5Ke29.js +147 -0
- package/dist/cli.js +451 -0
- package/dist/extract.js +133 -0
- package/dist/regions-B3r5LhXl.js +95 -0
- package/dist/sanitize-CKs8pAzZ.js +158 -0
- package/dist/sanitize.js +2 -0
- package/dist/template/AGENTS.md +133 -0
- package/dist/template/README.md +56 -0
- package/dist/template/_env.example +6 -0
- package/dist/template/_gitignore +7 -0
- package/dist/template/_oxfmtrc.json +18 -0
- package/dist/template/index.html +13 -0
- package/dist/template/oxlint.config.ts +43 -0
- package/dist/template/package.json +53 -0
- package/dist/template/public/favicon.svg +10 -0
- package/dist/template/src/components/AppChrome.tsx +201 -0
- package/dist/template/src/components/AssistantPanel.test.tsx +65 -0
- package/dist/template/src/components/AssistantPanel.tsx +96 -0
- package/dist/template/src/components/AssistantToggle.tsx +37 -0
- package/dist/template/src/components/ColorModeToggle.tsx +15 -0
- package/dist/template/src/components/HomePrompt.tsx +35 -0
- package/dist/template/src/components/KpiCard.test.tsx +20 -0
- package/dist/template/src/components/KpiCard.tsx +24 -0
- package/dist/template/src/components/SelectionFloater.test.tsx +73 -0
- package/dist/template/src/components/SelectionFloater.tsx +88 -0
- package/dist/template/src/components/SidebarModeMenu.test.tsx +68 -0
- package/dist/template/src/components/SidebarModeMenu.tsx +54 -0
- package/dist/template/src/index.css +26 -0
- package/dist/template/src/main.tsx +57 -0
- package/dist/template/src/nav.test.ts +25 -0
- package/dist/template/src/nav.ts +38 -0
- package/dist/template/src/overview-sections.test.tsx +36 -0
- package/dist/template/src/overview-sections.tsx +40 -0
- package/dist/template/src/recipes/Chart.test.tsx +24 -0
- package/dist/template/src/recipes/Chart.tsx +56 -0
- package/dist/template/src/recipes/DataGrid.test.tsx +47 -0
- package/dist/template/src/recipes/DataGrid.tsx +151 -0
- package/dist/template/src/routes/-fixtures.ts +113 -0
- package/dist/template/src/routes/-hooks.ts +15 -0
- package/dist/template/src/routes/-index.test.tsx +123 -0
- package/dist/template/src/routes/__root.tsx +20 -0
- package/dist/template/src/routes/index.tsx +95 -0
- package/dist/template/src/routes/tokens/$id.tsx +95 -0
- package/dist/template/src/routes/tokens/-fixtures.test.ts +60 -0
- package/dist/template/src/routes/tokens/-fixtures.ts +91 -0
- package/dist/template/src/routes/tokens/-hooks.ts +37 -0
- package/dist/template/src/routes/tokens/-index.test.tsx +75 -0
- package/dist/template/src/routes/tokens/-nav.ts +17 -0
- package/dist/template/src/routes/tokens/-types.ts +22 -0
- package/dist/template/src/routes/tokens/index.tsx +182 -0
- package/dist/template/src/routes/tokens/new.tsx +163 -0
- package/dist/template/src/sections/chart.tsx +34 -0
- package/dist/template/src/sections/grid.tsx +21 -0
- package/dist/template/src/sections/kpi.tsx +36 -0
- package/dist/template/src/slices/assistant.ts +68 -0
- package/dist/template/src/slices/shell.ts +54 -0
- package/dist/template/src/store.test.tsx +92 -0
- package/dist/template/src/store.ts +67 -0
- package/dist/template/src/test-setup.ts +11 -0
- package/dist/template/src/vite-env.d.ts +1 -0
- package/dist/template/templates/agent-safety/README.md +14 -0
- package/dist/template/templates/agent-safety/aiexclude +25 -0
- package/dist/template/templates/agent-safety/claude-code.settings.json +45 -0
- package/dist/template/templates/agent-safety/cursorignore +25 -0
- package/dist/template/tsconfig.json +10 -0
- package/dist/template/vercel.json +4 -0
- package/dist/template/vite.config.ts +21 -0
- package/dist/template/vitest.config.ts +21 -0
- package/dist/template/workspace-deps.json +7 -0
- 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,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
|
+
});
|
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
|
+
}
|