@dwizi/create-dzx 0.1.9 → 0.1.11

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 (2) hide show
  1. package/bin/index.js +51 -162
  2. package/package.json +9 -2
package/bin/index.js CHANGED
@@ -1,28 +1,13 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  import * as clack from "@clack/prompts";
7
-
8
6
  const TEMPLATES = ["basic", "tools-only", "full"];
9
7
  const RUNTIMES = ["node", "deno"];
10
-
11
- /**
12
- * Normalize a string into a filesystem-safe slug.
13
- */
14
8
  function slugify(value) {
15
- return value
16
- .toLowerCase()
17
- .replace(/[^a-z0-9-_]/g, "-")
18
- .replace(/--+/g, "-")
19
- .replace(/^-+/, "")
20
- .replace(/-+$/, "");
9
+ return value.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/--+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
21
10
  }
22
-
23
- /**
24
- * Parse CLI argv into a simple key/value map.
25
- */
26
11
  function parseArgs(argv) {
27
12
  const args = { positional: [] };
28
13
  for (let i = 0; i < argv.length; i += 1) {
@@ -42,23 +27,13 @@ function parseArgs(argv) {
42
27
  }
43
28
  return args;
44
29
  }
45
-
46
- /**
47
- * Return true when the directory is missing or empty.
48
- */
49
30
  function isEmptyDir(dir) {
50
31
  if (!fs.existsSync(dir)) return true;
51
32
  return fs.readdirSync(dir).length === 0;
52
33
  }
53
-
54
- /**
55
- * Recursively list files relative to the base directory.
56
- */
57
34
  function listFiles(dir, baseDir = dir) {
58
35
  if (!fs.existsSync(dir)) return [];
59
- const entries = fs
60
- .readdirSync(dir, { withFileTypes: true })
61
- .sort((a, b) => a.name.localeCompare(b.name));
36
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
62
37
  const files = [];
63
38
  for (const entry of entries) {
64
39
  const entryPath = path.join(dir, entry.name);
@@ -70,10 +45,6 @@ function listFiles(dir, baseDir = dir) {
70
45
  }
71
46
  return files;
72
47
  }
73
-
74
- /**
75
- * Copy a directory recursively.
76
- */
77
48
  function copyDir(src, dest) {
78
49
  fs.mkdirSync(dest, { recursive: true });
79
50
  const entries = fs.readdirSync(src, { withFileTypes: true });
@@ -87,25 +58,18 @@ function copyDir(src, dest) {
87
58
  }
88
59
  }
89
60
  }
90
-
91
- /**
92
- * Detect package manager from lockfiles or environment.
93
- */
94
61
  function detectPackageManager(cwd) {
95
62
  const lockfiles = {
96
63
  "pnpm-lock.yaml": "pnpm",
97
64
  "package-lock.json": "npm",
98
65
  "yarn.lock": "yarn",
99
- "bun.lockb": "bun",
66
+ "bun.lockb": "bun"
100
67
  };
101
-
102
68
  for (const [lockfile, pm] of Object.entries(lockfiles)) {
103
69
  if (fs.existsSync(path.join(cwd, lockfile))) {
104
70
  return pm;
105
71
  }
106
72
  }
107
-
108
- // Check parent directories (monorepo context)
109
73
  let current = cwd;
110
74
  for (let i = 0; i < 5; i++) {
111
75
  const parent = path.dirname(current);
@@ -117,21 +81,16 @@ function detectPackageManager(cwd) {
117
81
  }
118
82
  current = parent;
119
83
  }
120
-
121
- // Default to pnpm
122
84
  return "pnpm";
123
85
  }
124
-
125
- /**
126
- * Get install command for a package manager.
127
- */
128
86
  function getInstallCommand(pm) {
129
87
  return `${pm} install`;
130
88
  }
131
-
132
- /**
133
- * Run a shell command in a given working directory.
134
- */
89
+ function getRunCommand(pm, script) {
90
+ if (pm === "pnpm" || pm === "yarn") return `${pm} ${script}`;
91
+ if (pm === "bun") return `${pm} run ${script}`;
92
+ return `${pm} run ${script}`;
93
+ }
135
94
  async function runCommand(command, cwd) {
136
95
  const { spawn } = await import("node:child_process");
137
96
  return new Promise((resolve, reject) => {
@@ -143,141 +102,101 @@ async function runCommand(command, cwd) {
143
102
  });
144
103
  });
145
104
  }
