@dwizi/create-dzx 0.1.10 → 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 +45 -159
  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,144 +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
183
  const { createRequire } = await import("node:module");
257
- const require = createRequire(import.meta.url);
258
- const dzxPkgJsonPath = require.resolve("@dwizi/dzx/package.json", { paths: [process.cwd()] });
184
+ const require2 = createRequire(import.meta.url);
185
+ const dzxPkgJsonPath = require2.resolve("@dwizi/dzx/package.json", { paths: [process.cwd()] });
259
186
  const dzxPkg = JSON.parse(fs.readFileSync(dzxPkgJsonPath, "utf8"));
260
187
  if (dzxPkg && typeof dzxPkg.version === "string" && dzxPkg.version.trim()) {
261
188
  return dzxPkg.version.trim();
262
189
  }
263
190
  } catch {
264
191
  }
265
- // Fallback version if we cannot resolve an installed @dwizi/dzx
266
192
  return "*";
267
193
  }
268
-
269
- /**
270
- * Main scaffolding function.
271
- */
272
194
  async function main() {
273
195
  const args = parseArgs(process.argv.slice(2));
274
- const force = Boolean(args.force);
275
- const isYes = Boolean(args.yes);
276
- const shouldInstall = args.install
277
- ? true
278
- : args["no-install"]
279
- ? false
280
- : true; // Default to installing for scaffold mode
281
-
282
- if (args.help || args.h) {
283
- // 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) {
284
200
  console.log(`
285
201
  ${colorize.blue(symbols.brand)} ${colorize.bold("create-dzx")}
286
202
 
@@ -297,17 +213,14 @@ ${colorize.bold("Options")}
297
213
  `);
298
214
  return;
299
215
  }
300
-
301
216
  clack.intro("create-dzx");
302
-
303
- const dirArg = args.dir ?? args.positional[0];
217
+ const dirArg = typeof args.dir === "string" ? args.dir : args.positional[0];
304
218
  const defaultDir = "my-agent";
305
-
306
219
  let targetDir = path.resolve(process.cwd(), dirArg || defaultDir);
307
220
  if (!isYes) {
308
221
  const dirResponse = await clack.text({
309
222
  message: "Project directory",
310
- initialValue: dirArg || defaultDir,
223
+ initialValue: dirArg || defaultDir
311
224
  });
312
225
  if (clack.isCancel(dirResponse)) {
313
226
  clack.cancel("Aborted.");
@@ -315,17 +228,16 @@ ${colorize.bold("Options")}
315
228
  }
316
229
  targetDir = path.resolve(process.cwd(), dirResponse || defaultDir);
317
230
  }
318
-
319
- let template = args.template || (isYes ? "basic" : undefined);
231
+ let template = typeof args.template === "string" ? args.template : isYes ? "basic" : void 0;
320
232
  if (!template) {
321
233
  const templateResponse = await clack.select({
322
234
  message: "Template",
323
235
  options: [
324
236
  { value: "basic", label: "basic" },
325
237
  { value: "tools-only", label: "tools-only" },
326
- { value: "full", label: "full" },
238
+ { value: "full", label: "full" }
327
239
  ],
328
- initialValue: "basic",
240
+ initialValue: "basic"
329
241
  });
330
242
  if (clack.isCancel(templateResponse)) {
331
243
  clack.cancel("Aborted.");
@@ -333,16 +245,15 @@ ${colorize.bold("Options")}
333
245
  }
334
246
  template = templateResponse;
335
247
  }
336
-
337
- let runtime = args.runtime || (isYes ? "node" : undefined);
248
+ let runtime = typeof args.runtime === "string" ? args.runtime : isYes ? "node" : void 0;
338
249
  if (!runtime) {
339
250
  const runtimeResponse = await clack.select({
340
251
  message: "Runtime",
341
252
  options: [
342
253
  { value: "node", label: "node" },
343
- { value: "deno", label: "deno" },
254
+ { value: "deno", label: "deno" }
344
255
  ],
345
- initialValue: "node",
256
+ initialValue: "node"
346
257
  });
347
258
  if (clack.isCancel(runtimeResponse)) {
348
259
  clack.cancel("Aborted.");
@@ -350,67 +261,57 @@ ${colorize.bold("Options")}
350
261
  }
351
262
  runtime = runtimeResponse;
352
263
  }
353
-
354
264
  template = template ?? "basic";
355
265
  runtime = runtime ?? "node";
356
-
357
266
  if (!TEMPLATES.includes(template)) {
358
267
  throw new Error(`Unknown template: ${template}`);
359
268
  }
360
269
  if (!RUNTIMES.includes(runtime)) {
361
270
  throw new Error(`Unknown runtime: ${runtime}`);
362
271
  }
363
-
364
272
  if (!force && !isEmptyDir(targetDir)) {
365
273
  throw new Error(`Target directory is not empty: ${targetDir}. Use --force to overwrite.`);
366
274
  }
367
-
368
275
  const templatesRoot = resolveTemplatesRoot();
369
276
  const templateDir = path.join(templatesRoot, template);
370
277
  if (!fs.existsSync(templateDir)) {
371
278
  throw new Error(`Template not found: ${template}`);
372
279
  }
373
-
374
280
  if (force && !isYes) {
375
281
  const confirmation = await clack.confirm({
376
282
  message: "This will overwrite existing files. Continue?",
377
- initialValue: false,
283
+ initialValue: false
378
284
  });
379
285
  if (clack.isCancel(confirmation) || confirmation === false) {
380
286
  clack.cancel("Aborted.");
381
287
  process.exit(1);
382
288
  }
383
289
  }
384
-
385
290
  const spinner = createSpinner(process.stdout.isTTY);
386
291
  const stepLabels = [
387
292
  "Validating destination",
388
293
  "Copying template",
389
294
  "Configuring manifest",
390
- ...(shouldInstall ? ["Installing dependencies"] : []),
391
- "Finalizing",
295
+ ...shouldInstall ? ["Installing dependencies"] : [],
296
+ "Finalizing"
392
297
  ];
393
298
  const stepLabelWidth = stepLabels.reduce((max, label) => Math.max(max, label.length), 0);
394
299
  const stepTimes = [];
395
300
  let stepStart = Date.now();
396
301
  let lastStep = "";
397
302
  let spinnerStarted = false;
398
-
399
303
  const logStep = (label, ms) => {
400
304
  const paddedLabel = label.padEnd(stepLabelWidth);
401
305
  const paddedMs = `${ms}ms`.padStart(6);
402
306
  const line = `${colorize.cyan(symbols.step)} ${colorize.gray(paddedLabel)} ${colorize.dim(paddedMs)}`;
403
307
  if (spinner.isEnabled) {
404
308
  spinner.pause();
405
- // eslint-disable-next-line no-console
406
309
  console.log(line);
407
310
  spinner.resume();
408
311
  } else {
409
- // eslint-disable-next-line no-console
410
312
  console.log(line);
411
313
  }
412
314
  };
413
-
414
315
  const step = (message) => {
415
316
  const now = Date.now();
416
317
  if (lastStep) {
@@ -429,31 +330,24 @@ ${colorize.bold("Options")}
429
330
  }
430
331
  }
431
332
  };
