@cognite/dune 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_templates/app/new/root/app.json.ejs.t +3 -0
- package/_templates/app/new/root/package.json.ejs.t +3 -0
- package/_templates/app/new/src/App.tsx.ejs.t +70 -1
- package/_templates/app/new/src/main.tsx.ejs.t +6 -0
- package/bin/cli.js +21 -17
- package/bin/deploy-command.js +3 -14
- package/bin/deploy-interactive-command.js +41 -42
- package/bin/utils/fusion-url.js +28 -0
- package/bin/utils/fusion-url.test.js +50 -0
- package/dist/auth/index.d.ts +14 -1
- package/dist/auth/index.js +3 -1
- package/dist/{chunk-53VTKDSC.js → chunk-XWZCFZUH.js} +50 -0
- package/dist/cli/cli.js +63 -0
- package/dist/deploy/index.d.ts +41 -1
- package/dist/deploy/index.js +154 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/vite/index.js +10 -1
- package/package.json +23 -3
- package/src/auth/app-sdk-auth-provider.test.tsx +126 -0
- package/src/auth/app-sdk-auth-provider.tsx +69 -0
- package/src/auth/index.ts +1 -0
- package/src/deploy/apphosting-deployer.test.ts +177 -0
- package/src/deploy/apphosting-deployer.ts +166 -0
- package/src/deploy/deploy.ts +25 -20
- package/src/deploy/index.ts +1 -0
- package/src/deploy/types.ts +2 -0
- package/src/vite/fusion-open-plugin.ts +10 -1
|
@@ -6,6 +6,9 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>app.json'
|
|
|
6
6
|
"description": "<%= description %>",
|
|
7
7
|
"externalId": "<%= name %>",
|
|
8
8
|
"versionTag": "0.0.1",
|
|
9
|
+
<% if (infra === 'appsApi') { -%>
|
|
10
|
+
"infra": "appsApi",
|
|
11
|
+
<% } -%>
|
|
9
12
|
"deployments": [
|
|
10
13
|
{
|
|
11
14
|
"org": "<%= org %>",
|
|
@@ -24,6 +24,9 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>package.json'
|
|
|
24
24
|
"@cognite/aura": "^0.1.5",
|
|
25
25
|
"@cognite/sdk": "^10.3.0",
|
|
26
26
|
"@cognite/dune": "^0.3.7",
|
|
27
|
+
<% if (infra === 'appsApi') { -%>
|
|
28
|
+
"@cognite/app-sdk": "^0.3.0",
|
|
29
|
+
<% } -%>
|
|
27
30
|
"@tabler/icons-react": "^3.35.0",
|
|
28
31
|
"@tanstack/react-query": "^5.90.10",
|
|
29
32
|
"clsx": "^2.1.1",
|
|
@@ -16,7 +16,12 @@ import {
|
|
|
16
16
|
Loader,
|
|
17
17
|
Separator,
|
|
18
18
|
} from '@cognite/aura/components';
|
|
19
|
+
<% if (infra === 'appsApi') { -%>
|
|
20
|
+
import { connectToHostApp } from '@cognite/app-sdk';
|
|
21
|
+
import { useEffect, useState } from 'react';
|
|
22
|
+
<% } else { -%>
|
|
19
23
|
import { useDune } from '@cognite/dune';
|
|
24
|
+
<% } -%>
|
|
20
25
|
import { IconCaretUpDown, IconRocket } from '@tabler/icons-react';
|
|
21
26
|
|
|
22
27
|
import appConfig from '../app.json';
|
|
@@ -61,7 +66,37 @@ const CHECKLIST_STEPS = [
|
|
|
61
66
|
] as const;
|
|
62
67
|
|
|
63
68
|
function App() {
|
|
64
|
-
|
|
69
|
+
<% if (infra === 'appsApi') { -%>
|
|
70
|
+
// Connect to the Fusion host via @cognite/app-sdk. The handshake is
|
|
71
|
+
// asynchronous — `project` is only populated after Comlink finishes
|
|
72
|
+
// exposing the host API, so we render a loader until then.
|
|
73
|
+
const [project, setProject] = useState<string | null>(null);
|
|
74
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
75
|
+
const [error, setError] = useState<string | undefined>();
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
connectToHostApp({ applicationName: '<%= name %>' })
|
|
80
|
+
.then(async ({ api }) => {
|
|
81
|
+
if (cancelled) return;
|
|
82
|
+
const proj = await api.getProject();
|
|
83
|
+
if (cancelled) return;
|
|
84
|
+
setProject(proj);
|
|
85
|
+
})
|
|
86
|
+
.catch((err: unknown) => {
|
|
87
|
+
if (cancelled) return;
|
|
88
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
89
|
+
})
|
|
90
|
+
.finally(() => {
|
|
91
|
+
if (!cancelled) setIsLoading(false);
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
<% } else { -%>
|
|
98
|
+
const { sdk, isLoading, error } = useDune();
|
|
99
|
+
<% } -%>
|
|
65
100
|
|
|
66
101
|
if (isLoading) {
|
|
67
102
|
return (
|
|
@@ -82,9 +117,43 @@ function App() {
|
|
|
82
117
|
);
|
|
83
118
|
}
|
|
84
119
|
|
|
120
|
+
<% if (infra === 'appsApi') { -%>
|
|
121
|
+
if (error) {
|
|
122
|
+
return (
|
|
123
|
+
<main className="min-h-screen bg-muted/50 text-foreground">
|
|
124
|
+
<section className="mx-auto flex min-h-screen w-full max-w-lg flex-col justify-center p-4 sm:p-8">
|
|
125
|
+
<div className="mx-auto w-full max-w-sm">
|
|
126
|
+
<Alert>
|
|
127
|
+
<AlertDescription>Failed to connect to Fusion host: {error}</AlertDescription>
|
|
128
|
+
</Alert>
|
|
129
|
+
</div>
|
|
130
|
+
</section>
|
|
131
|
+
</main>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
<% } else { -%>
|
|
135
|
+
if (error) {
|
|
136
|
+
return (
|
|
137
|
+
<main className="min-h-screen bg-muted/50 text-foreground">
|
|
138
|
+
<section className="mx-auto flex min-h-screen w-full max-w-lg flex-col justify-center p-4 sm:p-8">
|
|
139
|
+
<div className="mx-auto w-full max-w-sm">
|
|
140
|
+
<Alert>
|
|
141
|
+
<AlertDescription>{error}</AlertDescription>
|
|
142
|
+
</Alert>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
</main>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
<% } -%>
|
|
149
|
+
|
|
85
150
|
const deployment = appConfig.deployments?.[0];
|
|
86
151
|
const orgLabel = deployment?.org ?? '';
|
|
152
|
+
<% if (infra === 'appsApi') { -%>
|
|
153
|
+
const projectLabel = deployment?.project ?? project ?? '';
|
|
154
|
+
<% } else { -%>
|
|
87
155
|
const projectLabel = deployment?.project ?? sdk.project;
|
|
156
|
+
<% } -%>
|
|
88
157
|
|
|
89
158
|
return (
|
|
90
159
|
<main className="min-h-screen bg-muted/50 text-foreground">
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>src/main.tsx'
|
|
3
3
|
---
|
|
4
|
+
<% if (infra !== 'appsApi') { -%>
|
|
4
5
|
import { DuneAuthProvider } from '@cognite/dune';
|
|
6
|
+
<% } -%>
|
|
5
7
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
6
8
|
import React from 'react';
|
|
7
9
|
import ReactDOM from 'react-dom/client';
|
|
@@ -22,9 +24,13 @@ const queryClient = new QueryClient({
|
|
|
22
24
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
23
25
|
<React.StrictMode>
|
|
24
26
|
<QueryClientProvider client={queryClient}>
|
|
27
|
+
<% if (infra === 'appsApi') { -%>
|
|
28
|
+
<App />
|
|
29
|
+
<% } else { -%>
|
|
25
30
|
<DuneAuthProvider>
|
|
26
31
|
<App />
|
|
27
32
|
</DuneAuthProvider>
|
|
33
|
+
<% } -%>
|
|
28
34
|
</QueryClientProvider>
|
|
29
35
|
</React.StrictMode>
|
|
30
36
|
);
|
package/bin/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ const defaultTemplates = resolve(dirname(fileURLToPath(import.meta.url)), '..',
|
|
|
31
31
|
* @param {Function} onAppName - Callback to track the app name
|
|
32
32
|
* @returns {() => object} Function that returns a prompter object with prompt method
|
|
33
33
|
*/
|
|
34
|
-
export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
|
|
34
|
+
export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName, infra) {
|
|
35
35
|
// IMPORTANT: All code paths below must set useCurrentDir (true or false) because
|
|
36
36
|
// templates rely on it being defined. It's never undefined.
|
|
37
37
|
return () => ({
|
|
@@ -52,8 +52,7 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
|
|
|
52
52
|
if (result.name && onAppName) {
|
|
53
53
|
onAppName(result.name);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
return { ...result, useCurrentDir: true, directoryName: undefined };
|
|
55
|
+
return { ...result, useCurrentDir: true, directoryName: undefined, infra };
|
|
57
56
|
}
|
|
58
57
|
// If directory argument is provided, pre-fill the name prompt with directory name
|
|
59
58
|
if (dirName) {
|
|
@@ -71,15 +70,13 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
|
|
|
71
70
|
if (result.name && onAppName) {
|
|
72
71
|
onAppName(result.name);
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
return { ...result, directoryName: dirName, useCurrentDir: false };
|
|
73
|
+
return { ...result, directoryName: dirName, useCurrentDir: false, infra };
|
|
76
74
|
}
|
|
77
75
|
const result = await enquirer.default.prompt(prompts);
|
|
78
76
|
if (result.name && onAppName) {
|
|
79
77
|
onAppName(result.name);
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
return { ...result, useCurrentDir: false, directoryName: undefined };
|
|
79
|
+
return { ...result, useCurrentDir: false, directoryName: undefined, infra };
|
|
83
80
|
},
|
|
84
81
|
});
|
|
85
82
|
}
|
|
@@ -93,12 +90,15 @@ async function main() {
|
|
|
93
90
|
// TODO(DUNE-362): simplify directory resolution logic
|
|
94
91
|
// Parse directory argument
|
|
95
92
|
// Support: npx @cognite/dune create . or npx @cognite/dune .
|
|
93
|
+
const useNewInfra = args.includes('--newinfra');
|
|
94
|
+
const filteredArgs = args.filter((a) => a !== '--newinfra');
|
|
95
|
+
const infra = useNewInfra ? 'appsApi' : undefined;
|
|
96
|
+
|
|
96
97
|
let dirArg;
|
|
97
98
|
if (command === '.') {
|
|
98
|
-
// If first arg is ".", treat it as directory argument with no command
|
|
99
99
|
dirArg = '.';
|
|
100
100
|
} else if (command === 'create' || command === 'new') {
|
|
101
|
-
dirArg =
|
|
101
|
+
dirArg = filteredArgs[1];
|
|
102
102
|
}
|
|
103
103
|
// When !command: no args, dirArg stays undefined (create app interactively)
|
|
104
104
|
const isCurrentDir = dirArg === '.' || dirArg === './';
|
|
@@ -125,7 +125,7 @@ async function main() {
|
|
|
125
125
|
logger: new Logger(console.log.bind(console)),
|
|
126
126
|
createPrompter: createAppPrompter(isCurrentDir, dirName, enquirer, (name) => {
|
|
127
127
|
appName = name;
|
|
128
|
-
}),
|
|
128
|
+
}, infra),
|
|
129
129
|
exec: async (action, body) => {
|
|
130
130
|
const { execa } = await import('execa');
|
|
131
131
|
const opts = body && body.length > 0 ? { input: body } : {};
|
|
@@ -210,14 +210,18 @@ Commands:
|
|
|
210
210
|
skills Manage AI agent skills (pull, push, list)
|
|
211
211
|
help Show this help message
|
|
212
212
|
|
|
213
|
+
Options:
|
|
214
|
+
--newinfra Use the new App Hosting infrastructure (create only)
|
|
215
|
+
|
|
213
216
|
Examples:
|
|
214
|
-
npx @cognite/dune
|
|
215
|
-
npx @cognite/dune create
|
|
216
|
-
npx @cognite/dune create
|
|
217
|
-
npx @cognite/dune create
|
|
218
|
-
npx @cognite/dune
|
|
219
|
-
npx @cognite/dune deploy
|
|
220
|
-
npx @cognite/dune
|
|
217
|
+
npx @cognite/dune # Create a new app (interactive)
|
|
218
|
+
npx @cognite/dune create # Create a new app (interactive)
|
|
219
|
+
npx @cognite/dune create --newinfra # Create app using new App Hosting infra
|
|
220
|
+
npx @cognite/dune create [directory] # Create app in [directory] subfolder
|
|
221
|
+
npx @cognite/dune create . # Create app in current directory (. is special)
|
|
222
|
+
npx @cognite/dune deploy # Deploy with env credentials
|
|
223
|
+
npx @cognite/dune deploy:interactive # Deploy with browser login
|
|
224
|
+
npx @cognite/dune skills pull # Pull AI skills into your project
|
|
221
225
|
|
|
222
226
|
For programmatic usage:
|
|
223
227
|
import { DuneAuthProvider, useDune } from "@cognite/dune/auth"
|
package/bin/deploy-command.js
CHANGED
|
@@ -2,19 +2,7 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Generate Fusion app URL
|
|
7
|
-
*/
|
|
8
|
-
function generateFusionUrl(deployment, appExternalId, versionTag) {
|
|
9
|
-
const { org, project, baseUrl } = deployment;
|
|
10
|
-
const cluster = baseUrl?.split('//')[1];
|
|
11
|
-
|
|
12
|
-
if (!org || !project || !cluster) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${appExternalId}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
|
|
17
|
-
}
|
|
5
|
+
import { generateFusionUrl } from './utils/fusion-url.js';
|
|
18
6
|
|
|
19
7
|
/**
|
|
20
8
|
* Load app.json from a directory
|
|
@@ -214,6 +202,7 @@ export async function handleDeploy(args) {
|
|
|
214
202
|
name: appConfig.name,
|
|
215
203
|
description: appConfig.description,
|
|
216
204
|
versionTag: appConfig.versionTag,
|
|
205
|
+
infra: appConfig.infra,
|
|
217
206
|
},
|
|
218
207
|
cwd
|
|
219
208
|
);
|
|
@@ -229,7 +218,7 @@ export async function handleDeploy(args) {
|
|
|
229
218
|
}
|
|
230
219
|
|
|
231
220
|
// Generate and display the app URL
|
|
232
|
-
const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag);
|
|
221
|
+
const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag, appConfig.infra);
|
|
233
222
|
if (appUrl) {
|
|
234
223
|
console.log(`\n🔗 Open your app:\n ${appUrl}`);
|
|
235
224
|
}
|
|
@@ -10,20 +10,7 @@ import os from 'node:os';
|
|
|
10
10
|
import path, { resolve } from 'node:path';
|
|
11
11
|
|
|
12
12
|
import { AuthenticationFlow } from './auth/authentication-flow.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Generate Fusion app URL
|
|
16
|
-
*/
|
|
17
|
-
function generateFusionUrl(deployment, appExternalId, versionTag) {
|
|
18
|
-
const { org, project, baseUrl } = deployment;
|
|
19
|
-
const cluster = baseUrl?.split('//')[1];
|
|
20
|
-
|
|
21
|
-
if (!org || !project || !cluster) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${appExternalId}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
|
|
26
|
-
}
|
|
13
|
+
import { generateFusionUrl } from './utils/fusion-url.js';
|
|
27
14
|
|
|
28
15
|
// Default OAuth configuration for CDF
|
|
29
16
|
const DEFAULT_CONFIG = {
|
|
@@ -324,40 +311,52 @@ export async function handleDeployInteractive(args) {
|
|
|
324
311
|
console.log('\n✅ Authentication successful!');
|
|
325
312
|
console.log(`\n📤 Deploying to ${deployment.project}...`);
|
|
326
313
|
|
|
327
|
-
|
|
328
|
-
const { CogniteClient } = await import('@cognite/sdk');
|
|
329
|
-
const { CdfApplicationDeployer, ApplicationPackager } = await import('../dist/deploy/index.js');
|
|
330
|
-
|
|
331
|
-
// Create SDK with the obtained token
|
|
332
|
-
const sdk = new CogniteClient({
|
|
333
|
-
appId: appConfig.externalId,
|
|
334
|
-
project: deployment.project,
|
|
335
|
-
baseUrl: deployment.baseUrl,
|
|
336
|
-
getToken: async () => tokens.access_token,
|
|
337
|
-
});
|
|
338
|
-
await sdk.authenticate();
|
|
314
|
+
const { ApplicationPackager } = await import('../dist/deploy/index.js');
|
|
339
315
|
|
|
340
316
|
// Package the application
|
|
341
317
|
const distPath = `${cwd}/dist`;
|
|
342
318
|
const packager = new ApplicationPackager(distPath);
|
|
343
319
|
const zipFilename = await packager.createZip('app.zip', true);
|
|
344
320
|
|
|
345
|
-
// Deploy to CDF
|
|
346
|
-
const deployer = new CdfApplicationDeployer(sdk);
|
|
347
|
-
await deployer.deploy(
|
|
348
|
-
appConfig.externalId,
|
|
349
|
-
appConfig.name,
|
|
350
|
-
appConfig.description,
|
|
351
|
-
appConfig.versionTag,
|
|
352
|
-
zipFilename,
|
|
353
|
-
deployment.published
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
// Clean up zip file
|
|
357
321
|
try {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
322
|
+
const { CogniteClient } = await import('@cognite/sdk');
|
|
323
|
+
const sdk = new CogniteClient({
|
|
324
|
+
appId: appConfig.externalId,
|
|
325
|
+
project: deployment.project,
|
|
326
|
+
baseUrl: deployment.baseUrl,
|
|
327
|
+
getToken: async () => tokens.access_token,
|
|
328
|
+
});
|
|
329
|
+
await sdk.authenticate();
|
|
330
|
+
|
|
331
|
+
if (appConfig.infra === 'appsApi') {
|
|
332
|
+
const { AppHostingDeployer } = await import('../dist/deploy/index.js');
|
|
333
|
+
const deployer = new AppHostingDeployer(sdk);
|
|
334
|
+
await deployer.deploy(
|
|
335
|
+
appConfig.externalId,
|
|
336
|
+
appConfig.name,
|
|
337
|
+
appConfig.description,
|
|
338
|
+
appConfig.versionTag,
|
|
339
|
+
zipFilename,
|
|
340
|
+
deployment.published || published,
|
|
341
|
+
);
|
|
342
|
+
} else {
|
|
343
|
+
const { CdfApplicationDeployer } = await import('../dist/deploy/index.js');
|
|
344
|
+
const deployer = new CdfApplicationDeployer(sdk);
|
|
345
|
+
await deployer.deploy(
|
|
346
|
+
appConfig.externalId,
|
|
347
|
+
appConfig.name,
|
|
348
|
+
appConfig.description,
|
|
349
|
+
appConfig.versionTag,
|
|
350
|
+
zipFilename,
|
|
351
|
+
deployment.published || published,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
} finally {
|
|
355
|
+
try {
|
|
356
|
+
unlinkSync(zipFilename);
|
|
357
|
+
} catch {
|
|
358
|
+
// Ignore cleanup errors
|
|
359
|
+
}
|
|
361
360
|
}
|
|
362
361
|
|
|
363
362
|
console.log(`\n✅ Successfully deployed ${appConfig.name} to ${deployment.project}`);
|
|
@@ -369,7 +368,7 @@ export async function handleDeployInteractive(args) {
|
|
|
369
368
|
}
|
|
370
369
|
|
|
371
370
|
// Generate and display the app URL
|
|
372
|
-
const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag);
|
|
371
|
+
const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag, appConfig.infra);
|
|
373
372
|
if (appUrl) {
|
|
374
373
|
console.log(`\n🔗 Open your app:\n ${appUrl}`);
|
|
375
374
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a Fusion app URL from deployment config and app metadata.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} deployment - Deployment config (org, project, baseUrl)
|
|
5
|
+
* @param {string} appExternalId - App external ID
|
|
6
|
+
* @param {string} versionTag - Version tag (used for legacy infra URL only)
|
|
7
|
+
* @param {string} [infra] - Infrastructure type; "appsApi" uses the new dune-app-host path
|
|
8
|
+
* @returns {string|null} URL, or null if required fields are missing
|
|
9
|
+
*/
|
|
10
|
+
export function generateFusionUrl(deployment, appExternalId, versionTag, infra) {
|
|
11
|
+
const { org, project, baseUrl } = deployment;
|
|
12
|
+
let cluster = null;
|
|
13
|
+
try {
|
|
14
|
+
cluster = baseUrl ? new URL(baseUrl).host : null;
|
|
15
|
+
} catch {
|
|
16
|
+
// baseUrl is not a valid URL; cluster stays null and we return null below
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!org || !project || !cluster) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (infra === 'appsApi') {
|
|
24
|
+
return `https://${org}.fusion.cognite.com/${project}/dune-app-host/app/${encodeURIComponent(appExternalId)}?cluster=${cluster}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${encodeURIComponent(appExternalId)}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { generateFusionUrl } from './fusion-url.js';
|
|
4
|
+
|
|
5
|
+
const BASE_DEPLOYMENT = {
|
|
6
|
+
org: 'acme',
|
|
7
|
+
project: 'my-project',
|
|
8
|
+
baseUrl: 'https://cognite.test',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe(generateFusionUrl.name, () => {
|
|
12
|
+
it('returns null when org is missing', () => {
|
|
13
|
+
const result = generateFusionUrl({ ...BASE_DEPLOYMENT, org: '' }, 'app-id', '1.0.0');
|
|
14
|
+
expect(result).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns null when project is missing', () => {
|
|
18
|
+
const result = generateFusionUrl({ ...BASE_DEPLOYMENT, project: '' }, 'app-id', '1.0.0');
|
|
19
|
+
expect(result).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns null when baseUrl yields no cluster', () => {
|
|
23
|
+
// 'no-protocol' is not a valid URL, so new URL() throws and cluster stays null
|
|
24
|
+
const result = generateFusionUrl({ ...BASE_DEPLOYMENT, baseUrl: 'no-protocol' }, 'app-id', '1.0.0');
|
|
25
|
+
expect(result).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns legacy URL when infra is undefined', () => {
|
|
29
|
+
const result = generateFusionUrl(BASE_DEPLOYMENT, 'my-app', '1.2.3');
|
|
30
|
+
expect(result).toBe(
|
|
31
|
+
'https://acme.fusion.cognite.com/my-project/streamlit-apps/dune/my-app-1.2.3?cluster=cognite.test&workspace=industrial-tools'
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns App Hosting URL when infra is appsApi', () => {
|
|
36
|
+
const result = generateFusionUrl(BASE_DEPLOYMENT, 'my-app', '1.2.3', 'appsApi');
|
|
37
|
+
expect(result).toBe(
|
|
38
|
+
'https://acme.fusion.cognite.com/my-project/dune-app-host/app/my-app?cluster=cognite.test'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('extracts cluster from baseUrl hostname', () => {
|
|
43
|
+
const result = generateFusionUrl(
|
|
44
|
+
{ ...BASE_DEPLOYMENT, baseUrl: 'https://az-ams-sp-002.cognitedata.com' },
|
|
45
|
+
'my-app',
|
|
46
|
+
'1.0.0'
|
|
47
|
+
);
|
|
48
|
+
expect(result).toContain('cluster=az-ams-sp-002.cognitedata.com');
|
|
49
|
+
});
|
|
50
|
+
});
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -3,6 +3,19 @@ import * as react from 'react';
|
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
import { CogniteClient } from '@cognite/sdk';
|
|
5
5
|
|
|
6
|
+
interface AppSdkAuthProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
loadingComponent?: ReactNode;
|
|
9
|
+
errorComponent?: (error: string) => ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Auth provider for apps running in the new Fusion app host (dune-app-host).
|
|
13
|
+
* Uses @cognite/app-sdk's Comlink handshake to connect to the host and obtain
|
|
14
|
+
* tokens on-demand. Exposes the same DuneContextValue ({ sdk, isLoading,
|
|
15
|
+
* error }) as DuneAuthProvider so useDune() works unchanged.
|
|
16
|
+
*/
|
|
17
|
+
declare const AppSdkAuthProvider: ({ children, loadingComponent, errorComponent, }: AppSdkAuthProviderProps) => react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
6
19
|
interface DuneContextValue {
|
|
7
20
|
sdk: CogniteClient;
|
|
8
21
|
isLoading: boolean;
|
|
@@ -35,4 +48,4 @@ declare const getToken: (clientId: string, clientSecret: string) => Promise<any>
|
|
|
35
48
|
declare const createCDFSDK: (config: CDFConfig) => Promise<CogniteClient>;
|
|
36
49
|
declare const EMPTY_SDK: CogniteClient;
|
|
37
50
|
|
|
38
|
-
export { type CDFConfig, DuneAuthProvider, DuneAuthProviderContext, type DuneAuthProviderProps, type DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune };
|
|
51
|
+
export { AppSdkAuthProvider, type CDFConfig, DuneAuthProvider, DuneAuthProviderContext, type DuneAuthProviderProps, type DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune };
|
package/dist/auth/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AppSdkAuthProvider,
|
|
2
3
|
DuneAuthProvider,
|
|
3
4
|
DuneAuthProviderContext,
|
|
4
5
|
EMPTY_SDK,
|
|
5
6
|
createCDFSDK,
|
|
6
7
|
getToken,
|
|
7
8
|
useDune
|
|
8
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-XWZCFZUH.js";
|
|
9
10
|
export {
|
|
11
|
+
AppSdkAuthProvider,
|
|
10
12
|
DuneAuthProvider,
|
|
11
13
|
DuneAuthProviderContext,
|
|
12
14
|
EMPTY_SDK,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/auth/app-sdk-auth-provider.tsx
|
|
2
|
+
import { connectToHostApp } from "@cognite/app-sdk";
|
|
3
|
+
import { CogniteClient as CogniteClient3 } from "@cognite/sdk";
|
|
4
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
5
|
+
|
|
1
6
|
// src/auth/dune-auth-provider.tsx
|
|
2
7
|
import { CogniteClient as CogniteClient2 } from "@cognite/sdk";
|
|
3
8
|
import { createContext, useEffect, useState } from "react";
|
|
@@ -131,6 +136,50 @@ var FusionIframeAuthenticationInnerProvider = ({
|
|
|
131
136
|
return /* @__PURE__ */ jsx(DuneAuthProviderContext.Provider, { value: { sdk, isLoading, error }, children });
|
|
132
137
|
};
|
|
133
138
|
|
|
139
|
+
// src/auth/app-sdk-auth-provider.tsx
|
|
140
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
141
|
+
var AppSdkAuthProvider = ({
|
|
142
|
+
children,
|
|
143
|
+
loadingComponent = /* @__PURE__ */ jsx2("div", { children: "Loading CDF authentication..." }),
|
|
144
|
+
errorComponent = (error) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
145
|
+
"Authentication error: ",
|
|
146
|
+
error
|
|
147
|
+
] })
|
|
148
|
+
}) => {
|
|
149
|
+
const [sdk, setSdk] = useState2(EMPTY_SDK);
|
|
150
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
151
|
+
const [error, setError] = useState2();
|
|
152
|
+
useEffect2(() => {
|
|
153
|
+
connectToHostApp().then(async ({ api }) => {
|
|
154
|
+
const [project, baseUrl] = await Promise.all([
|
|
155
|
+
api.getProject(),
|
|
156
|
+
api.getBaseUrl()
|
|
157
|
+
]);
|
|
158
|
+
const client = new CogniteClient3({
|
|
159
|
+
appId: "dune-app",
|
|
160
|
+
project,
|
|
161
|
+
baseUrl,
|
|
162
|
+
oidcTokenProvider: async () => api.getAccessToken()
|
|
163
|
+
});
|
|
164
|
+
await client.authenticate();
|
|
165
|
+
setSdk(client);
|
|
166
|
+
setError(void 0);
|
|
167
|
+
}).catch((err) => {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
169
|
+
setError(message);
|
|
170
|
+
}).finally(() => {
|
|
171
|
+
setIsLoading(false);
|
|
172
|
+
});
|
|
173
|
+
}, []);
|
|
174
|
+
if (error && errorComponent) {
|
|
175
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: errorComponent(error) });
|
|
176
|
+
}
|
|
177
|
+
if (isLoading && loadingComponent) {
|
|
178
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: loadingComponent });
|
|
179
|
+
}
|
|
180
|
+
return /* @__PURE__ */ jsx2(DuneAuthProviderContext.Provider, { value: { sdk, isLoading, error }, children });
|
|
181
|
+
};
|
|
182
|
+
|
|
134
183
|
// src/auth/use-dune.ts
|
|
135
184
|
import { useContext } from "react";
|
|
136
185
|
var useDune = () => {
|
|
@@ -147,5 +196,6 @@ export {
|
|
|
147
196
|
EMPTY_SDK,
|
|
148
197
|
DuneAuthProviderContext,
|
|
149
198
|
DuneAuthProvider,
|
|
199
|
+
AppSdkAuthProvider,
|
|
150
200
|
useDune
|
|
151
201
|
};
|
package/dist/cli/cli.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cli/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// cli/commands/skills.ts
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { InvalidArgumentError } from "commander";
|
|
10
|
+
var SKILL_SOURCE = "cognitedata/dune-skills";
|
|
11
|
+
var SUPPORTED_AGENTS = ["claude-code", "cursor"];
|
|
12
|
+
var AGENT_FLAGS = SUPPORTED_AGENTS.flatMap((agent) => ["-a", agent]);
|
|
13
|
+
var SKILLS_BIN = createRequire(import.meta.url).resolve("skills/bin/cli.mjs");
|
|
14
|
+
function execSkillsCli(args, options = {}) {
|
|
15
|
+
execFileSync(process.execPath, [SKILLS_BIN, ...args], {
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
cwd: process.cwd(),
|
|
18
|
+
...options
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function validateSource(value) {
|
|
22
|
+
if (!/^[\w.-]+\/[\w.-]+$/.test(value)) {
|
|
23
|
+
throw new InvalidArgumentError(
|
|
24
|
+
"Expected owner/repo format (e.g., cognitedata/dune-skills)"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function buildPullArgs(options) {
|
|
30
|
+
const args = ["add", options.source, ...AGENT_FLAGS];
|
|
31
|
+
if (options.skill) {
|
|
32
|
+
args.push("--skill", options.skill);
|
|
33
|
+
} else if (!options.interactive) {
|
|
34
|
+
args.push("--skill", "*", "-y");
|
|
35
|
+
}
|
|
36
|
+
if (options.global) {
|
|
37
|
+
args.push("--global");
|
|
38
|
+
}
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
function handlePull(options) {
|
|
42
|
+
console.log(`\u{1F504} Pulling skills from ${options.source}...`);
|
|
43
|
+
execSkillsCli(buildPullArgs(options));
|
|
44
|
+
console.log("\n\u2705 Skills pulled successfully");
|
|
45
|
+
}
|
|
46
|
+
function registerSkillsCommand(parent) {
|
|
47
|
+
const skills = parent.command("skills").description("Manage AI agent skills for your Dune app");
|
|
48
|
+
skills.command("pull").description("Pull all skills into your project").option(
|
|
49
|
+
"--source <owner/repo>",
|
|
50
|
+
"Skills repository",
|
|
51
|
+
validateSource,
|
|
52
|
+
SKILL_SOURCE
|
|
53
|
+
).option("--skill <name>", "Pull a specific skill by name").option("-i, --interactive", "Interactively select which skills to install", false).option("--global", "Install skills globally", false).action(handlePull);
|
|
54
|
+
skills.command("list").description("List installed skills").action(() => {
|
|
55
|
+
execSkillsCli(["list"]);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// cli/cli.ts
|
|
60
|
+
var program = new Command();
|
|
61
|
+
program.name("dune").description("Build and deploy React apps to Cognite Data Fusion").version("0.4.1");
|
|
62
|
+
registerSkillsCommand(program);
|
|
63
|
+
program.parse();
|