@cognite/dune 0.3.4 → 0.3.6

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.
@@ -0,0 +1,182 @@
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
+
12
+ ```typescript
13
+ const defaultDeps = { useDataSource, useAnalytics };
14
+ export type MyHookContextType = typeof defaultDeps;
15
+ export const MyHookContext = createContext<MyHookContextType>(defaultDeps);
16
+
17
+ export function useMyHook() {
18
+ const { useDataSource } = useContext(MyHookContext);
19
+ }
20
+ ```
21
+
22
+ ### Factory overrides
23
+
24
+ ```typescript
25
+ type Deps = { serviceFactory: () => SomeService };
26
+ const defaultDeps: Deps = { serviceFactory: () => new SomeServiceImpl() };
27
+
28
+ export const doWork = async (props: Props, overrides?: Partial<Deps>) => {
29
+ const { serviceFactory } = { ...defaultDeps, ...overrides };
30
+ };
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 2. Interface-Based Services
36
+
37
+ Define an interface; implement with a class. Never reference the concrete class outside its own file.
38
+
39
+ ```typescript
40
+ export interface DataService {
41
+ load(): Promise<Data>;
42
+ save(data: Data): Promise<void>;
43
+ }
44
+
45
+ export class ApiDataService implements DataService {
46
+ /* ... */
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 3. ViewModel Pattern
53
+
54
+ Business logic lives in `use<Name>ViewModel`. Components only render.
55
+
56
+ ```typescript
57
+ export function useTodoViewModel(): TodoViewModel {
58
+ const { useTodoStorage, addTodoCommand } = useContext(TodoViewModelContext);
59
+ const storage = useTodoStorage();
60
+ const addTodo = useCallback(
61
+ (text: string) => addTodoCommand(text, storage),
62
+ [storage, addTodoCommand]
63
+ );
64
+ return { todos: storage.listAllTodos(), addTodo };
65
+ }
66
+
67
+ export const TodoView = () => {
68
+ const { todos, addTodo } = useTodoViewModel();
69
+ return <ul>{todos.map((t) => <TodoItem key={t.id} todo={t} onAdd={addTodo} />)}</ul>;
70
+ };
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 4. Test-First Development
76
+
77
+ Write tests before implementation for all non-trivial behavior changes.
78
+
79
+ ### Preferred order
80
+
81
+ Start with behavior-focused tests so requirements are specified before implementation details:
82
+
83
+ 1. Integration tests (user-visible behavior)
84
+ 2. Unit tests (isolated module logic)
85
+ 3. Source files to make tests pass
86
+
87
+ For bug fixes, start by adding a failing regression test that reproduces the issue.
88
+
89
+ Every new module with logic (service, hook, component, utility) must include a corresponding `*.test.ts(x)` file in the same changeset.
90
+
91
+ ### Reasonable exceptions
92
+
93
+ - Bootstrapping/entry files (for example `main.tsx`)
94
+ - Generated code
95
+ - Trivial pure-markup components with no logic or state
96
+
97
+ ### Test levels in this repo
98
+
99
+ - **Integration test**: validates behavior across boundaries (for example component + view model + service contract), mocking only external systems such as network APIs.
100
+ - **Unit test**: validates one module in isolation (service, hook, utility, or component behavior).
101
+
102
+ ### Minimum expected coverage by file type
103
+
104
+ | File type | Required test cases |
105
+ | --- | --- |
106
+ | Service (`*Service.ts`) | Correct request construction; response parsing; error thrown on non-OK status |
107
+ | ViewModel hook (`use*ViewModel.ts`) | Loading state; success state with correct derived values; error state |
108
+ | Pure utility / helper | Every exported function and all meaningful branches |
109
+ | View component | Renders expected content from props; loading/error/empty states where applicable |
110
+
111
+ ### Conventions
112
+
113
+ - Files: `*.test.ts(x)`; runner: **Vitest** (`pnpm test` or `vitest run`)
114
+ - Structure: Arrange / Act / Assert (add explicit comments for longer tests)
115
+ - One behavior per test
116
+ - Keep helper functions at the bottom of the file
117
+ - Prefer dependency/context injection over `vi.mock`; add a short reason when `vi.mock` is unavoidable
118
+
119
+ ### Type-safe mocks
120
+
121
+ ```typescript
122
+ // Preferred: vi.fn(() => ...) for consistent behavior
123
+ mockContext = { useUserInfo: vi.fn(() => ({ data: mockUser, isFetched: true })) };
124
+
125
+ // Per-test reconfiguration
126
+ mockContext = { useUserInfo: vi.fn() };
127
+ vi.mocked(mockContext.useUserInfo).mockReturnValue({ data: undefined, isFetched: true });
128
+ ```
129
+
130
+ For full interface mocks, use `assert.fail` on methods the unit under test should never call, or preferably define a narrower interface.
131
+
132
+ ```typescript
133
+ mockStorage = {
134
+ list: vi.fn(),
135
+ retrieve: vi.fn(() => {
136
+ assert.fail('Not implemented');
137
+ }),
138
+ };
139
+ ```
140
+
141
+ ### React hook test pattern
142
+
143
+ ```typescript
144
+ describe(useMyHook.name, () => {
145
+ let mockContext: MyContextType;
146
+ let wrapper: ComponentType<{ children: ReactNode }>;
147
+
148
+ beforeEach(() => {
149
+ mockContext = { useUserInfo: vi.fn(() => ({ data: mockUser })) };
150
+ wrapper = ({ children }) => (
151
+ <MyHookContext.Provider value={mockContext}>{children}</MyHookContext.Provider>
152
+ );
153
+ });
154
+
155
+ it('should ...', async () => {
156
+ const { result } = renderHook(() => useMyHook(), { wrapper });
157
+ await act(async () => {
158
+ await result.current.someAction();
159
+ });
160
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
161
+ });
162
+ });
163
+ ```
164
+
165
+ ### Shared mock data
166
+
167
+ Place reusable factories in `src/__mocks__/`. Use `.test` TLD for fake URLs (RFC 2606).
168
+
169
+ ---
170
+
171
+ ## 5. TypeScript Rules
172
+
173
+ - Never use `any`; prefer `unknown` or explicit strong types
174
+ - Never use `as unknown as T`; for partial test doubles use `{ ...defaults, ...overrides } as T`
175
+ - Use direct React type imports: `import type { ComponentType, ReactNode } from 'react'`
176
+
177
+ ```typescript
178
+ function createMockWindow(overrides: Partial<Window> = {}): Window {
179
+ return { postMessage: vi.fn(), ...overrides } as Window;
180
+ }
181
+ ```
182
+ ---
@@ -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.2",
24
+ "@cognite/aura": "^0.1.4",
25
25
  "@cognite/sdk": "^10.3.0",
26
- "@cognite/dune": "^0.3.4",
26
+ "@cognite/dune": "^0.3.6",
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 { useDune } from '@cognite/dune';
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
- function mockUseDune(project: string, isLoading: boolean): ReturnType<typeof useDune> {
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
- const contextValue = {
23
- sdk,
15
+ function mockUseDune(project: string, isLoading: boolean): UseDuneResult {
16
+ return {
17
+ sdk: { project } as Partial<UseDuneResult['sdk']> as UseDuneResult['sdk'],
24
18
  isLoading,
25
- } satisfies ReturnType<typeof useDune>;
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 app with project name', () => {
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 my-test-project')).toBeInTheDocument();
47
- expect(screen.getByText('Open starter config')).toBeInTheDocument();
48
- expect(screen.getByText('<%= org %> / <%= project %>')).toBeInTheDocument();
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
- CodeBlock,
7
- Dialog,
8
- DialogContent,
9
- DialogDescription,
10
- DialogFooter,
11
- DialogHeader,
12
- DialogTitle,
13
- DialogTrigger,
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 deploymentChecklist = [
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
- function getCluster(baseUrl?: string) {
28
- return baseUrl?.replace(/^https?:\/\//, '').replace(/\.cognitedata\.com$/, '') ?? '';
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-[linear-gradient(145deg,var(--background)_0%,var(--mountain-50)_55%,var(--mountain-100)_100%)] text-foreground">
37
- <section className="mx-auto grid min-h-screen w-full max-w-[58rem] place-content-center p-4 sm:p-8">
38
- <div
39
- className="mx-auto grid w-full max-w-sm justify-items-center gap-6 rounded-3xl border border-border bg-card p-5 shadow-xl backdrop-blur-lg sm:p-8"
40
- aria-label="Loading project"
41
- aria-live="polite"
42
- >
43
- <div className="inline-flex items-center gap-3 text-muted-foreground">
44
- <Loader size={20} />
45
- <span>Loading project...</span>
46
- </div>
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 deploymentLabel = [deployment?.org, deployment?.project ?? sdk.project].filter(Boolean).join(' / ');
55
- const starterConfigPreview = JSON.stringify(
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-[linear-gradient(145deg,var(--background)_0%,var(--mountain-50)_55%,var(--mountain-100)_100%)] text-foreground">
67
- <section className="mx-auto grid min-h-screen w-full max-w-[58rem] place-content-center p-4 sm:p-8">
68
- <div className="grid gap-6 rounded-3xl border border-border bg-card p-5 shadow-xl backdrop-blur-lg sm:p-8">
69
- <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
70
- <div>
71
- <h1 className="text-[clamp(2rem,4vw,3rem)] leading-none font-semibold tracking-[-0.04em]">
72
- Welcome to {sdk.project}
73
- </h1>
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
- <Separator />
99
+ <CardContent>
100
+ <Separator />
100
101
 
101
- <div className="grid gap-4">
102
- <div id="deployment-checklist" className="rounded-2xl border border-border bg-muted p-4">
103
- <div className="flex items-center justify-between gap-3">
104
- <div>
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
- <Badge variant="fjord" background>
109
- 3 steps
110
- </Badge>
111
- </div>
112
- <div className="mt-4 grid gap-2">
113
- {deploymentChecklist.map((item, index) => (
114
- <div
115
- key={item}
116
- className="grid grid-cols-[auto_1fr] items-start gap-3 rounded-xl border border-border bg-card px-3 py-2"
117
- >
118
- <span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-foreground text-xs font-semibold text-background">
119
- {index + 1}
120
- </span>
121
- <p className="text-sm text-card-foreground">{item}</p>
122
- </div>
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
- </div>
180
+ </CardContent>
126
181
  </div>
127
- </div>
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 "@cognite/aura/styles.css";
5
- @import "tailwindcss";
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@cognite/dune",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Build and deploy React apps to Cognite Data Fusion",
5
5
  "keywords": ["cognite", "dune", "cdf", "fusion", "react", "scaffold", "deploy"],
6
6
  "license": "Apache-2.0",
@@ -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
- };