@cognite/dune 0.1.1 → 0.2.0

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 (49) hide show
  1. package/_templates/app/new/config/biome.json.ejs.t +54 -0
  2. package/_templates/app/new/config/components.json.ejs.t +28 -0
  3. package/_templates/app/new/config/tailwind.config.js.ejs.t +15 -0
  4. package/_templates/app/new/{tsconfig.json.ejs.t → config/tsconfig.json.ejs.t} +4 -1
  5. package/_templates/app/new/config/vite.config.ts.ejs.t +27 -0
  6. package/_templates/app/new/cursor/data-modeling.mdc.ejs.t +1994 -0
  7. package/_templates/app/new/cursor/mcp.json.ejs.t +15 -0
  8. package/_templates/app/new/cursor/rules.mdc.ejs.t +12 -0
  9. package/_templates/app/new/root/PRD.md.ejs.t +5 -0
  10. package/_templates/app/new/{package.json.ejs.t → root/package.json.ejs.t} +12 -3
  11. package/_templates/app/new/{App.test.tsx.ejs.t → src/App.test.tsx.ejs.t} +5 -5
  12. package/_templates/app/new/{App.tsx.ejs.t → src/App.tsx.ejs.t} +2 -3
  13. package/_templates/app/new/src/lib/utils.ts.ejs.t +10 -0
  14. package/_templates/app/new/{main.tsx.ejs.t → src/main.tsx.ejs.t} +2 -0
  15. package/_templates/app/new/src/styles.css.ejs.t +25 -0
  16. package/bin/auth/authentication-flow.js +89 -0
  17. package/bin/auth/callback-server.js +181 -0
  18. package/bin/auth/certificate-manager.js +81 -0
  19. package/bin/auth/client-credentials.js +240 -0
  20. package/bin/auth/oauth-client.js +92 -0
  21. package/bin/cli.js +45 -5
  22. package/bin/deploy-command.js +246 -0
  23. package/bin/deploy-interactive-command.js +382 -0
  24. package/bin/utils/crypto.js +35 -0
  25. package/dist/deploy/index.d.ts +7 -0
  26. package/dist/deploy/index.js +43 -1
  27. package/package.json +12 -7
  28. package/src/auth/dune-auth-provider.tsx +97 -0
  29. package/src/auth/index.ts +11 -0
  30. package/src/auth/use-dune.ts +12 -0
  31. package/src/auth/utils.ts +91 -0
  32. package/src/deploy/application-deployer.ts +137 -0
  33. package/src/deploy/application-packager.ts +73 -0
  34. package/src/deploy/deploy.ts +33 -0
  35. package/src/deploy/get-sdk.ts +17 -0
  36. package/src/deploy/index.ts +9 -0
  37. package/src/deploy/login.ts +69 -0
  38. package/src/deploy/types.ts +15 -0
  39. package/src/index.ts +2 -0
  40. package/src/vite/fusion-open-plugin.ts +51 -0
  41. package/src/vite/index.ts +2 -0
  42. package/_templates/app/new/biome.json.ejs.t +0 -25
  43. package/_templates/app/new/vite.config.ts.ejs.t +0 -15
  44. /package/_templates/app/new/{tsconfig.node.json.ejs.t → config/tsconfig.node.json.ejs.t} +0 -0
  45. /package/_templates/app/new/{vitest.config.ts.ejs.t → config/vitest.config.ts.ejs.t} +0 -0
  46. /package/_templates/app/new/{vitest.setup.ts.ejs.t → config/vitest.setup.ts.ejs.t} +0 -0
  47. /package/_templates/app/new/{app.json.ejs.t → root/app.json.ejs.t} +0 -0
  48. /package/_templates/app/new/{gitignore.ejs.t → root/gitignore.ejs.t} +0 -0
  49. /package/_templates/app/new/{index.html.ejs.t → root/index.html.ejs.t} +0 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ to: <%= name %>/.cursor/mcp.json