146
-
147
- /**
148
- * Resolve the templates directory for create-dzx.
149
- */
150
105
  function resolveTemplatesRoot() {
151
106
  const here = path.dirname(fileURLToPath(import.meta.url));
152
107
  const localTemplatesRoot = path.resolve(here, "..", "templates");
153
108
  if (fs.existsSync(localTemplatesRoot)) return localTemplatesRoot;
154
- // Fallback for when installed as npm package
155
109
  return path.resolve(process.cwd(), "node_modules", "create-dzx", "templates");
156
110
  }
157
-
158
- /**
159
- * Color utilities for terminal output.
160
- */
161
111
  const useColor = Boolean(process.stdout.isTTY);
162
112
  function color(code) {
163
- return (text) => (useColor ? `\x1b[${code}m${text}\x1b[0m` : text);
113
+ return (text) => useColor ? `\x1B[${code}m${text}\x1B[0m` : text;
164
114
  }
165
-
166
115
  const colorize = {
167
116
  green: color("32"),
168
117
  cyan: color("36"),
169
118
  blue: color("34"),
170
119
  gray: color("90"),
171
120
  bold: color("1"),
172
- dim: color("2"),
121
+ dim: color("2")
173
122
  };
174
-
175
123
  const symbols = {
176
- check: useColor ? "" : "OK",
177
- step: useColor ? "" : "*",
178
- brand: useColor ? "" : ">",
124
+ check: useColor ? "\u2714" : "OK",
125
+ step: useColor ? "\u25CF" : "*",
126
+ brand: useColor ? "\u25B2" : ">"
179
127
  };
180
-
181
- /**
182
- * Create a terminal spinner controller.
183
- */
184
128
  function createSpinner(enabled) {
185
- const frames = ["", "", "", ""];
129
+ const frames = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
186
130
  let timer = null;
187
131
  let message = "";
188
132
  let frameIndex = 0;
189
-
190
133
  const clearLine = () => {
191
134
  if (!enabled) return;
192
- process.stdout.write("\r\x1b[2K");
135
+ process.stdout.write("\r\x1B[2K");
193
136
  };
194
-
195
137
  const render = () => {
196
138
  if (!enabled) return;
197
139
  const frame = frames[frameIndex % frames.length];
198
140
  frameIndex += 1;
199
141
  process.stdout.write(`\r${colorize.gray(frame)} ${message}`);
200
142
  };
201
-
202
143
  const start = (nextMessage) => {
203
144
  message = nextMessage;
204
145
  if (!enabled || timer) return;
205
146
  render();
206
147
  timer = setInterval(render, 80);
207
148
  };
208
-
209
149
  const update = (nextMessage) => {
210
150
  message = nextMessage;
211
151
  if (!enabled || !timer) return;
212
152
  render();
213
153
  };
214
-
215
154
  const pause = () => {
216
155
  if (!enabled || !timer) return;
217
156
  clearInterval(timer);
218
157
  timer = null;
219
158
  clearLine();
220
159
  };
221
-
222
160
  const resume = () => {
223
161
  if (!enabled || timer || !message) return;
224
162
  render();
225
163
  timer = setInterval(render, 80);
226
164
  };
227
-
228
165
  const stop = () => {
229
166
  pause();
230
167
  message = "";
231
168
  };
232
-
233
169
  return { start, update, pause, resume, stop, isEnabled: enabled };
234
170
  }
235
-
236
- /**
237
- * Print a aligned key/value summary list.
238
- */
239
171
  function printKeyValueList(items) {
240
172
  if (items.length === 0) return;
241
173
  const maxLabel = items.reduce((max, item) => Math.max(max, item.label.length), 0);
242
174
  for (const item of items) {
243
175
  const padded = item.label.padEnd(maxLabel);
244
- // eslint-disable-next-line no-console
245
176
  console.log(
246
- `${colorize.gray(symbols.step)} ${colorize.gray(padded)} : ${colorize.cyan(item.value)}`,
177
+ `${colorize.gray(symbols.step)} ${colorize.gray(padded)} : ${colorize.cyan(item.value)}`
247
178
  );
248
179
  }
249
180
  }