432
-
433
333
  const dzxVersion = await getDzxVersion();
434
334
  const banner = `${colorize.blue(symbols.brand)} ${colorize.bold("create-dzx")} ${colorize.gray("scaffold")}`;
435
- // eslint-disable-next-line no-console
436
335
  console.log(banner);
437
-
438
336
  step("Validating destination");
439
337
  if (!force) {
440
338
  const templateFiles = listFiles(templateDir);
441
339
  const collisions = templateFiles.filter((file) => fs.existsSync(path.join(targetDir, file)));
442
340
  if (collisions.length > 0) {
443
- const preview = collisions
444
- .slice(0, 8)
445
- .map((file) => `- ${file}`)
446
- .join("\n");
341
+ const preview = collisions.slice(0, 8).map((file) => `- ${file}`).join("\n");
447
342
  const suffix = collisions.length > 8 ? "\n- ..." : "";
448
343
  throw new Error(
449
- `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}`
450
346
  );
451
347
  }
452
348
  }
453
-
454
349
  step("Copying template");
455
350
  copyDir(templateDir, targetDir);
456
-
457
351
  step("Configuring manifest");
458
352
  const manifestPath = path.join(targetDir, "mcp.json");
459
353
  if (fs.existsSync(manifestPath)) {
@@ -462,25 +356,23 @@ ${colorize.bold("Options")}
462
356
  manifest.runtime = runtime;
463
357
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
464
358
  }
