create-graphene 0.0.16

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 (4) hide show
  1. package/README.md +7 -0
  2. package/bin.js +18 -0
  3. package/dist/cli.js +526 -0
  4. package/package.json +36 -0
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # `create-graphene`
2
+
3
+ Scaffolds a new Graphene project.
4
+
5
+ ```bash
6
+ npm create graphene
7
+ ```
package/bin.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {access} from 'node:fs/promises'
4
+ import {dirname, join} from 'node:path'
5
+ import {fileURLToPath} from 'node:url'
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+ const devCli = join(__dirname, 'cli.ts')
9
+
10
+ let hasDevCli = await access(devCli).then(
11
+ () => true,
12
+ () => false,
13
+ )
14
+ if (hasDevCli) {
15
+ await import('./cli.ts')
16
+ } else {
17
+ await import('./dist/cli.js')
18
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,526 @@
1
+ // cli.ts
2
+ import process from "node:process";
3
+
4
+ // create.ts
5
+ import * as clack from "@clack/prompts";
6
+ import { spawn } from "node:child_process";
7
+ import { access, mkdir, readFile, readlink, readdir, symlink, writeFile } from "node:fs/promises";
8
+ import path2 from "node:path";
9
+
10
+ // ../cli/package.json
11
+ var package_default = {
12
+ name: "@graphenedata/cli",
13
+ version: "0.0.16",
14
+ private: true,
15
+ license: "Elastic-2.0",
16
+ author: "Graphene Systems Inc",
17
+ bin: {
18
+ graphene: "./bin.js"
19
+ },
20
+ files: [
21
+ "dist",
22
+ "bin.js",
23
+ "package.json",
24
+ "LICENSE.md",
25
+ "THIRD_PARTY_NOTICES.md"
26
+ ],
27
+ type: "module",
28
+ main: "bin.js",
29
+ types: "dist/index.d.ts",
30
+ exports: {
31
+ "./ui": {
32
+ browser: "./dist/ui/web.js"
33
+ }
34
+ },
35
+ scripts: {
36
+ build: "rm -rf dist && rm -f *.tgz && node ./esbuild.mjs",
37
+ test: "vitest run cli --root ..",
38
+ "test-one": "node ../scripts/turboTest.js",
39
+ prepack: "pnpm run build"
40
+ },
41
+ dependencies: {
42
+ "@graphenedata/lang": "workspace:*",
43
+ "@graphenedata/ui": "workspace:*",
44
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
45
+ chalk: "^5.3.0",
46
+ "ci-info": "^4.4.0",
47
+ "cli-table3": "^0.6.3",
48
+ commander: "^11.0.0",
49
+ dotenv: "^17.2.3",
50
+ entities: "^4.5.0",
51
+ "fs-extra": "11.2.0",
52
+ "js-yaml": "^3.14.2",
53
+ json5: "^2.2.3",
54
+ mdsvex: "^0.12.6",
55
+ "sanitize-html": "^2.17.0",
56
+ unified: "^11.0.5",
57
+ "unist-util-visit": "4.1.2",
58
+ vite: "7.3.2",
59
+ ws: "^8.18.0"
60
+ },
61
+ devDependencies: {
62
+ "@clickhouse/client": "^1.18.2",
63
+ "@duckdb/node-api": "1.3.2-alpha.26",
64
+ "@google-cloud/bigquery": "^8.2.0",
65
+ "@types/fs-extra": "^11.0.4",
66
+ "@types/node": "^20.0.0",
67
+ "@types/sanitize-html": "^2.16.0",
68
+ "@types/ws": "^8.18.1",
69
+ esbuild: "^0.27.2",
70
+ "snowflake-sdk": "2.4.0",
71
+ vitest: "4.0.18",
72
+ "vscode-languageserver-types": "^3.17.0"
73
+ },
74
+ peerDependencies: {
75
+ "@clickhouse/client": "^1.18.2",
76
+ "@duckdb/node-api": "1.3.2-alpha.26",
77
+ "@google-cloud/bigquery": "^8.2.0",
78
+ "snowflake-sdk": "2.4.0"
79
+ },
80
+ peerDependenciesMeta: {
81
+ "@clickhouse/client": {
82
+ optional: true
83
+ },
84
+ "@duckdb/node-api": {
85
+ optional: true
86
+ },
87
+ "@google-cloud/bigquery": {
88
+ optional: true
89
+ },
90
+ "snowflake-sdk": {
91
+ optional: true
92
+ }
93
+ },
94
+ engines: {
95
+ node: ">=20"
96
+ }
97
+ };
98
+
99
+ // create.ts
100
+ var packageJson = await readCreatePackageJson();
101
+ var cliManifest = package_default;
102
+ async function readCreatePackageJson() {
103
+ try {
104
+ return JSON.parse(await readFile(new URL("./package.json", import.meta.url), "utf8"));
105
+ } catch (err) {
106
+ if (err.code !== "ENOENT") throw err;
107
+ return JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
108
+ }
109
+ }
110
+ var CreateCancelled = class extends Error {
111
+ constructor() {
112
+ super("Create canceled");
113
+ }
114
+ };
115
+ function parseArgs(argv) {
116
+ let options = { yes: false, install: true, name: void 0, targetDir: void 0, help: false };
117
+ for (let i = 0; i < argv.length; i++) {
118
+ let arg = argv[i];
119
+ if (arg === "--yes" || arg === "-y") {
120
+ options.yes = true;
121
+ continue;
122
+ }
123
+ if (arg === "--no-install") {
124
+ options.install = false;
125
+ continue;
126
+ }
127
+ if (arg === "--help" || arg === "-h") {
128
+ options.help = true;
129
+ continue;
130
+ }
131
+ if (arg === "--name") {
132
+ if (!argv[i + 1]) throw new Error("--name requires a value");
133
+ options.name = argv[++i];
134
+ continue;
135
+ }
136
+ if (arg.startsWith("--name=")) {
137
+ options.name = arg.slice("--name=".length);
138
+ continue;
139
+ }
140
+ if (!options.targetDir && !arg.startsWith("-")) {
141
+ options.targetDir = arg;
142
+ continue;
143
+ }
144
+ throw new Error(`Unknown argument: ${arg}`);
145
+ }
146
+ return options;
147
+ }
148
+ function defaultProjectName(targetDir) {
149
+ let raw = path2.basename(targetDir || "graphene-app");
150
+ let normalized = raw.toLowerCase().trim().replaceAll(/[^a-z0-9._-]+/g, "-").replaceAll(/^-+|-+$/g, "");
151
+ return normalized || "graphene-app";
152
+ }
153
+ function detectPackageManager(env = {}) {
154
+ let userAgent = env.npm_config_user_agent || env.NPM_CONFIG_USER_AGENT || "";
155
+ let userAgentMatch = userAgent.match(/(?:^|\s)(npm|pnpm|yarn|bun)\/([^\s]+)/);
156
+ if (userAgentMatch) return { name: userAgentMatch[1], version: userAgentMatch[2] };
157
+ let execPath = path2.basename(env.npm_execpath || env.NPM_EXEC_PATH || "").toLowerCase();
158
+ if (execPath.includes("pnpm")) return { name: "pnpm" };
159
+ if (execPath.includes("yarn")) return { name: "yarn" };
160
+ if (execPath.includes("bun")) return { name: "bun" };
161
+ return { name: "npm" };
162
+ }
163
+ function renderTemplate({ answers, cliVersion }) {
164
+ let graphene = { dialect: answers.database };
165
+ if (answers.defaultNamespace) graphene.defaultNamespace = answers.defaultNamespace;
166
+ if (answers.database === "duckdb") {
167
+ if (answers.duckdbPath) graphene.duckdb = { path: answers.duckdbPath };
168
+ } else if (answers.database === "snowflake") {
169
+ graphene.snowflake = { account: answers.snowflakeAccount || "", username: answers.snowflakeUsername || "" };
170
+ } else {
171
+ graphene.bigquery = { projectId: answers.bigqueryProjectId || "" };
172
+ }
173
+ let pkg = {
174
+ name: answers.projectName,
175
+ version: "0.0.1",
176
+ packageManager: answers.packageManager?.version ? `${answers.packageManager.name}@${answers.packageManager.version}` : void 0,
177
+ scripts: {
178
+ graphene: "graphene",
179
+ serve: "graphene serve",
180
+ compile: "graphene compile",
181
+ run: "graphene run"
182
+ },
183
+ dependencies: {
184
+ "@graphenedata/cli": cliVersion
185
+ },
186
+ graphene
187
+ };
188
+ if (!pkg.packageManager) delete pkg.packageManager;
189
+ let warehouseClient = getWarehouseClient(answers.database);
190
+ pkg.dependencies[warehouseClient] = getWarehouseClientVersion(warehouseClient);
191
+ let files = {
192
+ "package.json": JSON.stringify(pkg, null, 2) + "\n",
193
+ ".gitignore": ["node_modules", ".env", "*.duckdb"].join("\n") + "\n",
194
+ "AGENTS.md": renderAgents(answers),
195
+ "index.md": renderIndex(answers)
196
+ };
197
+ if (answers.packageManager?.name === "yarn") files[".yarnrc.yml"] = "nodeLinker: node-modules\n";
198
+ let envFile = renderEnvFile(answers);
199
+ if (envFile) files[".env"] = envFile;
200
+ return files;
201
+ }
202
+ function grapheneCommand(packageManager, args) {
203
+ if (packageManager?.name === "pnpm") return `pnpm graphene ${args}`;
204
+ if (packageManager?.name === "yarn") return `yarn graphene ${args}`;
205
+ if (packageManager?.name === "bun") return `bun run graphene ${args}`;
206
+ return `npx graphene ${args}`;
207
+ }
208
+ function renderAgents(answers) {
209
+ return [
210
+ "## Graphene Reference",
211
+ "",
212
+ `Assume all ${databaseLabel(answers.database)} functions are available when writing GSQL.`,
213
+ "",
214
+ "Common commands:",
215
+ "",
216
+ `- ${grapheneCommand(answers.packageManager, "check")} - check the project for syntax and analysis errors`,
217
+ `- ${grapheneCommand(answers.packageManager, "run index.md")} - execute the index page`,
218
+ `- ${grapheneCommand(answers.packageManager, "serve --bg")} - start or restart the local dev server`
219
+ ].join("\n") + "\n";
220
+ }
221
+ function databaseLabel(database) {
222
+ if (database === "snowflake") return "Snowflake";
223
+ if (database === "bigquery") return "BigQuery";
224
+ return "DuckDB";
225
+ }
226
+ function renderIndex(answers) {
227
+ return [`# ${answers.projectName}`, "", `This Graphene project is configured for ${databaseLabel(answers.database)}.`, "", "Start by adding models and queries to this project."].join("\n") + "\n";
228
+ }
229
+ function renderEnvFile(answers) {
230
+ let lines = [];
231
+ if (answers.database === "snowflake") {
232
+ lines.push(`SNOWFLAKE_PRI_KEY_PATH=${answers.snowflakeKeyPath || ""}`);
233
+ if (answers.snowflakePassphrase) lines.push(`SNOWFLAKE_PRI_PASSPHRASE=${answers.snowflakePassphrase}`);
234
+ } else if (answers.database === "bigquery") {
235
+ lines.push(`GOOGLE_APPLICATION_CREDENTIALS=${answers.bigqueryKeyPath || ""}`);
236
+ }
237
+ return lines.length ? lines.join("\n") + "\n" : null;
238
+ }
239
+ async function ensureEmptyDir(targetDir) {
240
+ let entries = await readdir(targetDir).catch((err) => {
241
+ if (err.code === "ENOENT") return null;
242
+ throw err;
243
+ });
244
+ if (entries === null) return;
245
+ if (entries.length > 0) throw new Error(`Target directory is not empty: ${targetDir}`);
246
+ }
247
+ async function writeTemplate(targetDir, files) {
248
+ await mkdir(targetDir, { recursive: true });
249
+ for (let [relativePath, contents] of Object.entries(files)) {
250
+ let filePath = path2.join(targetDir, relativePath);
251
+ await mkdir(path2.dirname(filePath), { recursive: true });
252
+ await writeFile(filePath, contents);
253
+ }
254
+ }
255
+ function printHelp(stdout) {
256
+ stdout.write(
257
+ [
258
+ "Usage: npm create graphene [target-dir] [-- --yes] [--name <project-name>] [--no-install]",
259
+ "",
260
+ "Options:",
261
+ " -y, --yes Skip prompts and accept defaults",
262
+ " --name <name> Set the generated package name",
263
+ " --no-install Skip dependency installation"
264
+ ].join("\n") + "\n"
265
+ );
266
+ }
267
+ function promptOptions(input, output) {
268
+ return { input, output };
269
+ }
270
+ function unwrapPrompt(value) {
271
+ if (clack.isCancel(value)) throw new CreateCancelled();
272
+ return value;
273
+ }
274
+ function validateRequired(message) {
275
+ return (value) => {
276
+ if (typeof value !== "string" || !value.trim()) return message;
277
+ };
278
+ }
279
+ function validateFileExtension(filePath, expectedExtension) {
280
+ if (typeof filePath !== "string" || !filePath.trim()) return `Path to ${expectedExtension} file is required`;
281
+ if (!filePath.endsWith(expectedExtension)) return `File must end with ${expectedExtension}`;
282
+ }
283
+ async function promptExistingFilePath({ message, expectedExtension, input, output }) {
284
+ while (true) {
285
+ let filePath = unwrapPrompt(
286
+ await clack.path({
287
+ message,
288
+ validate: (value) => validateFileExtension(value, expectedExtension),
289
+ ...promptOptions(input, output)
290
+ })
291
+ );
292
+ try {
293
+ await access(filePath);
294
+ return filePath;
295
+ } catch {
296
+ clack.log.error(`File not found: ${filePath}`, { output });
297
+ }
298
+ }
299
+ }
300
+ function namespacePlaceholder(database) {
301
+ if (database === "snowflake") return "MY_DB.ANALYTICS";
302
+ if (database === "bigquery") return "my-project.analytics";
303
+ return "analytics";
304
+ }
305
+ function getWarehouseClient(database) {
306
+ if (database === "snowflake") return "snowflake-sdk";
307
+ if (database === "bigquery") return "@google-cloud/bigquery";
308
+ return "@duckdb/node-api";
309
+ }
310
+ function getWarehouseClientVersion(packageName) {
311
+ let version = cliManifest.peerDependencies?.[packageName];
312
+ if (!version) throw new Error(`Missing ${packageName} peerDependency in cli/package.json`);
313
+ return version;
314
+ }
315
+ async function collectAnswers({ options, packageManager, input, output }) {
316
+ if (options.yes) {
317
+ let targetDir2 = options.targetDir || "graphene-app";
318
+ return {
319
+ targetDir: targetDir2,
320
+ projectName: options.name || defaultProjectName(targetDir2),
321
+ packageManager,
322
+ database: "duckdb",
323
+ skillLinkTarget: "none"
324
+ };
325
+ }
326
+ let targetDir = options.targetDir;
327
+ if (!targetDir) {
328
+ targetDir = unwrapPrompt(
329
+ await clack.text({
330
+ message: "Project name",
331
+ placeholder: "my-analytics",
332
+ initialValue: "graphene-app",
333
+ validate: validateRequired("Project name is required"),
334
+ ...promptOptions(input, output)
335
+ })
336
+ );
337
+ }
338
+ let projectName = options.name || defaultProjectName(targetDir);
339
+ let database = unwrapPrompt(
340
+ await clack.select({
341
+ message: "Database",
342
+ options: [
343
+ { value: "duckdb", label: "DuckDB (local file)" },
344
+ { value: "snowflake", label: "Snowflake" },
345
+ { value: "bigquery", label: "BigQuery" }
346
+ ],
347
+ ...promptOptions(input, output)
348
+ })
349
+ );
350
+ let defaultNamespace = unwrapPrompt(
351
+ await clack.text({
352
+ message: "Default namespace (optional)",
353
+ placeholder: namespacePlaceholder(database),
354
+ ...promptOptions(input, output)
355
+ })
356
+ ).trim();
357
+ let answers = {
358
+ targetDir,
359
+ projectName,
360
+ packageManager,
361
+ database,
362
+ defaultNamespace: defaultNamespace || void 0,
363
+ skillLinkTarget: "none"
364
+ };
365
+ if (database === "duckdb") {
366
+ answers.duckdbPath = unwrapPrompt(
367
+ await clack.text({
368
+ message: "Path to .duckdb file",
369
+ placeholder: "./data.duckdb",
370
+ validate(value) {
371
+ if (typeof value !== "string" || !value.trim()) return;
372
+ if (!value.endsWith(".duckdb")) return "DuckDB path must end with .duckdb";
373
+ },
374
+ ...promptOptions(input, output)
375
+ })
376
+ ).trim() || void 0;
377
+ } else if (database === "snowflake") {
378
+ answers.snowflakeAccount = unwrapPrompt(
379
+ await clack.text({
380
+ message: "Snowflake account ID",
381
+ placeholder: "myorg-myaccount",
382
+ validate: validateRequired("Snowflake account ID is required"),
383
+ ...promptOptions(input, output)
384
+ })
385
+ );
386
+ answers.snowflakeUsername = unwrapPrompt(
387
+ await clack.text({
388
+ message: "Snowflake username",
389
+ placeholder: "graphene_user",
390
+ validate: validateRequired("Snowflake username is required"),
391
+ ...promptOptions(input, output)
392
+ })
393
+ );
394
+ answers.snowflakeKeyPath = await promptExistingFilePath({ message: "Path to .p8 key file", expectedExtension: ".p8", input, output });
395
+ answers.snowflakePassphrase = unwrapPrompt(
396
+ await clack.password({
397
+ message: "Key passphrase",
398
+ ...promptOptions(input, output)
399
+ })
400
+ );
401
+ } else {
402
+ answers.bigqueryProjectId = unwrapPrompt(
403
+ await clack.text({
404
+ message: "GCP project ID",
405
+ placeholder: "my-project-123",
406
+ validate: validateRequired("GCP project ID is required"),
407
+ ...promptOptions(input, output)
408
+ })
409
+ );
410
+ answers.bigqueryKeyPath = await promptExistingFilePath({ message: "Path to service account .json key file", expectedExtension: ".json", input, output });
411
+ }
412
+ if (options.install) {
413
+ answers.skillLinkTarget = unwrapPrompt(
414
+ await clack.select({
415
+ message: "Add the Graphene skill for agents?",
416
+ options: [
417
+ { value: ".agents", label: ".agents/skills/graphene" },
418
+ { value: ".claude", label: ".claude/skills/graphene" },
419
+ { value: "none", label: "Skip" }
420
+ ],
421
+ ...promptOptions(input, output)
422
+ })
423
+ );
424
+ }
425
+ return answers;
426
+ }
427
+ function installCommand(packageManager) {
428
+ if (packageManager.name === "npm") return ["npm", ["install", "--no-fund"]];
429
+ return [packageManager.name, ["install"]];
430
+ }
431
+ async function installDeps(targetDir, packageManager, env) {
432
+ let [command, args] = installCommand(packageManager);
433
+ let task = clack.taskLog({ title: `Installing dependencies with ${packageManager.name}...`, retainLog: true });
434
+ let child = spawn(command, args, {
435
+ cwd: targetDir,
436
+ stdio: ["ignore", "pipe", "pipe"],
437
+ env: { ...env, FORCE_COLOR: "1", npm_config_color: "always" }
438
+ });
439
+ if (!child.stdout || !child.stderr) throw new Error(`${command} install pipes were not created`);
440
+ let stderr = "";
441
+ child.stdout.setEncoding("utf8");
442
+ child.stderr.setEncoding("utf8");
443
+ let buffers = { stdout: "", stderr: "" };
444
+ let forward = (chunk, stream) => {
445
+ let next = buffers[stream] + chunk;
446
+ let lines = next.split(/\r?\n/);
447
+ buffers[stream] = lines.pop() || "";
448
+ for (let line of lines) {
449
+ if (!line.trim()) continue;
450
+ task.message(line);
451
+ }
452
+ };
453
+ let flush = (stream) => {
454
+ let line = buffers[stream];
455
+ if (!line.trim()) return;
456
+ task.message(line);
457
+ };
458
+ child.stdout.on("data", (chunk) => forward(String(chunk), "stdout"));
459
+ child.stderr.on("data", (chunk) => {
460
+ let text2 = String(chunk);
461
+ stderr += text2;
462
+ forward(text2, "stderr");
463
+ });
464
+ let code = await new Promise((resolve, reject) => {
465
+ child.on("error", reject);
466
+ child.on("close", (exitCode) => resolve(exitCode ?? 1));
467
+ });
468
+ flush("stdout");
469
+ flush("stderr");
470
+ if (code === 0) task.success("Dependencies installed", { showLog: true });
471
+ else task.error(`${command} install failed`, { showLog: true });
472
+ return { code, stderr };
473
+ }
474
+ async function symlinkGrapheneSkill(targetDir, skillLinkTarget) {
475
+ if (skillLinkTarget === "none") return;
476
+ let sourcePath = path2.join(targetDir, "node_modules/@graphenedata/cli/dist/skills/graphene");
477
+ let linkPath = path2.join(targetDir, skillLinkTarget, "skills/graphene");
478
+ let relativeSourcePath = path2.relative(path2.dirname(linkPath), sourcePath);
479
+ await mkdir(path2.dirname(linkPath), { recursive: true });
480
+ try {
481
+ await symlink(relativeSourcePath, linkPath, "dir");
482
+ } catch (err) {
483
+ if (err.code !== "EEXIST") throw err;
484
+ let existingTarget = await readlink(linkPath).catch(() => null);
485
+ if (existingTarget === relativeSourcePath) return;
486
+ throw new Error(`Cannot link Graphene skill because ${linkPath} already exists`, { cause: err });
487
+ }
488
+ }
489
+ async function runCreate({ argv, cwd, env = {}, stdin, stdout }) {
490
+ let options = parseArgs(argv);
491
+ if (options.help) {
492
+ printHelp(stdout);
493
+ return;
494
+ }
495
+ clack.intro("Create a new Graphene project", { output: stdout });
496
+ try {
497
+ let packageManager = detectPackageManager(env);
498
+ let answers = await collectAnswers({ options, packageManager, input: stdin, output: stdout });
499
+ let targetDir = path2.resolve(cwd, answers.targetDir);
500
+ await ensureEmptyDir(targetDir);
501
+ let cliVersion = packageJson.version || "0.0.15";
502
+ await writeTemplate(targetDir, renderTemplate({ answers, cliVersion }));
503
+ if (options.install) {
504
+ let install = await installDeps(targetDir, packageManager, env);
505
+ if (install.code !== 0) throw new Error(install.stderr.trim() || `${packageManager.name} install failed with code ${install.code}`);
506
+ await symlinkGrapheneSkill(targetDir, answers.skillLinkTarget);
507
+ }
508
+ clack.outro("Done!", { output: stdout });
509
+ } catch (err) {
510
+ if (err instanceof CreateCancelled) {
511
+ clack.cancel("Operation cancelled.", { output: stdout });
512
+ return;
513
+ }
514
+ throw err;
515
+ }
516
+ }
517
+
518
+ // cli.ts
519
+ await runCreate({
520
+ argv: process.argv.slice(2),
521
+ cwd: process.cwd(),
522
+ env: process.env,
523
+ stdin: process.stdin,
524
+ stdout: process.stdout,
525
+ stderr: process.stderr
526
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "create-graphene",
3
+ "version": "0.0.16",
4
+ "license": "Elastic-2.0",
5
+ "author": "Graphene Systems Inc",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/graphene-data/graphene.git",
9
+ "directory": "create"
10
+ },
11
+ "bin": {
12
+ "create-graphene": "./bin.js"
13
+ },
14
+ "files": [
15
+ "bin.js",
16
+ "dist",
17
+ "README.md",
18
+ "package.json"
19
+ ],
20
+ "type": "module",
21
+ "scripts": {
22
+ "build": "rm -rf dist && node ./esbuild.mjs",
23
+ "test": "vitest run create --root ..",
24
+ "test-one": "node ../scripts/turboTest.js",
25
+ "prepack": "pnpm run build"
26
+ },
27
+ "dependencies": {
28
+ "@clack/prompts": "^1.2.0"
29
+ },
30
+ "devDependencies": {
31
+ "esbuild": "^0.27.2"
32
+ },
33
+ "engines": {
34
+ "node": ">=24.0.0"
35
+ }
36
+ }