@cognite/dune 0.3.1 → 0.3.2
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/config/eslint.config.mjs.ejs.t +96 -0
- package/_templates/app/new/config/tailwind.config.js.ejs.t +1 -5
- package/_templates/app/new/config/vite.config.ts.ejs.t +9 -10
- package/_templates/app/new/config/vitest.config.ts.ejs.t +4 -5
- package/_templates/app/new/config/vitest.setup.ts.ejs.t +1 -2
- package/_templates/app/new/cursor/mcp.json.ejs.t +0 -5
- package/_templates/app/new/cursor/rules.mdc.ejs.t +1 -2
- package/_templates/app/new/prompt.js +29 -29
- package/_templates/app/new/root/index.html.ejs.t +3 -3
- package/_templates/app/new/root/package.json.ejs.t +11 -5
- package/_templates/app/new/src/App.test.tsx.ejs.t +32 -20
- package/_templates/app/new/src/App.tsx.ejs.t +118 -7
- package/_templates/app/new/src/lib/utils.ts.ejs.t +2 -3
- package/_templates/app/new/src/main.tsx.ejs.t +8 -8
- package/_templates/app/new/src/styles.css.ejs.t +5 -19
- package/bin/auth/authentication-flow.js +16 -14
- package/bin/auth/callback-server.js +23 -23
- package/bin/auth/certificate-manager.js +13 -13
- package/bin/auth/client-credentials.js +31 -32
- package/bin/auth/oauth-client.js +7 -7
- package/bin/cli.js +31 -30
- package/bin/deploy-command.js +32 -32
- package/bin/deploy-interactive-command.js +73 -73
- package/bin/skills-command.js +28 -28
- package/bin/utils/crypto.js +7 -8
- package/dist/auth/index.d.ts +10 -13
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-VIBN7U5H.js → chunk-53VTKDSC.js} +1 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/auth/dune-auth-provider.tsx +17 -16
- package/src/auth/index.ts +5 -5
- package/src/auth/use-dune.ts +5 -4
- package/src/auth/utils.ts +18 -18
- package/src/deploy/application-deployer.ts +12 -11
- package/src/deploy/application-packager.ts +11 -10
- package/src/deploy/deploy.ts +7 -6
- package/src/deploy/get-sdk.ts +4 -3
- package/src/deploy/index.ts +6 -6
- package/src/deploy/login.ts +7 -7
- package/src/index.ts +1 -1
- package/src/vite/fusion-open-plugin.ts +12 -12
- package/src/vite/index.ts +1 -1
- package/_templates/app/new/config/biome.json.ejs.t +0 -54
- package/_templates/app/new/config/components.json.ejs.t +0 -28
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Orchestrates the complete OAuth 2.0 authentication flow with PKCE.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import open from
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import {
|
|
7
|
+
import open from 'open';
|
|
8
|
+
|
|
9
|
+
import { CryptoUtils } from '../utils/crypto.js';
|
|
10
|
+
|
|
11
|
+
import { CallbackServer } from './callback-server.js';
|
|
12
|
+
import { CertificateManager } from './certificate-manager.js';
|
|
13
|
+
import { OAuthClient } from './oauth-client.js';
|
|
12
14
|
|
|
13
15
|
export class AuthenticationFlow {
|
|
14
16
|
/**
|
|
@@ -25,10 +27,10 @@ export class AuthenticationFlow {
|
|
|
25
27
|
* @throws {Error} If configuration is invalid
|
|
26
28
|
*/
|
|
27
29
|
validateConfig() {
|
|
28
|
-
if (this.config.clientId ===
|
|
30
|
+
if (this.config.clientId === 'your-client-id' || !this.config.clientId) {
|
|
29
31
|
throw new Error(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
'Please set the CDF_CLIENT_ID environment variable\n' +
|
|
33
|
+
' Example: export CDF_CLIENT_ID=<your-client-id>'
|
|
32
34
|
);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -39,7 +41,7 @@ export class AuthenticationFlow {
|
|
|
39
41
|
* @returns {Promise<Object>} Access tokens
|
|
40
42
|
*/
|
|
41
43
|
async login(organization) {
|
|
42
|
-
console.log(
|
|
44
|
+
console.log('🔐 Starting CDF login flow...\n');
|
|
43
45
|
|
|
44
46
|
this.validateConfig();
|
|
45
47
|
|
|
@@ -70,15 +72,15 @@ export class AuthenticationFlow {
|
|
|
70
72
|
if (organization) {
|
|
71
73
|
console.log(`🏢 Organization: ${organization}`);
|
|
72
74
|
}
|
|
73
|
-
console.log(
|
|
75
|
+
console.log('🚀 Opening browser for authentication...\n');
|
|
74
76
|
|
|
75
77
|
try {
|
|
76
78
|
await open(authUrl);
|
|
77
|
-
} catch (
|
|
78
|
-
console.error(
|
|
79
|
-
console.error(
|
|
79
|
+
} catch (_error) {
|
|
80
|
+
console.error('❌ Failed to open browser automatically.');
|
|
81
|
+
console.error('Please open this URL manually:\n');
|
|
80
82
|
console.error(authUrl);
|
|
81
|
-
console.error(
|
|
83
|
+
console.error('');
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
// Wait for callback
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* HTTPS server that handles OAuth 2.0 authorization callbacks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import https from
|
|
8
|
-
import { parse } from
|
|
7
|
+
import https from 'node:https';
|
|
8
|
+
import { parse } from 'node:url';
|
|
9
9
|
|
|
10
10
|
export class CallbackServer {
|
|
11
11
|
/**
|
|
@@ -31,7 +31,7 @@ export class CallbackServer {
|
|
|
31
31
|
*/
|
|
32
32
|
static generateHtml(type, title, message) {
|
|
33
33
|
const animation =
|
|
34
|
-
type ===
|
|
34
|
+
type === 'success'
|
|
35
35
|
? `
|
|
36
36
|
<style>
|
|
37
37
|
@keyframes checkmark {
|
|
@@ -42,7 +42,7 @@ export class CallbackServer {
|
|
|
42
42
|
h1 { animation: checkmark 0.5s ease-out; }
|
|
43
43
|
</style>
|
|
44
44
|
`
|
|
45
|
-
:
|
|
45
|
+
: '';
|
|
46
46
|
|
|
47
47
|
return `
|
|
48
48
|
<html>
|
|
@@ -68,9 +68,9 @@ export class CallbackServer {
|
|
|
68
68
|
async handleCallback(req, res, expectedState, codeVerifier, oauthClient) {
|
|
69
69
|
const parsedUrl = parse(req.url, true);
|
|
70
70
|
|
|
71
|
-
if (parsedUrl.pathname !==
|
|
71
|
+
if (parsedUrl.pathname !== '/') {
|
|
72
72
|
res.writeHead(404);
|
|
73
|
-
res.end(
|
|
73
|
+
res.end('Not found');
|
|
74
74
|
return { shouldClose: false };
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -78,8 +78,8 @@ export class CallbackServer {
|
|
|
78
78
|
|
|
79
79
|
// Handle authentication error
|
|
80
80
|
if (error) {
|
|
81
|
-
res.writeHead(400, {
|
|
82
|
-
res.end(CallbackServer.generateHtml(
|
|
81
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
82
|
+
res.end(CallbackServer.generateHtml('error', 'Authentication Error', String(error)));
|
|
83
83
|
return {
|
|
84
84
|
shouldClose: true,
|
|
85
85
|
error: new Error(`Authentication error: ${error}`),
|
|
@@ -88,20 +88,20 @@ export class CallbackServer {
|
|
|
88
88
|
|
|
89
89
|
// Validate state to prevent CSRF
|
|
90
90
|
if (returnedState !== expectedState) {
|
|
91
|
-
res.writeHead(400, {
|
|
91
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
92
92
|
res.end(
|
|
93
93
|
CallbackServer.generateHtml(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
'security',
|
|
95
|
+
'Security Error',
|
|
96
|
+
'State mismatch. Possible CSRF attack.'
|
|
97
97
|
)
|
|
98
98
|
);
|
|
99
|
-
return { shouldClose: true, error: new Error(
|
|
99
|
+
return { shouldClose: true, error: new Error('State mismatch') };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Exchange code for tokens
|
|
103
103
|
try {
|
|
104
|
-
console.log(
|
|
104
|
+
console.log('🔄 Exchanging authorization code for tokens...');
|
|
105
105
|
const openIdConfig = await oauthClient.fetchOpenIdConfiguration();
|
|
106
106
|
const tokens = await oauthClient.exchangeCodeForTokens(
|
|
107
107
|
openIdConfig.token_endpoint,
|
|
@@ -109,19 +109,19 @@ export class CallbackServer {
|
|
|
109
109
|
codeVerifier
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
-
res.writeHead(200, {
|
|
112
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
113
113
|
res.end(
|
|
114
114
|
CallbackServer.generateHtml(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
'success',
|
|
116
|
+
'Login Successful!',
|
|
117
|
+
'You can close this window and return to the terminal.'
|
|
118
118
|
)
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
return { shouldClose: true, tokens };
|
|
122
122
|
} catch (err) {
|
|
123
|
-
res.writeHead(500, {
|
|
124
|
-
res.end(CallbackServer.generateHtml(
|
|
123
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
124
|
+
res.end(CallbackServer.generateHtml('error', 'Token Exchange Failed', err.message));
|
|
125
125
|
return { shouldClose: true, error: err };
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -155,8 +155,8 @@ export class CallbackServer {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
// Handle server errors
|
|
158
|
-
this.server.on(
|
|
159
|
-
if (error.code ===
|
|
158
|
+
this.server.on('error', (error) => {
|
|
159
|
+
if (error.code === 'EADDRINUSE') {
|
|
160
160
|
console.error(
|
|
161
161
|
`❌ Port ${this.config.port} is already in use. Please close the other application.`
|
|
162
162
|
);
|
|
@@ -174,7 +174,7 @@ export class CallbackServer {
|
|
|
174
174
|
// Setup timeout
|
|
175
175
|
setTimeout(() => {
|
|
176
176
|
this.server.close();
|
|
177
|
-
reject(new Error(
|
|
177
|
+
reject(new Error('Login timeout - no response received within 5 minutes'));
|
|
178
178
|
}, this.config.loginTimeout);
|
|
179
179
|
});
|
|
180
180
|
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* for the local HTTPS OAuth callback server.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { execSync } from
|
|
9
|
-
import fs from
|
|
10
|
-
import path from
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
export class CertificateManager {
|
|
13
13
|
/**
|
|
@@ -15,8 +15,8 @@ export class CertificateManager {
|
|
|
15
15
|
*/
|
|
16
16
|
constructor(certDir) {
|
|
17
17
|
this.certDir = certDir;
|
|
18
|
-
this.keyPath = path.join(certDir,
|
|
19
|
-
this.certPath = path.join(certDir,
|
|
18
|
+
this.keyPath = path.join(certDir, 'localhost-key.pem');
|
|
19
|
+
this.certPath = path.join(certDir, 'localhost-cert.pem');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -44,7 +44,7 @@ export class CertificateManager {
|
|
|
44
44
|
* @throws {Error} If OpenSSL is not available or certificate generation fails
|
|
45
45
|
*/
|
|
46
46
|
generateCertificate() {
|
|
47
|
-
console.log(
|
|
47
|
+
console.log('🔐 Generating self-signed certificate for HTTPS...');
|
|
48
48
|
|
|
49
49
|
// Ensure directory exists
|
|
50
50
|
if (!fs.existsSync(this.certDir)) {
|
|
@@ -54,16 +54,16 @@ export class CertificateManager {
|
|
|
54
54
|
try {
|
|
55
55
|
const cmd = `openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -keyout "${this.keyPath}" -out "${this.certPath}" -days 365 2>/dev/null`;
|
|
56
56
|
|
|
57
|
-
execSync(cmd, { stdio:
|
|
58
|
-
console.log(
|
|
57
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
58
|
+
console.log('✅ Certificate generated and saved to ~/.cdf-login/\n');
|
|
59
59
|
|
|
60
60
|
return this.loadCertificates();
|
|
61
|
-
} catch (
|
|
61
|
+
} catch (_error) {
|
|
62
62
|
throw new Error(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
'Failed to generate self-signed certificate. Make sure openssl is installed.\n' +
|
|
64
|
+
' On macOS: openssl is pre-installed\n' +
|
|
65
|
+
' On Linux: sudo apt-get install openssl\n' +
|
|
66
|
+
' On Windows: Install OpenSSL or use WSL'
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
* @returns {string} Cluster name like "aw-dub-gp-001"
|
|
5
5
|
*/
|
|
6
6
|
function extractClusterFromUrl(url) {
|
|
7
|
-
if (!url) return
|
|
7
|
+
if (!url) return '';
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
10
|
const urlObj = new URL(url);
|
|
11
11
|
const hostname = urlObj.hostname;
|
|
12
12
|
// Remove .cognitedata.com suffix
|
|
13
|
-
return hostname.replace(/\.cognitedata\.com$/,
|
|
14
|
-
} catch (
|
|
13
|
+
return hostname.replace(/\.cognitedata\.com$/, '');
|
|
14
|
+
} catch (_error) {
|
|
15
15
|
// Fallback to simple string manipulation
|
|
16
|
-
let cluster = url.replace(/^https?:\/\//,
|
|
17
|
-
cluster = cluster.split(
|
|
18
|
-
cluster = cluster.split(
|
|
19
|
-
cluster = cluster.replace(/\.cognitedata\.com$/,
|
|
16
|
+
let cluster = url.replace(/^https?:\/\//, '');
|
|
17
|
+
cluster = cluster.split('/')[0]; // Remove path
|
|
18
|
+
cluster = cluster.split(':')[0]; // Remove port
|
|
19
|
+
cluster = cluster.replace(/\.cognitedata\.com$/, '');
|
|
20
20
|
return cluster;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -28,7 +28,6 @@ function extractClusterFromUrl(url) {
|
|
|
28
28
|
* Supports both CDF and Entra ID authentication providers.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
32
31
|
export class ClientCredentialsAuth {
|
|
33
32
|
/**
|
|
34
33
|
* Get access token using client credentials (CDF provider)
|
|
@@ -38,23 +37,23 @@ export class ClientCredentialsAuth {
|
|
|
38
37
|
*/
|
|
39
38
|
static async getTokenCdf(clientId, clientSecret) {
|
|
40
39
|
if (!clientId || !clientSecret) {
|
|
41
|
-
throw new Error(
|
|
40
|
+
throw new Error('CLIENT_ID and CLIENT_SECRET must be provided');
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString(
|
|
43
|
+
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
|
45
44
|
const header = `Basic ${credentials}`;
|
|
46
45
|
|
|
47
|
-
console.log(
|
|
46
|
+
console.log('🔑 Authenticating with CDF client credentials...');
|
|
48
47
|
|
|
49
48
|
try {
|
|
50
|
-
const response = await fetch(
|
|
51
|
-
method:
|
|
49
|
+
const response = await fetch('https://auth.cognite.com/oauth2/token', {
|
|
50
|
+
method: 'POST',
|
|
52
51
|
headers: {
|
|
53
52
|
Authorization: header,
|
|
54
|
-
|
|
53
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
55
54
|
},
|
|
56
55
|
body: new URLSearchParams({
|
|
57
|
-
grant_type:
|
|
56
|
+
grant_type: 'client_credentials',
|
|
58
57
|
}),
|
|
59
58
|
});
|
|
60
59
|
|
|
@@ -68,7 +67,7 @@ export class ClientCredentialsAuth {
|
|
|
68
67
|
const data = await response.json();
|
|
69
68
|
|
|
70
69
|
if (!data.access_token) {
|
|
71
|
-
throw new Error(
|
|
70
|
+
throw new Error('No access token returned from authentication');
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
return data.access_token;
|
|
@@ -88,23 +87,23 @@ export class ClientCredentialsAuth {
|
|
|
88
87
|
static async getTokenEntra(clientId, clientSecret, tokenUrl, scope) {
|
|
89
88
|
if (!clientId || !clientSecret || !tokenUrl || !scope) {
|
|
90
89
|
throw new Error(
|
|
91
|
-
|
|
90
|
+
'CLIENT_ID, CLIENT_SECRET, TOKEN_URL, and SCOPES must be provided for Entra ID'
|
|
92
91
|
);
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
console.log(
|
|
94
|
+
console.log('🔑 Authenticating with Entra ID client credentials...');
|
|
96
95
|
|
|
97
96
|
try {
|
|
98
97
|
const response = await fetch(tokenUrl, {
|
|
99
|
-
method:
|
|
98
|
+
method: 'POST',
|
|
100
99
|
headers: {
|
|
101
|
-
|
|
100
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
102
101
|
},
|
|
103
102
|
body: new URLSearchParams({
|
|
104
103
|
client_id: clientId,
|
|
105
104
|
client_secret: clientSecret,
|
|
106
105
|
scope: scope,
|
|
107
|
-
grant_type:
|
|
106
|
+
grant_type: 'client_credentials',
|
|
108
107
|
}),
|
|
109
108
|
});
|
|
110
109
|
|
|
@@ -118,7 +117,7 @@ export class ClientCredentialsAuth {
|
|
|
118
117
|
const data = await response.json();
|
|
119
118
|
|
|
120
119
|
if (!data.access_token) {
|
|
121
|
-
throw new Error(
|
|
120
|
+
throw new Error('No access token returned from authentication');
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
return data.access_token;
|
|
@@ -140,12 +139,12 @@ export class ClientCredentialsAuth {
|
|
|
140
139
|
* @returns {Promise<string>} Access token
|
|
141
140
|
*/
|
|
142
141
|
static async getToken(config) {
|
|
143
|
-
const provider = (config.provider ||
|
|
142
|
+
const provider = (config.provider || 'cdf').toLowerCase();
|
|
144
143
|
|
|
145
|
-
if (provider ===
|
|
144
|
+
if (provider === 'cdf') {
|
|
146
145
|
return ClientCredentialsAuth.getTokenCdf(config.clientId, config.clientSecret);
|
|
147
146
|
}
|
|
148
|
-
if (provider ===
|
|
147
|
+
if (provider === 'entra') {
|
|
149
148
|
const tokenUrl = ClientCredentialsAuth.buildEntraTokenUrl(config.tenantId, config.tokenUrl);
|
|
150
149
|
const scope = ClientCredentialsAuth.buildEntraScope(config.cluster, config.scopes);
|
|
151
150
|
|
|
@@ -171,7 +170,7 @@ export class ClientCredentialsAuth {
|
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
if (!tenantId) {
|
|
174
|
-
throw new Error(
|
|
173
|
+
throw new Error('TENANT_ID is required for Entra ID authentication (or provide TOKEN_URL)');
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
@@ -188,7 +187,7 @@ export class ClientCredentialsAuth {
|
|
|
188
187
|
|
|
189
188
|
if (!cluster) {
|
|
190
189
|
throw new Error(
|
|
191
|
-
|
|
190
|
+
'Cluster information is required to auto-generate scopes (or provide SCOPES)'
|
|
192
191
|
);
|
|
193
192
|
}
|
|
194
193
|
|
|
@@ -200,13 +199,13 @@ export class ClientCredentialsAuth {
|
|
|
200
199
|
* @returns {Object} Configuration object with provider and credentials
|
|
201
200
|
*/
|
|
202
201
|
static loadFromEnv() {
|
|
203
|
-
const provider = (process.env.PROVIDER ||
|
|
202
|
+
const provider = (process.env.PROVIDER || 'cdf').toLowerCase();
|
|
204
203
|
const clientId = process.env.CLIENT_ID;
|
|
205
204
|
const clientSecret = process.env.CLIENT_SECRET;
|
|
206
205
|
|
|
207
206
|
if (!clientId || !clientSecret) {
|
|
208
207
|
throw new Error(
|
|
209
|
-
|
|
208
|
+
'Missing required environment variables: CLIENT_ID and CLIENT_SECRET must be set'
|
|
210
209
|
);
|
|
211
210
|
}
|
|
212
211
|
|
|
@@ -217,21 +216,21 @@ export class ClientCredentialsAuth {
|
|
|
217
216
|
};
|
|
218
217
|
|
|
219
218
|
// Load provider-specific configuration
|
|
220
|
-
if (provider ===
|
|
219
|
+
if (provider === 'entra') {
|
|
221
220
|
config.tenantId = process.env.TENANT_ID;
|
|
222
221
|
config.tokenUrl = process.env.TOKEN_URL;
|
|
223
222
|
config.scopes = process.env.SCOPES;
|
|
224
223
|
|
|
225
224
|
// Validate required fields
|
|
226
225
|
if (!config.tenantId && !config.tokenUrl) {
|
|
227
|
-
throw new Error(
|
|
226
|
+
throw new Error('For Entra ID provider, either TENANT_ID or TOKEN_URL must be set');
|
|
228
227
|
}
|
|
229
228
|
|
|
230
229
|
// Extract cluster from BASE_URL for scope generation
|
|
231
230
|
if (process.env.BASE_URL) {
|
|
232
231
|
config.cluster = extractClusterFromUrl(process.env.BASE_URL);
|
|
233
232
|
} else if (!config.scopes) {
|
|
234
|
-
throw new Error(
|
|
233
|
+
throw new Error('For Entra ID provider, either BASE_URL or SCOPES must be set');
|
|
235
234
|
}
|
|
236
235
|
}
|
|
237
236
|
|
package/bin/auth/oauth-client.js
CHANGED
|
@@ -41,7 +41,7 @@ export class OAuthClient {
|
|
|
41
41
|
*/
|
|
42
42
|
async exchangeCodeForTokens(tokenEndpoint, code, codeVerifier) {
|
|
43
43
|
const params = new URLSearchParams({
|
|
44
|
-
grant_type:
|
|
44
|
+
grant_type: 'authorization_code',
|
|
45
45
|
client_id: this.config.clientId,
|
|
46
46
|
code: code,
|
|
47
47
|
redirect_uri: this.config.redirectUri,
|
|
@@ -49,9 +49,9 @@ export class OAuthClient {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const response = await fetch(tokenEndpoint, {
|
|
52
|
-
method:
|
|
52
|
+
method: 'POST',
|
|
53
53
|
headers: {
|
|
54
|
-
|
|
54
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
55
55
|
},
|
|
56
56
|
body: params.toString(),
|
|
57
57
|
});
|
|
@@ -76,15 +76,15 @@ export class OAuthClient {
|
|
|
76
76
|
const params = new URLSearchParams({
|
|
77
77
|
client_id: this.config.clientId,
|
|
78
78
|
redirect_uri: this.config.redirectUri,
|
|
79
|
-
response_type:
|
|
80
|
-
scope:
|
|
79
|
+
response_type: 'code',
|
|
80
|
+
scope: 'openid profile email',
|
|
81
81
|
state: state,
|
|
82
82
|
code_challenge: codeChallenge,
|
|
83
|
-
code_challenge_method:
|
|
83
|
+
code_challenge_method: 'S256',
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
if (organization) {
|
|
87
|
-
params.append(
|
|
87
|
+
params.append('organization_hint', organization);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
return `${authorizationEndpoint}?${params.toString()}`;
|
package/bin/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { basename, dirname, normalize, resolve } from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import { Logger, runner } from "hygen";
|
|
3
|
+
import { basename, dirname, normalize, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
import { Logger, runner } from 'hygen';
|
|
7
|
+
|
|
8
|
+
const defaultTemplates = resolve(dirname(fileURLToPath(import.meta.url)), '..', '_templates');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Creates a prompter function for hygen that handles useCurrentDir logic
|
|
@@ -23,7 +24,7 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
|
|
|
23
24
|
if (isCurrentDir) {
|
|
24
25
|
const currentDirName = basename(process.cwd());
|
|
25
26
|
const modifiedPrompts = prompts.map((p) => {
|
|
26
|
-
if (p.name ===
|
|
27
|
+
if (p.name === 'name') {
|
|
27
28
|
return {
|
|
28
29
|
...p,
|
|
29
30
|
initial: currentDirName,
|
|
@@ -42,7 +43,7 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
|
|
|
42
43
|
if (dirName) {
|
|
43
44
|
// Pre-fill the name prompt with directory name (user can change it)
|
|
44
45
|
const modifiedPrompts = prompts.map((p) => {
|
|
45
|
-
if (p.name ===
|
|
46
|
+
if (p.name === 'name') {
|
|
46
47
|
return {
|
|
47
48
|
...p,
|
|
48
49
|
initial: dirName,
|
|
@@ -72,19 +73,19 @@ async function main() {
|
|
|
72
73
|
const command = args[0];
|
|
73
74
|
|
|
74
75
|
// Handle different commands
|
|
75
|
-
if (command ===
|
|
76
|
+
if (command === 'create' || command === 'new' || !command || command === '.') {
|
|
76
77
|
// TODO(DUNE-362): simplify directory resolution logic
|
|
77
78
|
// Parse directory argument
|
|
78
79
|
// Support: npx @cognite/dune create . or npx @cognite/dune .
|
|
79
80
|
let dirArg;
|
|
80
|
-
if (command ===
|
|
81
|
+
if (command === '.') {
|
|
81
82
|
// If first arg is ".", treat it as directory argument with no command
|
|
82
|
-
dirArg =
|
|
83
|
-
} else if (command ===
|
|
83
|
+
dirArg = '.';
|
|
84
|
+
} else if (command === 'create' || command === 'new') {
|
|
84
85
|
dirArg = args[1];
|
|
85
86
|
}
|
|
86
87
|
// When !command: no args, dirArg stays undefined (create app interactively)
|
|
87
|
-
const isCurrentDir = dirArg ===
|
|
88
|
+
const isCurrentDir = dirArg === '.' || dirArg === './';
|
|
88
89
|
|
|
89
90
|
// Extract directory name from path (handles ./test-folder -> test-folder)
|
|
90
91
|
let dirName = null;
|
|
@@ -94,10 +95,10 @@ async function main() {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
// Run the app generator
|
|
97
|
-
const hygenArgs = [
|
|
98
|
+
const hygenArgs = ['app', 'new'];
|
|
98
99
|
|
|
99
100
|
try {
|
|
100
|
-
const enquirer = await import(
|
|
101
|
+
const enquirer = await import('enquirer');
|
|
101
102
|
|
|
102
103
|
// Track the app name from prompts
|
|
103
104
|
let appName = null;
|
|
@@ -110,7 +111,7 @@ async function main() {
|
|
|
110
111
|
appName = name;
|
|
111
112
|
}),
|
|
112
113
|
exec: async (action, body) => {
|
|
113
|
-
const { execa } = await import(
|
|
114
|
+
const { execa } = await import('execa');
|
|
114
115
|
const opts = body && body.length > 0 ? { input: body } : {};
|
|
115
116
|
return execa(action, { shell: true, ...opts });
|
|
116
117
|
},
|
|
@@ -118,9 +119,9 @@ async function main() {
|
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
// Print success message with next steps
|
|
121
|
-
const installAndDevSteps =
|
|
122
|
-
const deployLabel =
|
|
123
|
-
const deployCommand =
|
|
122
|
+
const installAndDevSteps = ' pnpm install\n pnpm dev';
|
|
123
|
+
const deployLabel = 'To deploy your app:';
|
|
124
|
+
const deployCommand = 'npx @cognite/dune deploy:interactive';
|
|
124
125
|
|
|
125
126
|
if (isCurrentDir) {
|
|
126
127
|
console.log(`
|
|
@@ -133,7 +134,7 @@ ${deployLabel}
|
|
|
133
134
|
${deployCommand}
|
|
134
135
|
`);
|
|
135
136
|
} else {
|
|
136
|
-
const appDir = dirName || appName ||
|
|
137
|
+
const appDir = dirName || appName || 'your-app';
|
|
137
138
|
console.log(`
|
|
138
139
|
✅ App created successfully!
|
|
139
140
|
|
|
@@ -151,32 +152,32 @@ ${deployLabel}
|
|
|
151
152
|
|
|
152
153
|
// Pull AI skills into the new app
|
|
153
154
|
try {
|
|
154
|
-
const { runSkillsCli, defaultPullArgs } = await import(
|
|
155
|
+
const { runSkillsCli, defaultPullArgs } = await import('./skills-command.js');
|
|
155
156
|
const skillsCwd = isCurrentDir ? process.cwd() : resolve(process.cwd(), dirName || appName);
|
|
156
|
-
console.log(
|
|
157
|
+
console.log('🧠 Pulling Dune skills...');
|
|
157
158
|
runSkillsCli(defaultPullArgs(), {
|
|
158
159
|
cwd: skillsCwd,
|
|
159
160
|
timeout: 30_000,
|
|
160
161
|
});
|
|
161
|
-
console.log(
|
|
162
|
+
console.log('✅ Skills installed successfully');
|
|
162
163
|
} catch (error) {
|
|
163
164
|
// Skills pull failure should never block app creation
|
|
164
|
-
console.warn(
|
|
165
|
+
console.warn('⚠️ Could not pull Dune skills:', error.message);
|
|
165
166
|
}
|
|
166
167
|
} catch (error) {
|
|
167
|
-
console.error(
|
|
168
|
+
console.error('Error:', error.message);
|
|
168
169
|
process.exit(1);
|
|
169
170
|
}
|
|
170
|
-
} else if (command ===
|
|
171
|
-
const { handleDeploy } = await import(
|
|
171
|
+
} else if (command === 'deploy') {
|
|
172
|
+
const { handleDeploy } = await import('./deploy-command.js');
|
|
172
173
|
await handleDeploy(args.slice(1));
|
|
173
|
-
} else if (command ===
|
|
174
|
-
const { handleDeployInteractive } = await import(
|
|
174
|
+
} else if (command === 'deploy:interactive') {
|
|
175
|
+
const { handleDeployInteractive } = await import('./deploy-interactive-command.js');
|
|
175
176
|
await handleDeployInteractive(args.slice(1));
|
|
176
|
-
} else if (command ===
|
|
177
|
-
const { handleSkillsCommand } = await import(
|
|
177
|
+
} else if (command === 'skills') {
|
|
178
|
+
const { handleSkillsCommand } = await import('./skills-command.js');
|
|
178
179
|
handleSkillsCommand(args.slice(1));
|
|
179
|
-
} else if (command ===
|
|
180
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
180
181
|
console.log(`
|
|
181
182
|
@cognite/dune - Build and deploy React apps to Cognite Data Fusion
|
|
182
183
|
|