465
-
466
- // Update package.json with correct @dwizi/dzx version
467
359
  const pkgPath = path.join(targetDir, "package.json");
468
360
  if (fs.existsSync(pkgPath)) {
469
361
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
470
- if (pkg.dependencies && pkg.dependencies["@dwizi/dzx"]) {
362
+ if (pkg.dependencies?.["@dwizi/dzx"] === "string") {
471
363
  pkg.dependencies["@dwizi/dzx"] = `^${dzxVersion}`;
472
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
364
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
365
+ `);
473
366
  }
474
367
  }
475
-
476
368
  if (shouldInstall) {
477
369
  step("Installing dependencies");
478
370
  if (!fs.existsSync(pkgPath)) {
479
371
  if (spinner.isEnabled) spinner.stop();
480
372
  throw new Error("Missing package.json in template. Cannot install dependencies.");
481
373
  }
482
- const pm = detectPackageManager(targetDir);
483
- const installCommand = getInstallCommand(pm);
374
+ const pm2 = detectPackageManager(targetDir);
375
+ const installCommand = getInstallCommand(pm2);
484
376
  try {
485
377
  await runCommand(installCommand, targetDir);
486
378
  } catch {
@@ -488,7 +380,6 @@ ${colorize.bold("Options")}
488
380
  throw new Error(`Dependency installation failed. Run \`${installCommand}\` manually.`);
489
381
  }
490
382
  }
491
-
492
383
  step("Finalizing");
493
384
  if (lastStep) {
494
385
  const ms = Date.now() - stepStart;
@@ -496,32 +387,27 @@ ${colorize.bold("Options")}
496
387
  logStep(lastStep, ms);
497
388
  }
498
389
  spinner.stop();
499
-
500
390
  const totalMs = stepTimes.reduce((sum, item) => sum + item.ms, 0);
501
391
  const summaryLines = [
502
392
  { label: "dir", value: targetDir },
503
393
  { label: "template", value: template },
504
394
  { label: "runtime", value: runtime },
505
395
  { label: "install", value: shouldInstall ? "yes" : "no" },
506
- { label: "ready", value: `${totalMs}ms` },
396
+ { label: "ready", value: `${totalMs}ms` }
507
397
  ];
508
- // eslint-disable-next-line no-console
509
398
  console.log("");
510
- // eslint-disable-next-line no-console
511
399
  console.log(`${colorize.green(symbols.check)} ${colorize.bold("Project ready")}`);
512
400
  printKeyValueList(summaryLines);
513
401
  const pm = shouldInstall ? detectPackageManager(targetDir) : "pnpm";
402
+ const runCommand2 = getRunCommand(pm, "dev");
514
403
  const nextSteps = [
515
404
  `cd ${path.basename(targetDir)}`,
516
- shouldInstall ? "dzx dev" : `${pm} install`,
517
- shouldInstall ? "" : "dzx dev",
405
+ shouldInstall ? runCommand2 : `${pm} install`,
406
+ shouldInstall ? "" : runCommand2
518
407
  ].filter(Boolean);
519
- // eslint-disable-next-line no-console
520
408
  console.log(`${colorize.gray("next")} ${colorize.cyan(nextSteps.join(" && "))}`);
521
409
  }
522
-
523
410
  main().catch((err) => {
524
- // eslint-disable-next-line no-console
525
411
  console.error(err);
526
412
  process.exit(1);
527
413
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwizi/create-dzx",
3
- "version": "0.1.10",
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
  }