@cognite/dune 0.3.3 → 0.3.5
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/_templates/app/new/root/AGENTS.md.ejs.t +136 -0
- package/_templates/app/new/root/package.json.ejs.t +3 -2
- package/_templates/app/new/src/App.test.tsx.ejs.t +23 -20
- package/_templates/app/new/src/App.tsx.ejs.t +150 -95
- package/_templates/app/new/src/styles.css.ejs.t +3 -2
- package/bin/cli.js +19 -0
- package/package.json +1 -1
- package/_templates/app/new/config/tailwind.config.js.ejs.t +0 -11
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>AGENTS.md'
|
|
3
|
+
---
|
|
4
|
+
# Coding Standards
|
|
5
|
+
|
|
6
|
+
## 1. Dependency Injection
|
|
7
|
+
|
|
8
|
+
Inject dependencies via React context (hooks/components) or factory-override pattern (plain functions). Never hard-code dependencies.
|
|
9
|
+
|
|
10
|
+
**React context**
|
|
11
|
+
```typescript
|
|
12
|
+
const defaultDeps = { useDataSource, useAnalytics };
|
|
13
|
+
export type MyHookContextType = typeof defaultDeps;
|
|
14
|
+
export const MyHookContext = createContext<MyHookContextType>(defaultDeps);
|
|
15
|
+
|
|
16
|
+
export function useMyHook() {
|
|
17
|
+
const { useDataSource } = useContext(MyHookContext);
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Factory overrides**
|
|
22
|
+
```typescript
|
|
23
|
+
type Deps = { serviceFactory: () => SomeService };
|
|
24
|
+
const defaultDeps: Deps = { serviceFactory: () => new SomeServiceImpl() };
|
|
25
|
+
|
|
26
|
+
export const doWork = async (props: Props, overrides?: Partial<Deps>) => {
|
|
27
|
+
const { serviceFactory } = { ...defaultDeps, ...overrides };
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Interface-Based Services
|
|
34
|
+
|
|
35
|
+
Define an interface; implement with a class. Never reference the concrete class outside its own file.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
export interface DataService {
|
|
39
|
+
load(): Promise<Data>;
|
|
40
|
+
save(data: Data): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ApiDataService implements DataService { /* ... */ }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. ViewModel Pattern
|
|
49
|
+
|
|
50
|
+
Business logic lives in `use<Name>ViewModel`. Components only render.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export function useTodoViewModel(): TodoViewModel {
|
|
54
|
+
const { useTodoStorage, addTodoCommand } = useContext(TodoViewModelContext);
|
|
55
|
+
const storage = useTodoStorage();
|
|
56
|
+
const addTodo = useCallback((text: string) => addTodoCommand(text, storage), [storage, addTodoCommand]);
|
|
57
|
+
return { todos: storage.listAllTodos(), addTodo };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const TodoView = () => {
|
|
61
|
+
const { todos, addTodo } = useTodoViewModel();
|
|
62
|
+
return <ul>{todos.map(t => <TodoItem key={t.id} todo={t} onAdd={addTodo} />)}</ul>;
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 4. Test-Driven Development
|
|
69
|
+
|
|
70
|
+
### File creation order
|
|
71
|
+
1. Integration tests
|
|
72
|
+
2. Unit tests
|
|
73
|
+
3. Source files to make tests pass
|
|
74
|
+
|
|
75
|
+
### Conventions
|
|
76
|
+
- Files: `*.test.ts(x)` — runner: **Vitest** (`pnpm test` to run all, `vitest run` within a package)
|
|
77
|
+
- Structure: Arrange / Act / Assert (explicit comments when test > ~10 statements)
|
|
78
|
+
- One behavior per test; helper functions at the bottom of the file
|
|
79
|
+
- Prefer context injection over `vi.mock`; always add a comment when `vi.mock` is unavoidable
|
|
80
|
+
|
|
81
|
+
### Type-safe mocks
|
|
82
|
+
```typescript
|
|
83
|
+
// Preferred: vi.fn(() => ...) for consistent behavior
|
|
84
|
+
mockContext = { useUserInfo: vi.fn(() => ({ data: mockUser, isFetched: true })) };
|
|
85
|
+
|
|
86
|
+
// For per-test reconfiguration
|
|
87
|
+
mockContext = { useUserInfo: vi.fn() };
|
|
88
|
+
vi.mocked(mockContext.useUserInfo).mockReturnValue({ data: undefined, isFetched: true });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For full interface mocks, use `assert.fail` on methods the unit under test should never call — or better, use narrow interfaces that only expose what is needed.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
mockStorage = {
|
|
95
|
+
list: vi.fn(),
|
|
96
|
+
retrieve: vi.fn(() => { assert.fail('Not implemented'); }),
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### React hooks
|
|
101
|
+
```typescript
|
|
102
|
+
describe(useMyHook.name, () => {
|
|
103
|
+
let mockContext: MyContextType;
|
|
104
|
+
let wrapper: ComponentType<{ children: ReactNode }>;
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
mockContext = { useUserInfo: vi.fn(() => ({ data: mockUser })) };
|
|
108
|
+
wrapper = ({ children }) => (
|
|
109
|
+
<MyHookContext.Provider value={mockContext}>{children}</MyHookContext.Provider>
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should ...', async () => {
|
|
114
|
+
const { result } = renderHook(() => useMyHook(), { wrapper });
|
|
115
|
+
|
|
116
|
+
await act(async () => { await result.current.someAction(); });
|
|
117
|
+
|
|
118
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Type rules
|
|
124
|
+
- Never use `any` — prefer `unknown` or strong types
|
|
125
|
+
- No `as unknown as T` casts; for partial mocks use `{ ...defaults, ...overrides } as T`
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
function createMockWindow(overrides: Partial<Window> = {}): Window {
|
|
129
|
+
return { postMessage: vi.fn(), ...overrides } as Window;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- Use direct React type imports: `import type { ComponentType, ReactNode } from 'react'`
|
|
134
|
+
|
|
135
|
+
### Shared mock data
|
|
136
|
+
Place reusable factories in `src/__mocks__/`. Use `.test` TLD for fake URLs (RFC 2606).
|
|
@@ -21,9 +21,10 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>package.json'
|
|
|
21
21
|
"deploy-preview": "dune deploy:interactive"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@cognite/aura": "^0.1.
|
|
24
|
+
"@cognite/aura": "^0.1.4",
|
|
25
25
|
"@cognite/sdk": "^10.3.0",
|
|
26
|
-
"@cognite/dune": "^0.
|
|
26
|
+
"@cognite/dune": "^0.3.5",
|
|
27
|
+
"@tabler/icons-react": "^3.35.0",
|
|
27
28
|
"@tanstack/react-query": "^5.90.10",
|
|
28
29
|
"clsx": "^2.1.1",
|
|
29
30
|
"react": "^19.2.0",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>src/App.test.tsx'
|
|
3
3
|
---
|
|
4
|
-
import
|
|
5
|
-
import { CogniteClient } from '@cognite/sdk';
|
|
4
|
+
import * as duneAuth from '@cognite/dune';
|
|
6
5
|
import { render, screen } from '@testing-library/react';
|
|
7
6
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
7
|
|
|
@@ -11,20 +10,13 @@ import App from './App';
|
|
|
11
10
|
// Mock the @cognite/dune module
|
|
12
11
|
vi.mock(import('@cognite/dune'));
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
const sdk = new CogniteClient({
|
|
16
|
-
appId: 'dune-template-test',
|
|
17
|
-
project,
|
|
18
|
-
baseUrl: 'https://api.cognitedata.com',
|
|
19
|
-
oidcTokenProvider: async () => '',
|
|
20
|
-
});
|
|
13
|
+
type UseDuneResult = ReturnType<typeof duneAuth.useDune>;
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
function mockUseDune(project: string, isLoading: boolean): UseDuneResult {
|
|
16
|
+
return {
|
|
17
|
+
sdk: { project } as Partial<UseDuneResult['sdk']> as UseDuneResult['sdk'],
|
|
24
18
|
isLoading,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return contextValue;
|
|
19
|
+
};
|
|
28
20
|
}
|
|
29
21
|
|
|
30
22
|
describe('App', () => {
|
|
@@ -33,18 +25,29 @@ describe('App', () => {
|
|
|
33
25
|
});
|
|
34
26
|
|
|
35
27
|
it('renders loading state', () => {
|
|
36
|
-
vi.mocked(useDune).mockReturnValue(mockUseDune('test-project', true));
|
|
28
|
+
vi.mocked(duneAuth.useDune).mockReturnValue(mockUseDune('test-project', true));
|
|
37
29
|
|
|
38
30
|
render(<App />);
|
|
39
31
|
expect(screen.getByText('Loading project...')).toBeInTheDocument();
|
|
40
32
|
});
|
|
41
33
|
|
|
42
|
-
it('renders
|
|
43
|
-
vi.mocked(useDune).mockReturnValue(mockUseDune('my-test-project', false));
|
|
34
|
+
it('renders splash with deployment targets and checklist copy', () => {
|
|
35
|
+
vi.mocked(duneAuth.useDune).mockReturnValue(mockUseDune('my-test-project', false));
|
|
44
36
|
|
|
45
37
|
render(<App />);
|
|
46
|
-
expect(screen.getByText('Welcome to
|
|
47
|
-
expect(screen.getByText('
|
|
48
|
-
expect(screen.getByText('
|
|
38
|
+
expect(screen.getByText('Welcome to Dune')).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByText('App deployment checklist')).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByText('Plan')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByText('Explore')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('Deploy')).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByText('Support')).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByText('Help & feedback')).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText('Your app will deploy to')).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByText('org')).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByText('and project')).toBeInTheDocument();
|
|
48
|
+
expect(screen.getByText('<%= org %>')).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByText('<%= project %>')).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByText(/PRD\.md/)).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText(/deploy:interactive/)).toBeInTheDocument();
|
|
49
52
|
});
|
|
50
53
|
});
|
|
@@ -2,48 +2,80 @@
|
|
|
2
2
|
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>src/App.tsx'
|
|
3
3
|
---
|
|
4
4
|
import {
|
|
5
|
+
Alert,
|
|
6
|
+
AlertDescription,
|
|
5
7
|
Badge,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
Collapsible,
|
|
14
|
+
CollapsibleContent,
|
|
15
|
+
CollapsibleTrigger,
|
|
14
16
|
Loader,
|
|
15
17
|
Separator,
|
|
16
|
-
buttonVariants,
|
|
17
18
|
} from '@cognite/aura/components';
|
|
18
19
|
import { useDune } from '@cognite/dune';
|
|
20
|
+
import { IconCaretUpDown, IconRocket } from '@tabler/icons-react';
|
|
21
|
+
|
|
19
22
|
import appConfig from '../app.json';
|
|
20
23
|
|
|
21
|
-
const
|
|
22
|
-
'Confirm org, project, and cluster in app.json.',
|
|
23
|
-
'Replace the starter workflow with your first real query.',
|
|
24
|
-
'Deploy with dune deploy:interactive when the shell is ready.',
|
|
25
|
-
];
|
|
24
|
+
const DUNE_DOCUMENTATION_HREF = 'https://laughing-adventure-r6kwpyy.pages.github.io/';
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const INTRO_COPY =
|
|
27
|
+
"Build and deploy React apps to Cognite Data Fusion in minutes. Aura, Cognite's AI-native design system, comes pre-configured so your app looks and feels at home from day one. Follow the checklist below to get started.";
|
|
28
|
+
|
|
29
|
+
const CHECKLIST_STEPS = [
|
|
30
|
+
{
|
|
31
|
+
label: 'Plan',
|
|
32
|
+
badge: 'Step 1',
|
|
33
|
+
body: (
|
|
34
|
+
<>
|
|
35
|
+
Describe what you want to build in Cursor and ask the agent to collaborate on a PRD. It will generate a
|
|
36
|
+
structured template in PRD.md, which you can refine in plan mode with as much detail as needed. Keep it simple
|
|
37
|
+
and clear, then move on to building when ready.
|
|
38
|
+
</>
|
|
39
|
+
),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Explore',
|
|
43
|
+
badge: 'Step 2',
|
|
44
|
+
body: (
|
|
45
|
+
<>
|
|
46
|
+
Ask Cursor to review and understand your data model, then answer any follow-up questions it raises. Continue
|
|
47
|
+
refining the app by providing additional input as needed.
|
|
48
|
+
</>
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Deploy',
|
|
53
|
+
badge: 'Step 3',
|
|
54
|
+
body: (
|
|
55
|
+
<>
|
|
56
|
+
When ready to deploy, run <code>npx @cognite/dune deploy:interactive</code> in the terminal. Your app will
|
|
57
|
+
appear in the Fusion portal under Custom apps. Run the command again to redeploy new changes.
|
|
58
|
+
</>
|
|
59
|
+
),
|
|
60
|
+
},
|
|
61
|
+
] as const;
|
|
30
62
|
|
|
31
63
|
function App() {
|
|
32
64
|
const { sdk, isLoading } = useDune();
|
|
33
65
|
|
|
34
66
|
if (isLoading) {
|
|
35
67
|
return (
|
|
36
|
-
<main className="min-h-screen bg-
|
|
37
|
-
<section className="mx-auto
|
|
38
|
-
<div
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</
|
|
68
|
+
<main className="min-h-screen bg-muted/50 text-foreground">
|
|
69
|
+
<section className="mx-auto flex min-h-screen w-full max-w-lg flex-col justify-center p-4 sm:p-8">
|
|
70
|
+
<div className="mx-auto w-full max-w-sm">
|
|
71
|
+
<Card aria-label="Loading project" aria-live="polite">
|
|
72
|
+
<CardContent>
|
|
73
|
+
<div className="inline-flex items-center gap-3 text-muted-foreground">
|
|
74
|
+
<Loader size={20} />
|
|
75
|
+
<span>Loading project...</span>
|
|
76
|
+
</div>
|
|
77
|
+
</CardContent>
|
|
78
|
+
</Card>
|
|
47
79
|
</div>
|
|
48
80
|
</section>
|
|
49
81
|
</main>
|
|
@@ -51,80 +83,103 @@ function App() {
|
|
|
51
83
|
}
|
|
52
84
|
|
|
53
85
|
const deployment = appConfig.deployments?.[0];
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
{
|
|
57
|
-
org: deployment?.org ?? '',
|
|
58
|
-
project: deployment?.project ?? sdk.project,
|
|
59
|
-
cluster: getCluster(deployment?.baseUrl),
|
|
60
|
-
},
|
|
61
|
-
null,
|
|
62
|
-
2
|
|
63
|
-
);
|
|
86
|
+
const orgLabel = deployment?.org ?? '';
|
|
87
|
+
const projectLabel = deployment?.project ?? sdk.project;
|
|
64
88
|
|
|
65
89
|
return (
|
|
66
|
-
<main className="min-h-screen bg-
|
|
67
|
-
<section className="mx-auto
|
|
68
|
-
<
|
|
69
|
-
<div className="
|
|
70
|
-
<
|
|
71
|
-
<h1 className="text-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<div className="mt-3">
|
|
75
|
-
<Badge variant="fjord" background>
|
|
76
|
-
{deploymentLabel}
|
|
77
|
-
</Badge>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
<Dialog>
|
|
81
|
-
<DialogTrigger className={buttonVariants()}>Open starter config</DialogTrigger>
|
|
82
|
-
<DialogContent className="max-w-xl">
|
|
83
|
-
<DialogHeader>
|
|
84
|
-
<DialogTitle>Starter config</DialogTitle>
|
|
85
|
-
<DialogDescription>Current starter values as JSON.</DialogDescription>
|
|
86
|
-
</DialogHeader>
|
|
87
|
-
<div className="grid gap-2">
|
|
88
|
-
<div className="overflow-hidden rounded-2xl border border-border bg-background">
|
|
89
|
-
<CodeBlock code={starterConfigPreview} language="json" />
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<DialogFooter>
|
|
93
|
-
<Badge variant="fjord" background>{sdk.project}</Badge>
|
|
94
|
-
</DialogFooter>
|
|
95
|
-
</DialogContent>
|
|
96
|
-
</Dialog>
|
|
97
|
-
</div>
|
|
90
|
+
<main className="min-h-screen bg-muted/50 text-foreground">
|
|
91
|
+
<section className="mx-auto flex min-h-screen w-full max-w-3xl flex-col justify-center p-4 sm:p-8">
|
|
92
|
+
<Card>
|
|
93
|
+
<div className="p-15 gap-16">
|
|
94
|
+
<CardHeader>
|
|
95
|
+
<CardTitle as="h1" className="text-4xl">Welcome to Dune</CardTitle>
|
|
96
|
+
<CardDescription className="text-xl">{INTRO_COPY}</CardDescription>
|
|
97
|
+
</CardHeader>
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
<CardContent>
|
|
100
|
+
<Separator />
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<p className="text-sm font-semibold">Deployment checklist</p>
|
|
106
|
-
<p className="mt-1 text-sm text-muted-foreground">Use this to turn the starter into a deployable app.</p>
|
|
102
|
+
<div className="flex flex-col gap-6 pt-16">
|
|
103
|
+
<div className="flex items-center gap-2 pt-4">
|
|
104
|
+
<IconRocket aria-hidden />
|
|
105
|
+
<span className="text-2xl font-medium">App deployment checklist</span>
|
|
107
106
|
</div>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
107
|
+
|
|
108
|
+
<div className="flex flex-col gap-4 px-4">
|
|
109
|
+
{CHECKLIST_STEPS.map((step, index) => (
|
|
110
|
+
<Collapsible key={step.label} defaultOpen={index === 0}>
|
|
111
|
+
<CollapsibleTrigger className="w-full">
|
|
112
|
+
<div className="flex w-full min-w-0 items-center justify-between gap-3 text-left">
|
|
113
|
+
<span className="text-lg">{step.label}</span>
|
|
114
|
+
<span className="inline-flex shrink-0 items-center gap-2">
|
|
115
|
+
<Badge variant="mountain" background>
|
|
116
|
+
{step.badge}
|
|
117
|
+
</Badge>
|
|
118
|
+
<IconCaretUpDown aria-hidden className="size-4 text-muted-foreground" />
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
</CollapsibleTrigger>
|
|
122
|
+
<CollapsibleContent className="py-2">
|
|
123
|
+
{step.body}
|
|
124
|
+
</CollapsibleContent>
|
|
125
|
+
</Collapsible>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<Alert className="bg-mountain-50 mb-10">
|
|
130
|
+
<AlertDescription>
|
|
131
|
+
<div className="flex flex-wrap items-center gap-2 text-lg">
|
|
132
|
+
<span>Your app will deploy to</span>
|
|
133
|
+
{orgLabel ? (
|
|
134
|
+
<>
|
|
135
|
+
<span>org</span>
|
|
136
|
+
<Badge variant="nordic" background>
|
|
137
|
+
{orgLabel}
|
|
138
|
+
</Badge>
|
|
139
|
+
<span>and project</span>
|
|
140
|
+
</>
|
|
141
|
+
) : (
|
|
142
|
+
<span>project</span>
|
|
143
|
+
)}
|
|
144
|
+
<Badge variant="nordic" background>
|
|
145
|
+
{projectLabel}
|
|
146
|
+
</Badge>
|
|
147
|
+
</div>
|
|
148
|
+
</AlertDescription>
|
|
149
|
+
</Alert>
|
|
150
|
+
|
|
151
|
+
<Collapsible>
|
|
152
|
+
<CollapsibleTrigger className="w-full">
|
|
153
|
+
<div className="flex w-full min-w-0 items-center justify-between gap-3 text-left">
|
|
154
|
+
<span className="text-lg">Support</span>
|
|
155
|
+
<span className="inline-flex shrink-0 items-center gap-2">
|
|
156
|
+
<Badge variant="mountain">
|
|
157
|
+
Help & feedback
|
|
158
|
+
</Badge>
|
|
159
|
+
<IconCaretUpDown aria-hidden className="size-4 text-muted-foreground" />
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
</CollapsibleTrigger>
|
|
163
|
+
<CollapsibleContent className="py-2">
|
|
164
|
+
<p>
|
|
165
|
+
For additional support and feedback, please head to{' '}
|
|
166
|
+
<a
|
|
167
|
+
href={DUNE_DOCUMENTATION_HREF}
|
|
168
|
+
rel="noreferrer"
|
|
169
|
+
style={{ color: '#486AED' }}
|
|
170
|
+
target="_blank"
|
|
171
|
+
>
|
|
172
|
+
Dune documentation
|
|
173
|
+
</a>{' '}
|
|
174
|
+
or the{' '}
|
|
175
|
+
<span style={{ color: '#486AED' }}>#Dune Slack channel</span>.
|
|
176
|
+
</p>
|
|
177
|
+
</CollapsibleContent>
|
|
178
|
+
</Collapsible>
|
|
124
179
|
</div>
|
|
125
|
-
</
|
|
180
|
+
</CardContent>
|
|
126
181
|
</div>
|
|
127
|
-
</
|
|
182
|
+
</Card>
|
|
128
183
|
</section>
|
|
129
184
|
</main>
|
|
130
185
|
);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>src/styles.css'
|
|
3
3
|
---
|
|
4
|
-
@import
|
|
5
|
-
|
|
4
|
+
@import '@cognite/aura/styles.source.css';
|
|
5
|
+
|
|
6
|
+
@source '../node_modules/@cognite/aura/dist/components';
|
|
6
7
|
|
|
7
8
|
:root {
|
|
8
9
|
--font-inter: "Inter", ui-sans-serif, system-ui, sans-serif;
|
package/bin/cli.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { symlinkSync } from 'node:fs';
|
|
3
4
|
import { basename, dirname, normalize, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Creates a CLAUDE.md symlink pointing to AGENTS.md in the given directory.
|
|
9
|
+
* Silently skips if the symlink already exists (EEXIST).
|
|
10
|
+
* @param {string} appDir - Absolute path to the generated app directory
|
|
11
|
+
*/
|
|
12
|
+
export function createClaudeMdSymlink(appDir) {
|
|
13
|
+
try {
|
|
14
|
+
symlinkSync('AGENTS.md', resolve(appDir, 'CLAUDE.md'));
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err.code !== 'EEXIST') {
|
|
17
|
+
console.warn('⚠️ Could not create CLAUDE.md symlink:', err.message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
import { Logger, runner } from 'hygen';
|
|
7
23
|
|
|
8
24
|
const defaultTemplates = resolve(dirname(fileURLToPath(import.meta.url)), '..', '_templates');
|
|
@@ -118,6 +134,9 @@ async function main() {
|
|
|
118
134
|
debug: !!process.env.DEBUG,
|
|
119
135
|
});
|
|
120
136
|
|
|
137
|
+
// Create CLAUDE.md symlink -> AGENTS.md for Claude Code support
|
|
138
|
+
createClaudeMdSymlink(isCurrentDir ? process.cwd() : dirName || appName);
|
|
139
|
+
|
|
121
140
|
// Print success message with next steps
|
|
122
141
|
const installAndDevSteps = ' pnpm install\n pnpm dev';
|
|
123
142
|
const deployLabel = 'To deploy your app:';
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>tailwind.config.js'
|
|
3
|
-
---
|
|
4
|
-
/** @type {import('tailwindcss').Config} */
|
|
5
|
-
export default {
|
|
6
|
-
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
7
|
-
theme: {
|
|
8
|
-
extend: {},
|
|
9
|
-
},
|
|
10
|
-
plugins: [],
|
|
11
|
-
};
|