3
+ ---
4
+ {
5
+ "mcpServers": {
6
+ "shadcn": {
7
+ "command": "npx",
8
+ "args": ["shadcn@latest", "mcp"]
9
+ },
10
+ "cdf-datamodeling-mcp": {
11
+ "command": "npx cdf-datamodelling-mcp-server"
12
+ }
13
+ }
14
+ }
15
+
@@ -0,0 +1,12 @@
1
+ ---
2
+ to: <%= name %>/.cursor/rules.mdc
3
+ ---
4
+ ---
5
+ description: Describes global rules
6
+ alwaysApply: true
7
+ ---
8
+
9
+ - Use @aura shadcn components as much as possible. Stick with the style guide provided.
10
+ - Look at the PRD.md file at the root for requirements. If the file is empty, write some yourself as the first thing, and confirm with the user the requirements.
11
+ - The PRD.md file should contain the following: 1.1 Purpose, 1.2 Scope, 1.3 Target Audience, 2. Functional Requirements, 3. Non-Functional Requirements, 4. Data Models & Integration, 4.1 CDF Data Model, 4.1.1 Existing views, 4.1.2 New views, 4.1.3 Spaces, 5. User Stories
12
+
@@ -0,0 +1,5 @@
1
+ ---
2
+ to: <%= name %>/PRD.md
3
+ ---
4
+
5
+ Add your product requirements here, or ask AI to collaborate on this file with you.
@@ -15,17 +15,22 @@ to: <%= name %>/package.json
15
15
  "test:watch": "vitest",
16
16
  "test:ui": "vitest --ui",
17
17
  "lint": "biome check .",
18
- "lint:fix": "biome check --write ."
18
+ "lint:fix": "biome check --write .",
19
+ "deploy": "pnpm run build && echo 'Deploying...' && echo 'Deployed!'",
20
+ "deploy-preview": "pnpm run build && echo 'Deploying preview...' && echo 'Deployed preview!'"
19
21
  },
20
22
  "dependencies": {
21
23
  "@cognite/sdk": "^10.3.0",
22
- "@cognite/dune": "^0.1.0",
24
+ "@cognite/dune": "^0.1.2",
23
25
  "@tanstack/react-query": "^5.90.10",
26
+ "clsx": "^2.1.1",
24
27
  "react": "^19.2.0",
25
- "react-dom": "^19.2.0"
28
+ "react-dom": "^19.2.0",
29
+ "tailwind-merge": "^3.4.0"
26
30
  },
