@cognite/dune 0.1.2 → 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.
- package/_templates/app/new/config/biome.json.ejs.t +54 -0
- package/_templates/app/new/config/components.json.ejs.t +28 -0
- package/_templates/app/new/config/tailwind.config.js.ejs.t +15 -0
- package/_templates/app/new/{tsconfig.json.ejs.t → config/tsconfig.json.ejs.t} +4 -1
- package/_templates/app/new/config/vite.config.ts.ejs.t +27 -0
- package/_templates/app/new/cursor/data-modeling.mdc.ejs.t +1994 -0
- package/_templates/app/new/cursor/mcp.json.ejs.t +15 -0
- package/_templates/app/new/cursor/rules.mdc.ejs.t +12 -0
- package/_templates/app/new/root/PRD.md.ejs.t +5 -0
- package/_templates/app/new/{package.json.ejs.t → root/package.json.ejs.t} +11 -2
- package/_templates/app/new/{App.test.tsx.ejs.t → src/App.test.tsx.ejs.t} +5 -5
- package/_templates/app/new/{App.tsx.ejs.t → src/App.tsx.ejs.t} +2 -3
- package/_templates/app/new/src/lib/utils.ts.ejs.t +10 -0
- package/_templates/app/new/{main.tsx.ejs.t → src/main.tsx.ejs.t} +2 -0
- package/_templates/app/new/src/styles.css.ejs.t +25 -0
- package/bin/auth/authentication-flow.js +89 -0
- package/bin/auth/callback-server.js +181 -0
- package/bin/auth/certificate-manager.js +81 -0
- package/bin/auth/client-credentials.js +240 -0
- package/bin/auth/oauth-client.js +92 -0
- package/bin/cli.js +45 -5
- package/bin/deploy-command.js +246 -0
- package/bin/deploy-interactive-command.js +382 -0
- package/bin/utils/crypto.js +35 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +43 -1
- package/package.json +3 -2
- package/src/deploy/application-deployer.ts +38 -0
- package/src/deploy/deploy.ts +8 -0
- package/_templates/app/new/biome.json.ejs.t +0 -25
- package/_templates/app/new/vite.config.ts.ejs.t +0 -15
- /package/_templates/app/new/{tsconfig.node.json.ejs.t → config/tsconfig.node.json.ejs.t} +0 -0
- /package/_templates/app/new/{vitest.config.ts.ejs.t → config/vitest.config.ts.ejs.t} +0 -0
- /package/_templates/app/new/{vitest.setup.ts.ejs.t → config/vitest.setup.ts.ejs.t} +0 -0
- /package/_templates/app/new/{app.json.ejs.t → root/app.json.ejs.t} +0 -0
- /package/_templates/app/new/{gitignore.ejs.t → root/gitignore.ejs.t} +0 -0
- /package/_templates/app/new/{index.html.ejs.t → root/index.html.ejs.t} +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Deploy Command
|
|
3
|
+
*
|
|
4
|
+
* Handles deployment with browser-based OAuth login instead of environment variables.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { resolve } from "node:path";
|
|
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
|
+
}
|
|
27
|
+
|
|
28
|
+
// Default OAuth configuration for CDF
|
|
29
|
+
const DEFAULT_CONFIG = {
|
|
30
|
+
authority: "https://auth.cognite.com",
|
|
31
|
+
clientId: "c6f97d29-79a5-48ac-85de-1de8229226cb", // CDF CLI public client ID
|
|
32
|
+
redirectUri: "https://localhost:3000",
|
|
33
|
+
port: 3000,
|
|
34
|
+
loginTimeout: 5 * 60 * 1000, // 5 minutes
|
|
35
|
+
certDir: path.join(os.homedir(), ".cdf-login"),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load app.json from a directory
|
|
40
|
+
*/
|
|
41
|
+
function loadAppConfig(appDir) {
|
|
42
|
+
const configPath = resolve(appDir, "app.json");
|
|
43
|
+
if (!existsSync(configPath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`Error parsing app.json: ${error.message}`);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect the package manager being used
|
|
56
|
+
*/
|
|
57
|
+
function detectPackageManager(appDir) {
|
|
58
|
+
if (existsSync(resolve(appDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
59
|
+
if (existsSync(resolve(appDir, "yarn.lock"))) return "yarn";
|
|
60
|
+
if (existsSync(resolve(appDir, "bun.lockb"))) return "bun";
|
|
61
|
+
return "npm";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the app
|
|
66
|
+
*/
|
|
67
|
+
function buildApp(appDir, verbose = true) {
|
|
68
|
+
const pm = detectPackageManager(appDir);
|
|
69
|
+
console.log(`📦 Building app with ${pm}...`);
|
|
70
|
+
|
|
71
|
+
execSync(`${pm} run build`, {
|
|
72
|
+
cwd: appDir,
|
|
73
|
+
stdio: verbose ? "inherit" : "pipe",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log("✅ Build successful");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Print help message
|
|
81
|
+
*/
|
|
82
|
+
function printHelp() {
|
|
83
|
+
console.log(`
|
|
84
|
+
Deploy your Dune app to Cognite Data Fusion (Interactive Login)
|
|
85
|
+
|
|
86
|
+
Usage:
|
|
87
|
+
npx @cognite/dune deploy:interactive [options]
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
--base-url <url> CDF base URL (e.g., https://api.cognitedata.com)
|
|
91
|
+
--project <project> CDF project name
|
|
92
|
+
--org <organization> Organization hint for login
|
|
93
|
+
--deployment, -d <target> Deployment target from app.json (index or name)
|
|
94
|
+
--skip-build Skip the build step
|
|
95
|
+
--published Deploy as published (default: draft)
|
|
96
|
+
--help, -h Show this help message
|
|
97
|
+
|
|
98
|
+
Description:
|
|
99
|
+
This command opens a browser window for you to log in to CDF.
|
|
100
|
+
No environment variables or secrets are required - authentication
|
|
101
|
+
is handled through your browser session.
|
|
102
|
+
|
|
103
|
+
If no options are provided, you'll be prompted to select a deployment
|
|
104
|
+
target from app.json or enter custom values.
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
npx @cognite/dune deploy:interactive # Interactive prompts
|
|
108
|
+
npx @cognite/dune deploy:interactive -d 0 # Use first deployment
|
|
109
|
+
npx @cognite/dune deploy:interactive --base-url https://api.cognitedata.com --project my-project
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Interactive deploy command handler
|
|
115
|
+
*/
|
|
116
|
+
export async function handleDeployInteractive(args) {
|
|
117
|
+
// Check for help first
|
|
118
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
119
|
+
printHelp();
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
|
|
125
|
+
// Load app.json
|
|
126
|
+
const appConfig = loadAppConfig(cwd);
|
|
127
|
+
if (!appConfig) {
|
|
128
|
+
console.error("❌ No app.json found in current directory");
|
|
129
|
+
console.error("Make sure you're running this command from your app's root directory.");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Parse arguments
|
|
134
|
+
let baseUrl = null;
|
|
135
|
+
let project = null;
|
|
136
|
+
let organization = null;
|
|
137
|
+
let deploymentIndex = null;
|
|
138
|
+
let skipBuild = false;
|
|
139
|
+
let published = false;
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < args.length; i++) {
|
|
142
|
+
const arg = args[i];
|
|
143
|
+
if (arg === "--base-url") {
|
|
144
|
+
baseUrl = args[++i];
|
|
145
|
+
} else if (arg === "--project") {
|
|
146
|
+
project = args[++i];
|
|
147
|
+
} else if (arg === "--org") {
|
|
148
|
+
organization = args[++i];
|
|
149
|
+
} else if (arg === "--deployment" || arg === "-d") {
|
|
150
|
+
const value = args[++i];
|
|
151
|
+
if (value === undefined) {
|
|
152
|
+
console.error("❌ --deployment requires a value (index or project name)");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
const idx = Number.parseInt(value, 10);
|
|
156
|
+
if (!Number.isNaN(idx)) {
|
|
157
|
+
deploymentIndex = idx;
|
|
158
|
+
} else if (appConfig.deployments) {
|
|
159
|
+
const found = appConfig.deployments.findIndex(
|
|
160
|
+
(d) => d.project === value || `${d.org}/${d.project}` === value
|
|
161
|
+
);
|
|
162
|
+
if (found === -1) {
|
|
163
|
+
console.error(`❌ No deployment found for project: ${value}`);
|
|
164
|
+
console.log("Available deployments:");
|
|
165
|
+
appConfig.deployments.forEach((d, i) => {
|
|
166
|
+
console.log(` ${i}: ${d.org}/${d.project}`);
|
|
167
|
+
});
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
deploymentIndex = found;
|
|
171
|
+
}
|
|
172
|
+
} else if (arg === "--skip-build") {
|
|
173
|
+
skipBuild = true;
|
|
174
|
+
} else if (arg === "--published") {
|
|
175
|
+
published = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Determine deployment target
|
|
180
|
+
let deployment;
|
|
181
|
+
const enquirer = await import("enquirer");
|
|
182
|
+
|
|
183
|
+
if (baseUrl && project) {
|
|
184
|
+
// Use CLI-provided values
|
|
185
|
+
deployment = {
|
|
186
|
+
baseUrl,
|
|
187
|
+
project,
|
|
188
|
+
org: organization || "unknown",
|
|
189
|
+
published,
|
|
190
|
+
};
|
|
191
|
+
} else if (deploymentIndex !== null) {
|
|
192
|
+
// Use deployment from app.json by index
|
|
193
|
+
if (
|
|
194
|
+
!appConfig.deployments ||
|
|
195
|
+
deploymentIndex < 0 ||
|
|
196
|
+
deploymentIndex >= appConfig.deployments.length
|
|
197
|
+
) {
|
|
198
|
+
console.error(`❌ Invalid deployment index: ${deploymentIndex}`);
|
|
199
|
+
if (appConfig.deployments) {
|
|
200
|
+
console.log(`Available deployments (0-${appConfig.deployments.length - 1}):`);
|
|
201
|
+
appConfig.deployments.forEach((d, i) => {
|
|
202
|
+
console.log(` ${i}: ${d.org}/${d.project}`);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
deployment = appConfig.deployments[deploymentIndex];
|
|
208
|
+
} else if (appConfig.deployments && appConfig.deployments.length > 0) {
|
|
209
|
+
// Prompt user to select from existing deployments or enter custom
|
|
210
|
+
const choices = [
|
|
211
|
+
...appConfig.deployments.map((d) => `${d.org}/${d.project}`),
|
|
212
|
+
"Enter custom target...",
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const { selected } = await enquirer.default.prompt({
|
|
216
|
+
type: "select",
|
|
217
|
+
name: "selected",
|
|
218
|
+
message: "Select deployment target",
|
|
219
|
+
choices,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (selected === "Enter custom target...") {
|
|
223
|
+
const answers = await enquirer.default.prompt([
|
|
224
|
+
{
|
|
225
|
+
type: "input",
|
|
226
|
+
name: "baseUrl",
|
|
227
|
+
message: "CDF Base URL",
|
|
228
|
+
initial: "https://api.cognitedata.com",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
type: "input",
|
|
232
|
+
name: "project",
|
|
233
|
+
message: "CDF Project",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: "input",
|
|
237
|
+
name: "org",
|
|
238
|
+
message: "Organization (for login hint)",
|
|
239
|
+
initial: "",
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
deployment = {
|
|
243
|
+
baseUrl: answers.baseUrl,
|
|
244
|
+
project: answers.project,
|
|
245
|
+
org: answers.org || null,
|
|
246
|
+
published,
|
|
247
|
+
};
|
|
248
|
+
if (answers.org) {
|
|
249
|
+
organization = answers.org;
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
// Find the deployment by matching the selected name
|
|
253
|
+
deployment = appConfig.deployments.find((d) => `${d.org}/${d.project}` === selected);
|
|
254
|
+
}
|
|
255
|
+
} else {
|
|
256
|
+
// No deployments in app.json - prompt for values
|
|
257
|
+
console.log("No deployments configured in app.json. Enter target details:\n");
|
|
258
|
+
|
|
259
|
+
const answers = await enquirer.default.prompt([
|
|
260
|
+
{
|
|
261
|
+
type: "input",
|
|
262
|
+
name: "baseUrl",
|
|
263
|
+
message: "CDF Base URL",
|
|
264
|
+
initial: "https://api.cognitedata.com",
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: "input",
|
|
268
|
+
name: "project",
|
|
269
|
+
message: "CDF Project",
|
|
270
|
+
validate: (v) => (v ? true : "Project is required"),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
type: "input",
|
|
274
|
+
name: "org",
|
|
275
|
+
message: "Organization (for login hint)",
|
|
276
|
+
initial: "",
|
|
277
|
+
},
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
deployment = {
|
|
281
|
+
baseUrl: answers.baseUrl,
|
|
282
|
+
project: answers.project,
|
|
283
|
+
org: answers.org || null,
|
|
284
|
+
published,
|
|
285
|
+
};
|
|
286
|
+
if (answers.org) {
|
|
287
|
+
organization = answers.org;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log("\n🚀 Dune Deploy (Interactive)");
|
|
292
|
+
console.log("=============================");
|
|
293
|
+
console.log(`App: ${appConfig.name} (${appConfig.externalId})`);
|
|
294
|
+
console.log(`Version: ${appConfig.versionTag}`);
|
|
295
|
+
console.log(`Target: ${deployment.project} @ ${deployment.baseUrl}`);
|
|
296
|
+
console.log();
|
|
297
|
+
|
|
298
|
+
// Build the app first
|
|
299
|
+
if (!skipBuild) {
|
|
300
|
+
try {
|
|
301
|
+
buildApp(cwd);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error("❌ Build failed:", error.message);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Start interactive login flow
|
|
309
|
+
try {
|
|
310
|
+
const authFlow = new AuthenticationFlow(DEFAULT_CONFIG);
|
|
311
|
+
|
|
312
|
+
// Use org from CLI, then deployment, then null
|
|
313
|
+
const orgHint = organization || deployment.org || null;
|
|
314
|
+
|
|
315
|
+
console.log("\n🔐 Starting browser authentication...");
|
|
316
|
+
console.log("A browser window will open for you to log in.\n");
|
|
317
|
+
|
|
318
|
+
const tokens = await authFlow.login(orgHint);
|
|
319
|
+
|
|
320
|
+
if (!tokens || !tokens.access_token) {
|
|
321
|
+
throw new Error("No access token received from authentication");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log("\n✅ Authentication successful!");
|
|
325
|
+
console.log(`\n📤 Deploying to ${deployment.project}...`);
|
|
326
|
+
|
|
327
|
+
// Import CogniteClient and deployer classes
|
|
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();
|
|
339
|
+
|
|
340
|
+
// Package the application
|
|
341
|
+
const distPath = `${cwd}/dist`;
|
|
342
|
+
const packager = new ApplicationPackager(distPath);
|
|
343
|
+
const zipFilename = await packager.createZip("app.zip", true);
|
|
344
|
+
|
|
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
|
+
try {
|
|
358
|
+
unlinkSync(zipFilename);
|
|
359
|
+
} catch {
|
|
360
|
+
// Ignore cleanup errors
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log(`\n✅ Successfully deployed ${appConfig.name} to ${deployment.project}`);
|
|
364
|
+
|
|
365
|
+
if (deployment.published || published) {
|
|
366
|
+
console.log("🌐 App is published and available to all users");
|
|
367
|
+
} else {
|
|
368
|
+
console.log("🔒 App is deployed in draft mode");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Generate and display the app URL
|
|
372
|
+
const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag);
|
|
373
|
+
if (appUrl) {
|
|
374
|
+
console.log(`\n🔗 Open your app:\n ${appUrl}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
process.exit(0);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error("\n❌ Deployment failed:", error.message);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic Utilities for OAuth 2.0 PKCE
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for generating secure random strings and PKCE code challenges.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
|
|
9
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: Utility class pattern
|
|
10
|
+
export class CryptoUtils {
|
|
11
|
+
/**
|
|
12
|
+
* Generate a cryptographically secure random string
|
|
13
|
+
* @param {number} length - Desired length of the string
|
|
14
|
+
* @returns {string} Random string using URL-safe base64 characters
|
|
15
|
+
*/
|
|
16
|
+
static generateRandomString(length) {
|
|
17
|
+
const bytes = crypto.randomBytes(length);
|
|
18
|
+
return bytes
|
|
19
|
+
.toString("base64")
|
|
20
|
+
.replace(/\+/g, "-")
|
|
21
|
+
.replace(/\//g, "_")
|
|
22
|
+
.replace(/=/g, "")
|
|
23
|
+
.slice(0, length);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate PKCE code challenge from verifier
|
|
28
|
+
* @param {string} verifier - The code verifier string
|
|
29
|
+
* @returns {string} Base64url-encoded SHA256 hash of the verifier
|
|
30
|
+
*/
|
|
31
|
+
static generateCodeChallenge(verifier) {
|
|
32
|
+
const hash = crypto.createHash("sha256").update(verifier).digest();
|
|
33
|
+
return hash.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/deploy/index.d.ts
CHANGED
|
@@ -29,6 +29,13 @@ declare class CdfApplicationDeployer {
|
|
|
29
29
|
* @param {CogniteClient} client - Cognite SDK client
|
|
30
30
|
*/
|
|
31
31
|
constructor(client: CogniteClient);
|
|
32
|
+
private DATA_SET_EXTERNAL_ID;
|
|
33
|
+
/**
|
|
34
|
+
* Validate that the required data set exists and is accessible
|
|
35
|
+
* @returns {Promise<number>} Data set ID (numerical)
|
|
36
|
+
* @throws {Error} If data set doesn't exist or user doesn't have access
|
|
37
|
+
*/
|
|
38
|
+
validateDataSet(): Promise<number | undefined>;
|
|
32
39
|
/**
|
|
33
40
|
* Upload application package to CDF Files API
|
|
34
41
|
* @param {string} appExternalId - Application external ID
|
package/dist/deploy/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/deploy/deploy.ts
|
|
2
|
+
import fs3 from "fs";
|
|
3
|
+
|
|
1
4
|
// src/deploy/application-deployer.ts
|
|
2
5
|
import fs from "fs";
|
|
3
6
|
var CdfApplicationDeployer = class {
|
|
@@ -5,8 +8,38 @@ var CdfApplicationDeployer = class {
|
|
|
5
8
|
* @param {CogniteClient} client - Cognite SDK client
|
|
6
9
|
*/
|
|
7
10
|
constructor(client) {
|
|
11
|
+
this.DATA_SET_EXTERNAL_ID = "published-custom-apps";
|
|
8
12
|
this.client = client;
|
|
9
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate that the required data set exists and is accessible
|
|
16
|
+
* @returns {Promise<number>} Data set ID (numerical)
|
|
17
|
+
* @throws {Error} If data set doesn't exist or user doesn't have access
|
|
18
|
+
*/
|
|
19
|
+
async validateDataSet() {
|
|
20
|
+
try {
|
|
21
|
+
const dataSets = await this.client.datasets.retrieve([
|
|
22
|
+
{ externalId: this.DATA_SET_EXTERNAL_ID }
|
|
23
|
+
]);
|
|
24
|
+
return dataSets[0].id;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
try {
|
|
27
|
+
this.client.datasets.create([
|
|
28
|
+
{
|
|
29
|
+
externalId: this.DATA_SET_EXTERNAL_ID,
|
|
30
|
+
name: "Published Custom Apps",
|
|
31
|
+
description: "Published Custom Apps",
|
|
32
|
+
writeProtected: false
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
console.log(`\u2705 Data set '${this.DATA_SET_EXTERNAL_ID}' created`);
|
|
36
|
+
} catch (error2) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Failed to create data set '${this.DATA_SET_EXTERNAL_ID}'. Please ask your IT admin to create it or grant access.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
/**
|
|
11
44
|
* Upload application package to CDF Files API
|
|
12
45
|
* @param {string} appExternalId - Application external ID
|
|
@@ -17,6 +50,10 @@ var CdfApplicationDeployer = class {
|
|
|
17
50
|
* @param {boolean} published - Whether the application should be published
|
|
18
51
|
*/
|
|
19
52
|
async uploadToFilesApi(appExternalId, name, description, versionTag, zipFilename, published = false) {
|
|
53
|
+
console.log("\u{1F50D} Validating data set access...");
|
|
54
|
+
const dataSetId = await this.validateDataSet();
|
|
55
|
+
console.log(`\u2705 Data set '${this.DATA_SET_EXTERNAL_ID}' validated (ID: ${dataSetId})
|
|
56
|
+
`);
|
|
20
57
|
console.log("\u{1F4C1} Creating file record...");
|
|
21
58
|
const fileContent = fs.readFileSync(zipFilename);
|
|
22
59
|
const metadata = {
|
|
@@ -31,7 +68,8 @@ var CdfApplicationDeployer = class {
|
|
|
31
68
|
name: `${appExternalId}-${versionTag}.zip`,
|
|
32
69
|
externalId: `${appExternalId}-${versionTag}`,
|
|
33
70
|
directory: "/dune-apps",
|
|
34
|
-
metadata
|
|
71
|
+
metadata,
|
|
72
|
+
dataSetId
|
|
35
73
|
},
|
|
36
74
|
fileContent,
|
|
37
75
|
true,
|
|
@@ -209,6 +247,10 @@ var deploy = async (deployment, app, folder) => {
|
|
|
209
247
|
zipFilename,
|
|
210
248
|
deployment.published
|
|
211
249
|
);
|
|
250
|
+
try {
|
|
251
|
+
fs3.unlinkSync(zipFilename);
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
212
254
|
};
|
|
213
255
|
export {
|
|
214
256
|
ApplicationPackager,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cognite/dune",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Build and deploy React apps to Cognite Data Fusion",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cognite",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"archiver": "^7.0.1",
|
|
60
60
|
"enquirer": "^2.4.1",
|
|
61
61
|
"execa": "^5.1.1",
|
|
62
|
-
"hygen": "^6.2.11"
|
|
62
|
+
"hygen": "^6.2.11",
|
|
63
|
+
"open": "^10.1.0"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"@cognite/sdk": ">=9.0.0",
|
|
@@ -17,6 +17,38 @@ export class CdfApplicationDeployer {
|
|
|
17
17
|
this.client = client;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
private DATA_SET_EXTERNAL_ID = "published-custom-apps";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate that the required data set exists and is accessible
|
|
24
|
+
* @returns {Promise<number>} Data set ID (numerical)
|
|
25
|
+
* @throws {Error} If data set doesn't exist or user doesn't have access
|
|
26
|
+
*/
|
|
27
|
+
async validateDataSet() {
|
|
28
|
+
try {
|
|
29
|
+
const dataSets = await this.client.datasets.retrieve([
|
|
30
|
+
{ externalId: this.DATA_SET_EXTERNAL_ID },
|
|
31
|
+
]);
|
|
32
|
+
return dataSets[0].id;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
try {
|
|
35
|
+
this.client.datasets.create([
|
|
36
|
+
{
|
|
37
|
+
externalId: this.DATA_SET_EXTERNAL_ID,
|
|
38
|
+
name: "Published Custom Apps",
|
|
39
|
+
description: "Published Custom Apps",
|
|
40
|
+
writeProtected: false,
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
console.log(`✅ Data set '${this.DATA_SET_EXTERNAL_ID}' created`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Failed to create data set '${this.DATA_SET_EXTERNAL_ID}'. Please ask your IT admin to create it or grant access.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
/**
|
|
21
53
|
* Upload application package to CDF Files API
|
|
22
54
|
* @param {string} appExternalId - Application external ID
|
|
@@ -34,6 +66,11 @@ export class CdfApplicationDeployer {
|
|
|
34
66
|
zipFilename: string,
|
|
35
67
|
published = false
|
|
36
68
|
): Promise<void> {
|
|
69
|
+
// Validate data set exists and get its ID
|
|
70
|
+
console.log("🔍 Validating data set access...");
|
|
71
|
+
const dataSetId = await this.validateDataSet();
|
|
72
|
+
console.log(`✅ Data set '${this.DATA_SET_EXTERNAL_ID}' validated (ID: ${dataSetId})\n`);
|
|
73
|
+
|
|
37
74
|
console.log("📁 Creating file record...");
|
|
38
75
|
|
|
39
76
|
const fileContent = fs.readFileSync(zipFilename);
|
|
@@ -51,6 +88,7 @@ export class CdfApplicationDeployer {
|
|
|
51
88
|
externalId: `${appExternalId}-${versionTag}`,
|
|
52
89
|
directory: "/dune-apps",
|
|
53
90
|
metadata: metadata,
|
|
91
|
+
dataSetId: dataSetId,
|
|
54
92
|
},
|
|
55
93
|
fileContent,
|
|
56
94
|
true, // overwrite
|
package/src/deploy/deploy.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import { CdfApplicationDeployer } from "./application-deployer";
|
|
2
3
|
import { ApplicationPackager } from "./application-packager";
|
|
3
4
|
import { getSdk } from "./get-sdk";
|
|
@@ -22,4 +23,11 @@ export const deploy = async (deployment: Deployment, app: App, folder: string) =
|
|
|
22
23
|
zipFilename,
|
|
23
24
|
deployment.published
|
|
24
25
|
);
|
|
26
|
+
|
|
27
|
+
// Step 4: Clean up zip file
|
|
28
|
+
try {
|
|
29
|
+
fs.unlinkSync(zipFilename);
|
|
30
|
+
} catch {
|
|
31
|
+
// Ignore cleanup errors
|
|
32
|
+
}
|
|
25
33
|
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: <%= name %>/biome.json
|
|
3
|
-
---
|
|
4
|
-
{
|
|
5
|
-
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
6
|
-
"vcs": {
|
|
7
|
-
"enabled": true,
|
|
8
|
-
"clientKind": "git",
|
|
9
|
-
"useIgnoreFile": true
|
|
10
|
-
},
|
|
11
|
-
"organizeImports": {
|
|
12
|
-
"enabled": true
|
|
13
|
-
},
|
|
14
|
-
"linter": {
|
|
15
|
-
"enabled": true,
|
|
16
|
-
"rules": {
|
|
17
|
-
"recommended": true
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"formatter": {
|
|
21
|
-
"enabled": true,
|
|
22
|
-
"indentStyle": "tab"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: <%= name %>/vite.config.ts
|
|
3
|
-
---
|
|
4
|
-
import react from "@vitejs/plugin-react";
|
|
5
|
-
import { defineConfig } from "vite";
|
|
6
|
-
import mkcert from "vite-plugin-mkcert";
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
plugins: [react(), mkcert()],
|
|
10
|
-
server: {
|
|
11
|
-
https: true,
|
|
12
|
-
port: 3000,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|