250
-
251
- /**
252
- * Get the latest version of @dwizi/dzx from npm registry.
253
- */
254
181
  async function getDzxVersion() {
255
182
  try {
256
- const { promisify } = await import("node:util");
257
- const { exec } = promisify(await import("node:child_process"));
258
- const { stdout } = await exec("npm view @dwizi/dzx version", { encoding: "utf8" });
259
- return stdout.trim();
183
+ const { createRequire } = await import("node:module");
184
+ const require2 = createRequire(import.meta.url);
185
+ const dzxPkgJsonPath = require2.resolve("@dwizi/dzx/package.json", { paths: [process.cwd()] });
186
+ const dzxPkg = JSON.parse(fs.readFileSync(dzxPkgJsonPath, "utf8"));
187
+ if (dzxPkg && typeof dzxPkg.version === "string" && dzxPkg.version.trim()) {
188
+ return dzxPkg.version.trim();
189
+ }
260
190
  } catch {
261
- // Fallback version if npm query fails
262
- return "latest";
263
191
  }
192
+ return "*";
264
193
  }
265
-
266
- /**
267
- * Main scaffolding function.
268
- */
269
194
  async function main() {
270
195
  const args = parseArgs(process.argv.slice(2));
271
- const force = Boolean(args.force);
272
- const isYes = Boolean(args.yes);
273
- const shouldInstall = args.install
274
- ? true
275
- : args["no-install"]
276
- ? false
277
- : true; // Default to installing for scaffold mode
278
-
279
- if (args.help || args.h) {
280
- // eslint-disable-next-line no-console
196
+ const force = args.force === true;
197
+ const isYes = args.yes === true;
198
+ const shouldInstall = args.install === true ? true : args["no-install"] !== true;
199
+ if (args.help === true || args.h === true) {
281
200
  console.log(`
282
201
  ${colorize.blue(symbols.brand)} ${colorize.bold("create-dzx")}
283
202
 
@@ -294,17 +213,14 @@ ${colorize.bold("Options")}
294
213
  `);
295
214
  return;
296
215
  }
297
-
298
216
  clack.intro("create-dzx");
299
-
300
- const dirArg = args.dir ?? args.positional[0];
217
+ const dirArg = typeof args.dir === "string" ? args.dir : args.positional[0];
301
218
  const defaultDir = "my-agent";
302
-
303
219
  let targetDir = path.resolve(process.cwd(), dirArg || defaultDir);
304
220
  if (!isYes) {
305
221
  const dirResponse = await clack.text({
306
222
  message: "Project directory",
307
- initialValue: dirArg || defaultDir,
223
+ initialValue: dirArg || defaultDir
308
224
  });
309
225
  if (clack.isCancel(dirResponse)) {
310
226
  clack.cancel("Aborted.");
@@ -312,17 +228,16 @@ ${colorize.bold("Options")}
312
228
  }
313
229
  targetDir = path.resolve(process.cwd(), dirResponse || defaultDir);
314
230
  }
315
-
316
- let template = args.template || (isYes ? "basic" : undefined);
231
+ let template = typeof args.template === "string" ? args.template : isYes ? "basic" : void 0;
317
232
  if (!template) {
318
233
  const templateResponse = await clack.select({
319
234
  message: "Template",
320
235
  options: [
321
236
  { value: "basic", label: "basic" },
322
237
  { value: "tools-only", label: "tools-only" },
323
- { value: "full", label: "full" },
238
+ { value: "full", label: "full" }
324
239
  ],
325
- initialValue: "basic",
240
+ initialValue: "basic"
326
241
  });
327
242
  if (clack.isCancel(templateResponse)) {
328
243
  clack.cancel("Aborted.");
@@ -330,16 +245,15 @@ ${colorize.bold("Options")}
330
245
  }
331
246
  template = templateResponse;
332
247
  }
333
-
334
- let runtime = args.runtime || (isYes ? "node" : undefined);
248
+ let runtime = typeof args.runtime === "string" ? args.runtime : isYes ? "node" : void 0;
335
249
  if (!runtime) {
336
250
  const runtimeResponse = await clack.select({
337
251
  message: "Runtime",
338
252
  options: [
339
253
  { value: "node", label: "node" },
340
- { value: "deno", label: "deno" },
254
+ { value: "deno", label: "deno" }
341
255
  ],
342
- initialValue: "node",
256
+ initialValue: "node"
343
257
  });
344
258
  if (clack.isCancel(runtimeResponse)) {
345
259
  clack.cancel("Aborted.");
@@ -347,67 +261,57 @@ ${colorize.bold("Options")}
347
261
  }
348
262
  runtime = runtimeResponse;
349
263
  }
350
-
351
264
  template = template ?? "basic";
352
265
  runtime = runtime ?? "node";
353
-
354
266
  if (!TEMPLATES.includes(template)) {
355
267
  throw new Error(`Unknown template: ${template}`);
356
268
  }
357
269
  if (!RUNTIMES.includes(runtime)) {
358
270
  throw new Error(`Unknown runtime: ${runtime}`);
359
271
  }
360
-
361
272
  if (!force && !isEmptyDir(targetDir)) {
362
273
  throw new Error(`Target directory is not empty: ${targetDir}. Use --force to overwrite.`);
363
274
  }
364
-
365
275
  const templatesRoot = resolveTemplatesRoot();
366
276
  const templateDir = path.join(templatesRoot, template);
367
277
  if (!fs.existsSync(templateDir)) {
368
278
  throw new Error(`Template not found: ${template}`);
369
279
  }
370
-
371
280
  if (force && !isYes) {
372
281
  const confirmation = await clack.confirm({
373
282
  message: "This will overwrite existing files. Continue?",
374
- initialValue: false,
283
+ initialValue: false
375
284
  });
376
285
  if (clack.isCancel(confirmation) || confirmation === false) {
377
286
  clack.cancel("Aborted.");
378
287
  process.exit(1);
379
288
  }
380
289
  }
381
-
382
290
  const spinner = createSpinner(process.stdout.isTTY);
383
291
  const stepLabels = [
384
292
  "Validating destination",
385
293
  "Copying template",
386
294
  "Configuring manifest",
387
- ...(shouldInstall ? ["Installing dependencies"] : []),
388
- "Finalizing",
295
+ ...shouldInstall ? ["Installing dependencies"] : [],
296
+ "Finalizing"
389
297
  ];
390
298
  const stepLabelWidth = stepLabels.reduce((max, label) => Math.max(max, label.length), 0);
391
299
  const stepTimes = [];
392
300
  let stepStart = Date.now();
393
301
  let lastStep = "";
394
302
  let spinnerStarted = false;
395
-
396
303
  const logStep = (label, ms) => {
397
304
  const paddedLabel = label.padEnd(stepLabelWidth);
398
305
  const paddedMs = `${ms}ms`.padStart(6);
399
306
  const line = `${colorize.cyan(symbols.step)} ${colorize.gray(paddedLabel)} ${colorize.dim(paddedMs)}`;
400
307
  if (spinner.isEnabled) {
401
308
  spinner.pause();
402
- // eslint-disable-next-line no-console
403
309
  console.log(line);
404
310
  spinner.resume();
405
311
  } else {
406
- // eslint-disable-next-line no-console
407
312
  console.log(line);
408
313
  }
409
314
  };
410
-
411
315
  const step = (message) => {
412
316
  const now = Date.now();
413
317
  if (lastStep) {
@@ -426,31 +330,24 @@ ${colorize.bold("Options")}
426
330
  }
427
331
  }
428
332
  };
429
-
430
333
  const dzxVersion = await getDzxVersion();
431
334
  const banner = `${colorize.blue(symbols.brand)} ${colorize.bold("create-dzx")} ${colorize.gray("scaffold")}`;
432
- // eslint-disable-next-line no-console
433
335
  console.log(banner);
434
-
435
336
  step("Validating destination");
436
337
  if (!force) {
437
338
  const templateFiles = listFiles(templateDir);
438
339
  const collisions = templateFiles.filter((file) => fs.existsSync(path.join(targetDir, file)));
439
340
  if (collisions.length > 0) {
440
- const preview = collisions
441
- .slice(0, 8)
442
- .map((file) => `- ${file}`)
443
- .join("\n");
341
+ const preview = collisions.slice(0, 8).map((file) => `- ${file}`).join("\n");
444
342
  const suffix = collisions.length > 8 ? "\n- ..." : "";
445
343
  throw new Error(
446
- `Refusing to overwrite existing files. Use --force to proceed.\n${preview}${suffix}`,
344
+ `Refusing to overwrite existing files. Use --force to proceed.
345
+ ${preview}${suffix}`
447
346
  );
448
347
  }
449
348
  }
450
-
451
349
  step("Copying template");
452
350
  copyDir(templateDir, targetDir);
453
-
454
351
  step("Configuring manifest");
455
352
  const manifestPath = path.join(targetDir, "mcp.json");
456
353
  if (fs.existsSync(manifestPath)) {
@@ -459,25 +356,23 @@ ${colorize.bold("Options")}
459
356
  manifest.runtime = runtime;
460
357
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
461
358
  }
462
-
463
- // Update package.json with correct @dwizi/dzx version
464
359
  const pkgPath = path.join(targetDir, "package.json");
465
360
  if (fs.existsSync(pkgPath)) {
466
361
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
467
- if (pkg.dependencies && pkg.dependencies["@dwizi/dzx"]) {
362
+ if (pkg.dependencies?.["@dwizi/dzx"] === "string") {
468
363
  pkg.dependencies["@dwizi/dzx"] = `^${dzxVersion}`;
469
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
364
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
365
+ `);
470
366
  }
471
367
  }
472
-
473
368
  if (shouldInstall) {
474
369
  step("Installing dependencies");
475
370
  if (!fs.existsSync(pkgPath)) {
476
371
  if (spinner.isEnabled) spinner.stop();
477
372
  throw new Error("Missing package.json in template. Cannot install dependencies.");
478
373
  }
479
- const pm = detectPackageManager(targetDir);
480
- const installCommand = getInstallCommand(pm);
374
+ const pm2 = detectPackageManager(targetDir);
375
+ const installCommand = getInstallCommand(pm2);
481
376
  try {
482
377
  await runCommand(installCommand, targetDir);
483
378
  } catch {
@@ -485,7 +380,6 @@ ${colorize.bold("Options")}
485
380
  throw new Error(`Dependency installation failed. Run \`${installCommand}\` manually.`);
486
381
  }
487
382
  }
488
-
489
383
  step("Finalizing");
490
384
  if (lastStep) {
491
385
  const ms = Date.now() - stepStart;
@@ -493,32 +387,27 @@ ${colorize.bold("Options")}
493
387
  logStep(lastStep, ms);
494
388
  }
495
389
  spinner.stop();
496
-
497
390
  const totalMs = stepTimes.reduce((sum, item) => sum + item.ms, 0);
498
391
  const summaryLines = [
499
392
  { label: "dir", value: targetDir },
500
393
  { label: "template", value: template },
501
394
  { label: "runtime", value: runtime },
502
395
  { label: "install", value: shouldInstall ? "yes" : "no" },
503
- { label: "ready", value: `${totalMs}ms` },
396
+ { label: "ready", value: `${totalMs}ms` }
504
397
  ];
505
- // eslint-disable-next-line no-console
506
398
  console.log("");
507
- // eslint-disable-next-line no-console
508
399
  console.log(`${colorize.green(symbols.check)} ${colorize.bold("Project ready")}`);
509
400
  printKeyValueList(summaryLines);
510
401
  const pm = shouldInstall ? detectPackageManager(targetDir) : "pnpm";
402
+ const runCommand2 = getRunCommand(pm, "dev");
511
403
  const nextSteps = [
512
404
  `cd ${path.basename(targetDir)}`,
513
- shouldInstall ? "dzx dev" : `${pm} install`,
514
- shouldInstall ? "" : "dzx dev",
405
+ shouldInstall ? runCommand2 : `${pm} install`,
406
+ shouldInstall ? "" : runCommand2
515
407
  ].filter(Boolean);
516
- // eslint-disable-next-line no-console
517
408
  console.log(`${colorize.gray("next")} ${colorize.cyan(nextSteps.join(" && "))}`);
518
409
  }
519
-
520
410
  main().catch((err) => {
521
- // eslint-disable-next-line no-console
522
411
  console.error(err);
523
412
  process.exit(1);
524
413
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwizi/create-dzx",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -29,10 +29,17 @@
29
29
  "node": ">=24"
30
30
  },
31
31
  "devDependencies": {
32
- "@dwizi/dzx": "*"
32
+ "@biomejs/biome": "^2.3.13",
33
+ "@dwizi/dzx": "*",
34
+ "@types/node": "^25.1.0",
35
+ "esbuild": "^0.27.2",
36
+ "typescript": "^5.9.3"
33
37
  },
34
38
  "scripts": {
35
39
  "build": "node scripts/build.mjs",
40
+ "lint": "biome check .",
41
+ "format": "biome format .",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
36
43
  "test": "node --test tests/*.test.mjs",
37
44
  "fix:lockfile": "pnpm install --lockfile-only --ignore-workspace"
38
45
  }