27
31
  "devDependencies": {
28
32
  "@biomejs/biome": "^1.9.4",
33
+ "@tailwindcss/vite": "^4.1.17",
29
34
  "@testing-library/jest-dom": "^6.6.3",
30
35
  "@testing-library/react": "^16.1.0",
31
36
  "@testing-library/user-event": "^14.5.2",
@@ -34,7 +39,11 @@ to: <%= name %>/package.json
34
39
  "@types/react-dom": "^19.2.3",
35
40
  "@vitejs/plugin-react": "^5.1.1",
36
41
  "@vitest/ui": "^2.1.8",
42
+ "autoprefixer": "^10.4.22",
37
43
  "happy-dom": "^20.0.2",
44
+ "postcss": "^8.5.6",
45
+ "shadcn": "^3.5.2",
46
+ "tailwindcss": "^4.1.17",
38
47
  "typescript": "^5.0.0",
39
48
  "vite": "^7.2.4",
40
49
  "vite-plugin-mkcert": "^1.17.9",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  to: <%= name %>/src/App.test.tsx
3
3
  ---
4
- import * as dune from "@cognite/dune";
4
+ import * as duneAuth from "@cognite/dune";
5
5
  import { render, screen } from "@testing-library/react";
6
6
  import { beforeEach, describe, expect, it, vi } from "vitest";
7
7
  import App from "./App";
@@ -15,7 +15,7 @@ describe("App", () => {
15
15
  });
16
16
 
17
17
  it("renders loading state", () => {
18
- vi.mocked(dune.useDune).mockReturnValue({
18
+ vi.mocked(duneAuth.useDune).mockReturnValue({
19
19
  sdk: { project: "test-project" } as any,
20
20
  isLoading: true,
21
21
  });
@@ -25,14 +25,14 @@ describe("App", () => {
25
25
  });
26
26
 
27
27
  it("renders app with project name", () => {
28
- vi.mocked(dune.useDune).mockReturnValue({
28
+ vi.mocked(duneAuth.useDune).mockReturnValue({
29
29
  sdk: { project: "my-test-project" } as any,
30
30
  isLoading: false,
31
31
  });
32
32
 
33
33
  render(<App />);
34
- expect(screen.getByText("<%= displayName %>")).toBeInTheDocument();
35
- expect(screen.getByText("Project: my-test-project")).toBeInTheDocument();
34
+ expect(screen.getByText("Welcome to my-test-project")).toBeInTheDocument();
35
+ expect(screen.getByText("Your Dune app is ready.")).toBeInTheDocument();
36
36
  });
37
37
  });
38
38
 
@@ -5,7 +5,6 @@ import { useDune } from "@cognite/dune";
5
5
 
6
6
  function App() {
7
7
  const { sdk, isLoading } = useDune();
8
- const project = sdk.project;
9
8
 
10
9
  if (isLoading) {
11
10
  return <div>Loading...</div>;
@@ -13,8 +12,8 @@ function App() {
13
12
 
14
13
  return (
15
14
  <div>
16
- <h1><%= displayName %></h1>
17
- <p>Project: {project}</p>
15
+ <h1>Welcome to {sdk.project}</h1>
16
+ <p>Your Dune app is ready.</p>
18
17
  </div>
19
18
  );
20
19
  }
@@ -0,0 +1,10 @@
1
+ ---
2
+ to: <%= name %>/src/lib/utils.ts
3
+ ---
4
+ import { type ClassValue, clsx } from "clsx";
5
+ import { twMerge } from "tailwind-merge";
6
+
7
+ export function cn(...inputs: ClassValue[]) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+
@@ -7,6 +7,8 @@ import React from "react";
7
7
  import ReactDOM from "react-dom/client";
8
8
  import App from "./App.tsx";
9
9
 
10
+ import "./styles.css";
11
+
10
12
  const queryClient = new QueryClient({
11
13
  defaultOptions: {
12
14
  queries: {
@@ -0,0 +1,25 @@
1
+ ---
2
+ to: <%= name %>/src/styles.css
3
+ ---
4
+ @import "tailwindcss";
5
+
6
+ body {
7
+ font-family: system-ui, -apple-system, sans-serif;
8
+ min-height: 100vh;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
13
+ }
14
+
15
+ h1 {
16
+ font-size: 2rem;
17
+ font-weight: 600;
18
+ color: #1a1a2e;
19
+ margin-bottom: 0.5rem;
20
+ }
21
+
22
+ p {
23
+ color: #6b7280;
24
+ }
25
+
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Authentication Flow
3
+ *
4
+ * Orchestrates the complete OAuth 2.0 authentication flow with PKCE.
5
+ */
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";
12
+
13
+ export class AuthenticationFlow {
14
+ /**
15
+ * @param {Object} config - Authentication configuration
16
+ */
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.oauthClient = new OAuthClient(config);
20
+ this.certManager = new CertificateManager(config.certDir);
21
+ }
22
+
23
+ /**
24
+ * Validate client configuration
25
+ * @throws {Error} If configuration is invalid
26
+ */
27
+ validateConfig() {
28
+ if (this.config.clientId === "your-client-id" || !this.config.clientId) {
29
+ throw new Error(
30
+ "Please set the CDF_CLIENT_ID environment variable\n" +
31
+ " Example: export CDF_CLIENT_ID=<your-client-id>"
32
+ );
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Execute complete OAuth login flow
38
+ * @param {string} [organization] - Optional organization hint
39
+ * @returns {Promise<Object>} Access tokens
40
+ */
41
+ async login(organization) {
42
+ console.log("🔐 Starting CDF login flow...\n");
43
+
44
+ this.validateConfig();
45
+
46
+ // Generate PKCE parameters (code verifier must be 43-128 chars per RFC 7636)
47
+ const codeVerifier = CryptoUtils.generateRandomString(64);
48
+ const codeChallenge = CryptoUtils.generateCodeChallenge(codeVerifier);
49
+ const state = CryptoUtils.generateRandomString(16);
50
+
51
+ // Get OpenID configuration
52
+ console.log(`📡 Fetching OpenID configuration from ${this.config.authority}...`);
53
+ const openIdConfig = await this.oauthClient.fetchOpenIdConfiguration();
54
+
55
+ // Build authorization URL
56
+ const authUrl = this.oauthClient.buildAuthorizationUrl(
57
+ openIdConfig.authorization_endpoint,
58
+ codeChallenge,
59
+ state,
60
+ organization
61
+ );
62
+
63
+ // Get SSL certificates
64
+ const sslOptions = this.certManager.getOrCreateCertificates();
65
+
66
+ // Start callback server
67
+ const callbackServer = new CallbackServer(this.config, sslOptions);
68
+
69
+ // Open browser
70
+ if (organization) {
71
+ console.log(`🏢 Organization: ${organization}`);
72
+ }
73
+ console.log("🚀 Opening browser for authentication...\n");
74
+
75
+ try {
76
+ await open(authUrl);
77
+ } catch (error) {
78
+ console.error("❌ Failed to open browser automatically.");
79
+ console.error("Please open this URL manually:\n");
80
+ console.error(authUrl);
81
+ console.error("");
82
+ }
83
+
84
+ // Wait for callback
85
+ const tokens = await callbackServer.start(state, codeVerifier, this.oauthClient);
86
+
87
+ return tokens;
88
+ }
89
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * OAuth Callback Server
3
+ *
4
+ * HTTPS server that handles OAuth 2.0 authorization callbacks.
5
+ */
6
+
7
+ import https from "node:https";
8
+ import { parse } from "node:url";
9
+
10
+ export class CallbackServer {
11
+ /**
12
+ * @param {Object} config - Server configuration
13
+ * @param {number} config.port - Port to listen on
14
+ * @param {number} config.loginTimeout - Timeout in milliseconds
15
+ * @param {Object} sslOptions - SSL certificate options
16
+ * @param {Buffer} sslOptions.key - SSL private key
17
+ * @param {Buffer} sslOptions.cert - SSL certificate
18
+ */
19
+ constructor(config, sslOptions) {
20
+ this.config = config;
21
+ this.sslOptions = sslOptions;
22
+ this.server = null;
23
+ }
24
+
25
+ /**
26
+ * Generate HTML response page
27
+ * @param {string} type - Response type (success, error, security)
28
+ * @param {string} title - Page title
29
+ * @param {string} message - Message to display
30
+ * @returns {string} HTML string
31
+ */
32
+ static generateHtml(type, title, message) {
33
+ const animation =
34
+ type === "success"
35
+ ? `
36
+ <style>
37
+ @keyframes checkmark {
38
+ 0% { transform: scale(0); }
39
+ 50% { transform: scale(1.2); }
40
+ 100% { transform: scale(1); }
41
+ }
42
+ h1 { animation: checkmark 0.5s ease-out; }
43
+ </style>
44
+ `
45
+ : "";
46
+
47
+ return `
48
+ <html>
49
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
50
+ <h1>${title}</h1>
51
+ <p>${message}</p>
52
+ <p>You can close this window.</p>
53
+ ${animation}
54
+ </body>
55
+ </html>
56
+ `;
57
+ }
58
+
59
+ /**
60
+ * Handle OAuth callback request
61
+ * @param {Object} req - HTTP request
62
+ * @param {Object} res - HTTP response
63
+ * @param {string} expectedState - Expected CSRF state
64
+ * @param {string} codeVerifier - PKCE code verifier
65
+ * @param {Object} oauthClient - OAuth client instance
66
+ * @returns {Promise<{ shouldClose: boolean, tokens?: Object, error?: Error }>}
67
+ */
68
+ async handleCallback(req, res, expectedState, codeVerifier, oauthClient) {
69
+ const parsedUrl = parse(req.url, true);
70
+
71
+ if (parsedUrl.pathname !== "/") {
72
+ res.writeHead(404);
73
+ res.end("Not found");
74
+ return { shouldClose: false };
75
+ }
76
+
77
+ const { code, state: returnedState, error } = parsedUrl.query;
78
+
79
+ // Handle authentication error
80
+ if (error) {
81
+ res.writeHead(400, { "Content-Type": "text/html" });
82
+ res.end(CallbackServer.generateHtml("error", "Authentication Error", String(error)));
83
+ return {
84
+ shouldClose: true,
85
+ error: new Error(`Authentication error: ${error}`),
86
+ };
87
+ }
88
+
89
+ // Validate state to prevent CSRF
90
+ if (returnedState !== expectedState) {
91
+ res.writeHead(400, { "Content-Type": "text/html" });
92
+ res.end(
93
+ CallbackServer.generateHtml(
94
+ "security",
95
+ "Security Error",
96
+ "State mismatch. Possible CSRF attack."
97
+ )
98
+ );
99
+ return { shouldClose: true, error: new Error("State mismatch") };
100
+ }
101
+
102
+ // Exchange code for tokens
103
+ try {
104
+ console.log("🔄 Exchanging authorization code for tokens...");
105
+ const openIdConfig = await oauthClient.fetchOpenIdConfiguration();
106
+ const tokens = await oauthClient.exchangeCodeForTokens(
107
+ openIdConfig.token_endpoint,
108
+ code,
109
+ codeVerifier
110
+ );
111
+
112
+ res.writeHead(200, { "Content-Type": "text/html" });
113
+ res.end(
114
+ CallbackServer.generateHtml(
115
+ "success",
116
+ "Login Successful!",
117
+ "You can close this window and return to the terminal."
118
+ )
119
+ );
120
+
121
+ return { shouldClose: true, tokens };
122
+ } catch (err) {
123
+ res.writeHead(500, { "Content-Type": "text/html" });
124
+ res.end(CallbackServer.generateHtml("error", "Token Exchange Failed", err.message));
125
+ return { shouldClose: true, error: err };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Start HTTPS server and wait for OAuth callback
131
+ * @param {string} expectedState - Expected CSRF state
132
+ * @param {string} codeVerifier - PKCE code verifier
133
+ * @param {Object} oauthClient - OAuth client instance
134
+ * @returns {Promise<Object>} Access tokens
135
+ */
136
+ async start(expectedState, codeVerifier, oauthClient) {
137
+ return new Promise((resolve, reject) => {
138
+ this.server = https.createServer(this.sslOptions, async (req, res) => {
139
+ const result = await this.handleCallback(
140
+ req,
141
+ res,
142
+ expectedState,
143
+ codeVerifier,
144
+ oauthClient
145
+ );
146
+
147
+ if (result.shouldClose) {
148
+ this.server.close();
149
+ if (result.error) {
150
+ reject(result.error);
151
+ } else {
152
+ resolve(result.tokens);
153
+ }
154
+ }
155
+ });
156
+
157
+ // Handle server errors
158
+ this.server.on("error", (error) => {
159
+ if (error.code === "EADDRINUSE") {
160
+ console.error(
161
+ `❌ Port ${this.config.port} is already in use. Please close the other application.`
162
+ );
163
+ } else {
164
+ console.error(`❌ Server error: ${error.message}`);
165
+ }
166
+ reject(error);
167
+ });
168
+
169
+ // Start listening
170
+ this.server.listen(this.config.port, () => {
171
+ console.log(`🌐 Local HTTPS server started on https://localhost:${this.config.port}`);
172
+ });
173
+
174
+ // Setup timeout
175
+ setTimeout(() => {
176
+ this.server.close();
177
+ reject(new Error("Login timeout - no response received within 5 minutes"));
178
+ }, this.config.loginTimeout);
179
+ });
180
+ }
181
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * SSL Certificate Management
3
+ *
4
+ * Handles generation and loading of self-signed SSL certificates
5
+ * for the local HTTPS OAuth callback server.
6
+ */
7
+
8
+ import { execSync } from "node:child_process";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+
12
+ export class CertificateManager {
13
+ /**
14
+ * @param {string} certDir - Directory to store certificates
15
+ */
16
+ constructor(certDir) {
17
+ this.certDir = certDir;
18
+ this.keyPath = path.join(certDir, "localhost-key.pem");
19
+ this.certPath = path.join(certDir, "localhost-cert.pem");
20
+ }
21
+
22
+ /**
23
+ * Check if valid certificates exist
24
+ * @returns {boolean} True if both key and certificate files exist
25
+ */
26
+ certificatesExist() {
27
+ return fs.existsSync(this.keyPath) && fs.existsSync(this.certPath);
28
+ }
29
+
30
+ /**
31
+ * Load existing certificates from disk
32
+ * @returns {{ key: Buffer, cert: Buffer }} SSL certificate pair
33
+ */
34
+ loadCertificates() {
35
+ return {
36
+ key: fs.readFileSync(this.keyPath),
37
+ cert: fs.readFileSync(this.certPath),
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Generate new self-signed certificate using OpenSSL
43
+ * @returns {{ key: Buffer, cert: Buffer }} Newly generated certificate pair
44
+ * @throws {Error} If OpenSSL is not available or certificate generation fails
45
+ */
46
+ generateCertificate() {
47
+ console.log("🔐 Generating self-signed certificate for HTTPS...");
48
+
49
+ // Ensure directory exists
50
+ if (!fs.existsSync(this.certDir)) {
51
+ fs.mkdirSync(this.certDir, { recursive: true });
52
+ }
53
+
54
+ try {
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
+
57
+ execSync(cmd, { stdio: "pipe" });
58
+ console.log("✅ Certificate generated and saved to ~/.cdf-login/\n");
59
+
60
+ return this.loadCertificates();
61
+ } catch (error) {
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"
67
+ );
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Get existing certificates or generate new ones
73
+ * @returns {{ key: Buffer, cert: Buffer }} SSL certificate pair
74
+ */
75
+ getOrCreateCertificates() {
76
+ if (this.certificatesExist()) {
77
+ return this.loadCertificates();
78
+ }
79
+ return this.generateCertificate();
80
+ }
81
+ }