@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.
Files changed (46) hide show
  1. package/_templates/app/new/config/eslint.config.mjs.ejs.t +96 -0
  2. package/_templates/app/new/config/tailwind.config.js.ejs.t +1 -5
  3. package/_templates/app/new/config/vite.config.ts.ejs.t +9 -10
  4. package/_templates/app/new/config/vitest.config.ts.ejs.t +4 -5
  5. package/_templates/app/new/config/vitest.setup.ts.ejs.t +1 -2
  6. package/_templates/app/new/cursor/mcp.json.ejs.t +0 -5
  7. package/_templates/app/new/cursor/rules.mdc.ejs.t +1 -2
  8. package/_templates/app/new/prompt.js +29 -29
  9. package/_templates/app/new/root/index.html.ejs.t +3 -3
  10. package/_templates/app/new/root/package.json.ejs.t +11 -5
  11. package/_templates/app/new/src/App.test.tsx.ejs.t +32 -20
  12. package/_templates/app/new/src/App.tsx.ejs.t +118 -7
  13. package/_templates/app/new/src/lib/utils.ts.ejs.t +2 -3
  14. package/_templates/app/new/src/main.tsx.ejs.t +8 -8
  15. package/_templates/app/new/src/styles.css.ejs.t +5 -19
  16. package/bin/auth/authentication-flow.js +16 -14
  17. package/bin/auth/callback-server.js +23 -23
  18. package/bin/auth/certificate-manager.js +13 -13
  19. package/bin/auth/client-credentials.js +31 -32
  20. package/bin/auth/oauth-client.js +7 -7
  21. package/bin/cli.js +31 -30
  22. package/bin/deploy-command.js +32 -32
  23. package/bin/deploy-interactive-command.js +73 -73
  24. package/bin/skills-command.js +28 -28
  25. package/bin/utils/crypto.js +7 -8
  26. package/dist/auth/index.d.ts +10 -13
  27. package/dist/auth/index.js +1 -1
  28. package/dist/{chunk-VIBN7U5H.js → chunk-53VTKDSC.js} +1 -2
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +1 -1
  31. package/package.json +1 -1
  32. package/src/auth/dune-auth-provider.tsx +17 -16
  33. package/src/auth/index.ts +5 -5
  34. package/src/auth/use-dune.ts +5 -4
  35. package/src/auth/utils.ts +18 -18
  36. package/src/deploy/application-deployer.ts +12 -11
  37. package/src/deploy/application-packager.ts +11 -10
  38. package/src/deploy/deploy.ts +7 -6
  39. package/src/deploy/get-sdk.ts +4 -3
  40. package/src/deploy/index.ts +6 -6
  41. package/src/deploy/login.ts +7 -7
  42. package/src/index.ts +1 -1
  43. package/src/vite/fusion-open-plugin.ts +12 -12
  44. package/src/vite/index.ts +1 -1
  45. package/_templates/app/new/config/biome.json.ejs.t +0 -54
  46. 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 "open";
8
- import { CryptoUtils } from "../utils/crypto.js";
9
- import { CallbackServer } from "./callback-server.js";
10
- import { CertificateManager } from "./certificate-manager.js";
11
- import { OAuthClient } from "./oauth-client.js";
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 === "your-client-id" || !this.config.clientId) {
30
+ if (this.config.clientId === 'your-client-id' || !this.config.clientId) {
29
31
  throw new Error(
30
- "Please set the CDF_CLIENT_ID environment variable\n" +
31
- " Example: export CDF_CLIENT_ID=<your-client-id>"
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("🔐 Starting CDF login flow...\n");
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("🚀 Opening browser for authentication...\n");
75
+ console.log('🚀 Opening browser for authentication...\n');
74
76
 
75
77
  try {
76
78
  await open(authUrl);
77
- } catch (error) {
78
- console.error("❌ Failed to open browser automatically.");
79
- console.error("Please open this URL manually:\n");
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 "node:https";
8
- import { parse } from "node:url";
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 === "success"
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("Not found");
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, { "Content-Type": "text/html" });
82
- res.end(CallbackServer.generateHtml("error", "Authentication Error", String(error)));
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, { "Content-Type": "text/html" });
91
+ res.writeHead(400, { 'Content-Type': 'text/html' });
92
92
  res.end(
93
93
  CallbackServer.generateHtml(
94
- "security",
95
- "Security Error",
96
- "State mismatch. Possible CSRF attack."
94
+ 'security',
95
+ 'Security Error',
96
+ 'State mismatch. Possible CSRF attack.'
97
97
  )
98
98
  );
99
- return { shouldClose: true, error: new Error("State mismatch") };
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("🔄 Exchanging authorization code for tokens...");
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, { "Content-Type": "text/html" });
112
+ res.writeHead(200, { 'Content-Type': 'text/html' });
113
113
  res.end(
114
114
  CallbackServer.generateHtml(
115
- "success",
116
- "Login Successful!",
117
- "You can close this window and return to the terminal."
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, { "Content-Type": "text/html" });
124
- res.end(CallbackServer.generateHtml("error", "Token Exchange Failed", err.message));
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("error", (error) => {
159
- if (error.code === "EADDRINUSE") {
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("Login timeout - no response received within 5 minutes"));
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 "node:child_process";
9
- import fs from "node:fs";
10
- import path from "node:path";
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, "localhost-key.pem");
19
- this.certPath = path.join(certDir, "localhost-cert.pem");
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("🔐 Generating self-signed certificate for HTTPS...");
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: "pipe" });
58
- console.log("✅ Certificate generated and saved to ~/.cdf-login/\n");
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 (error) {
61
+ } catch (_error) {
62
62
  throw new Error(
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"
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 (error) {
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("/")[0]; // Remove path
18
- cluster = cluster.split(":")[0]; // Remove port
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("CLIENT_ID and CLIENT_SECRET must be provided");
40
+ throw new Error('CLIENT_ID and CLIENT_SECRET must be provided');
42
41
  }
43
42
 
44
- const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
43
+ const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
45
44
  const header = `Basic ${credentials}`;
46
45
 
47
- console.log("🔑 Authenticating with CDF client credentials...");
46
+ console.log('🔑 Authenticating with CDF client credentials...');
48
47
 
49
48
  try {
50
- const response = await fetch("https://auth.cognite.com/oauth2/token", {
51
- method: "POST",
49
+ const response = await fetch('https://auth.cognite.com/oauth2/token', {
50
+ method: 'POST',
52
51
  headers: {
53
52
  Authorization: header,
54
- "Content-Type": "application/x-www-form-urlencoded",
53
+ 'Content-Type': 'application/x-www-form-urlencoded',
55
54
  },
56
55
  body: new URLSearchParams({
57
- grant_type: "client_credentials",
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("No access token returned from authentication");
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
- "CLIENT_ID, CLIENT_SECRET, TOKEN_URL, and SCOPES must be provided for Entra ID"
90
+ 'CLIENT_ID, CLIENT_SECRET, TOKEN_URL, and SCOPES must be provided for Entra ID'
92
91
  );
93
92
  }
94
93
 
95
- console.log("🔑 Authenticating with Entra ID client credentials...");
94
+ console.log('🔑 Authenticating with Entra ID client credentials...');
96
95
 
97
96
  try {
98
97
  const response = await fetch(tokenUrl, {
99
- method: "POST",
98
+ method: 'POST',
100
99
  headers: {
101
- "Content-Type": "application/x-www-form-urlencoded",
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: "client_credentials",
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("No access token returned from authentication");
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 || "cdf").toLowerCase();
142
+ const provider = (config.provider || 'cdf').toLowerCase();
144
143
 
145
- if (provider === "cdf") {
144
+ if (provider === 'cdf') {
146
145
  return ClientCredentialsAuth.getTokenCdf(config.clientId, config.clientSecret);
147
146
  }
148
- if (provider === "entra") {
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("TENANT_ID is required for Entra ID authentication (or provide TOKEN_URL)");
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
- "Cluster information is required to auto-generate scopes (or provide SCOPES)"
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 || "cdf").toLowerCase();
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
- "Missing required environment variables: CLIENT_ID and CLIENT_SECRET must be set"
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 === "entra") {
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("For Entra ID provider, either TENANT_ID or TOKEN_URL must be set");
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("For Entra ID provider, either BASE_URL or SCOPES must be set");
233
+ throw new Error('For Entra ID provider, either BASE_URL or SCOPES must be set');
235
234
  }
236
235
  }
237
236
 
@@ -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: "authorization_code",
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: "POST",
52
+ method: 'POST',
53
53
  headers: {
54
- "Content-Type": "application/x-www-form-urlencoded",
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: "code",
80
- scope: "openid profile email",
79
+ response_type: 'code',
80
+ scope: 'openid profile email',
81
81
  state: state,
82
82
  code_challenge: codeChallenge,
83
- code_challenge_method: "S256",
83
+ code_challenge_method: 'S256',
84
84
  });
85
85
 
86
86
  if (organization) {
87
- params.append("organization_hint", organization);
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 "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { Logger, runner } from "hygen";
3
+ import { basename, dirname, normalize, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
6
5
 
7
- const defaultTemplates = resolve(dirname(fileURLToPath(import.meta.url)), "..", "_templates");
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 === "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 === "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 === "create" || command === "new" || !command || 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 === "create" || command === "new") {
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 === "." || 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 = ["app", "new"];
98
+ const hygenArgs = ['app', 'new'];
98
99
 
99
100
  try {
100
- const enquirer = await import("enquirer");
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("execa");
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 = " pnpm install\n pnpm dev";
122
- const deployLabel = "To deploy your app:";
123
- const deployCommand = "npx @cognite/dune deploy:interactive";
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 || "your-app";
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("./skills-command.js");
155
+ const { runSkillsCli, defaultPullArgs } = await import('./skills-command.js');
155
156
  const skillsCwd = isCurrentDir ? process.cwd() : resolve(process.cwd(), dirName || appName);
156
- console.log("🧠 Pulling Dune skills...");
157
+ console.log('🧠 Pulling Dune skills...');
157
158
  runSkillsCli(defaultPullArgs(), {
158
159
  cwd: skillsCwd,
159
160
  timeout: 30_000,
160
161
  });
161
- console.log("✅ Skills installed successfully");
162
+ console.log('✅ Skills installed successfully');
162
163
  } catch (error) {
163
164
  // Skills pull failure should never block app creation
164
- console.warn("⚠️ Could not pull Dune skills:", error.message);
165
+ console.warn('⚠️ Could not pull Dune skills:', error.message);
165
166
  }
166
167
  } catch (error) {
167
- console.error("Error:", error.message);
168
+ console.error('Error:', error.message);
168
169
  process.exit(1);
169
170
  }
170
- } else if (command === "deploy") {
171
- const { handleDeploy } = await import("./deploy-command.js");
171
+ } else if (command === 'deploy') {
172
+ const { handleDeploy } = await import('./deploy-command.js');
172
173
  await handleDeploy(args.slice(1));
173
- } else if (command === "deploy:interactive") {
174
- const { handleDeployInteractive } = await import("./deploy-interactive-command.js");
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 === "skills") {
177
- const { handleSkillsCommand } = await import("./skills-command.js");
177
+ } else if (command === 'skills') {
178
+ const { handleSkillsCommand } = await import('./skills-command.js');
178
179
  handleSkillsCommand(args.slice(1));
179
- } else if (command === "help" || command === "--help" || command === "-h") {
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