@nanoforge-dev/cli 1.1.0 → 1.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/dist/nf.js CHANGED
@@ -36,11 +36,11 @@ var __decorateClass = (decorators, target, key, kind) => {
36
36
 
37
37
  // package.json
38
38
  var require_package = __commonJS({
39
- "package.json"(exports, module2) {
40
- module2.exports = {
39
+ "package.json"(exports, module) {
40
+ module.exports = {
41
41
  $schema: "https://json.schemastore.org/package.json",
42
42
  name: "@nanoforge-dev/cli",
43
- version: "1.1.0",
43
+ version: "1.2.0",
44
44
  description: "NanoForge CLI",
45
45
  keywords: [
46
46
  "nanoforge",
@@ -79,6 +79,12 @@ var require_package = __commonJS({
79
79
  prepack: "pnpm run build && pnpm run lint",
80
80
  changelog: "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r . --include-path '.'",
81
81
  release: "cliff-jumper",
82
+ test: "pnpm run test:unit && pnpm run test:e2e",
83
+ "test:unit": "vitest run -c vitest.config.ts",
84
+ "test:unit:watch": "vitest -c vitest.config.ts",
85
+ "test:e2e": "vitest run -c vitest.config.e2e.ts",
86
+ "test:e2e:watch": "vitest -c vitest.config.e2e.ts",
87
+ "test:coverage": "pnpm run test:unit --coverage && pnpm run test:e2e",
82
88
  prepare: "husky"
83
89
  },
84
90
  dependencies: {
@@ -108,12 +114,14 @@ var require_package = __commonJS({
108
114
  "@trivago/prettier-plugin-sort-imports": "catalog:lint",
109
115
  "@types/inquirer": "catalog:cli",
110
116
  "@types/node": "catalog:core",
117
+ "@vitest/coverage-v8": "catalog:tests",
111
118
  eslint: "catalog:lint",
112
119
  husky: "catalog:ci",
113
120
  "lint-staged": "catalog:ci",
114
121
  prettier: "catalog:lint",
115
122
  tsup: "catalog:build",
116
- typescript: "catalog:build"
123
+ typescript: "catalog:build",
124
+ vitest: "catalog:tests"
117
125
  },
118
126
  packageManager: "pnpm@10.28.1",
119
127
  engines: {
@@ -151,7 +159,7 @@ var loadLocalBinCommandLoader = /* @__PURE__ */ __name(async () => {
151
159
  }, "loadLocalBinCommandLoader");
152
160
 
153
161
  // src/command/command.loader.ts
154
- import { red as red8 } from "ansis";
162
+ import { red as red6 } from "ansis";
155
163
 
156
164
  // src/lib/ui/messages.ts
157
165
  import { green } from "ansis";
@@ -179,48 +187,63 @@ var Emojis = {
179
187
  };
180
188
 
181
189
  // src/lib/ui/messages.ts
190
+ var success = /* @__PURE__ */ __name((text) => `${Emojis.ROCKET} ${text}`, "success");
191
+ var failure = /* @__PURE__ */ __name((text) => `${Emojis.SCREAM} ${text}`, "failure");
182
192
  var Messages = {
193
+ // --- Build ---
183
194
  BUILD_START: "NanoForge Build",
184
- BUILD_WATCH_START: "Start watching mode",
195
+ BUILD_SUCCESS: success("Build succeeded!"),
196
+ BUILD_FAILED: failure("Build failed!"),
197
+ BUILD_WATCH_START: "Watching for changes...",
185
198
  BUILD_PART_IN_PROGRESS: /* @__PURE__ */ __name((part) => `Building ${part}`, "BUILD_PART_IN_PROGRESS"),
186
- BUILD_PART_WATCH_IN_PROGRESS: /* @__PURE__ */ __name((part) => `${part} updated. Rebuilding`, "BUILD_PART_WATCH_IN_PROGRESS"),
187
- BUILD_NOTHING: "Nothing to build, terminated.",
188
- BUILD_SUCCESS: `${Emojis.ROCKET} Build succeeded !`,
189
- BUILD_PART_FAILED: /* @__PURE__ */ __name((part, commandToRunManually) => `${Emojis.SCREAM} Build of ${part} failed !
190
- In case you don't see any errors above, consider manually running the failed command ${commandToRunManually} to see more details on why it errored out.`, "BUILD_PART_FAILED"),
191
- BUILD_FAILED: `${Emojis.SCREAM} Build failed !`,
199
+ BUILD_PART_WATCH_IN_PROGRESS: /* @__PURE__ */ __name((part) => `${part} updated, rebuilding`, "BUILD_PART_WATCH_IN_PROGRESS"),
200
+ BUILD_PART_FAILED: /* @__PURE__ */ __name((part, command) => failure(`Build of ${part} failed!
201
+ Try running manually: ${command}`), "BUILD_PART_FAILED"),
202
+ // --- Install ---
192
203
  INSTALL_START: "NanoForge Installation",
193
- INSTALL_NAMES_QUESTION: "Witch libraries do you want to install ?",
204
+ INSTALL_SUCCESS: success("Installation completed!"),
205
+ INSTALL_FAILED: failure("Installation failed!"),
206
+ INSTALL_NAMES_QUESTION: "Which libraries do you want to install?",
207
+ // --- New Project ---
194
208
  NEW_START: "NanoForge Project Creation",
195
- NEW_SUCCESS: `${Emojis.ROCKET} Project successfully created !`,
196
- NEW_FAILED: `${Emojis.SCREAM} Project creation failed !`,
197
- NEW_NAME_QUESTION: "What is the name of your project ?",
198
- NEW_PACKAGE_MANAGER_QUESTION: "Which package manager do you want to use ?",
199
- NEW_LANGUAGE_QUESTION: "Which language do you want to use ?",
200
- NEW_STRICT_QUESTION: "Do you want to use types strict mode ?",
201
- NEW_SERVER_QUESTION: "Do you want generate a server to create a multiplayer game ?",
202
- NEW_SKIP_INSTALL_QUESTION: "Do you want to skip installation ?",
209
+ NEW_SUCCESS: success("Project successfully created!"),
210
+ NEW_FAILED: failure("Project creation failed!"),
211
+ NEW_NAME_QUESTION: "What is the name of your project?",
212
+ NEW_PACKAGE_MANAGER_QUESTION: "Which package manager do you want to use?",
213
+ NEW_LANGUAGE_QUESTION: "Which language do you want to use?",
214
+ NEW_STRICT_QUESTION: "Do you want to use strict type checking?",
215
+ NEW_SERVER_QUESTION: "Do you want to generate a server for multiplayer?",
216
+ NEW_SKIP_INSTALL_QUESTION: "Do you want to skip dependency installation?",
217
+ NEW_DOCKER_QUESTION: "Do you want to add a Dockerfile for containerization?",
218
+ // --- Generate ---
203
219
  GENERATE_START: "NanoForge Generate",
204
- GENERATE_WATCH_START: "Start watching mode",
205
- GENERATE_SUCCESS: `${Emojis.ROCKET} Generate succeeded !`,
206
- GENERATE_FAILED: `${Emojis.SCREAM} Generate failed !`,
207
- DEV_START: "NanoForge Dev mode",
220
+ GENERATE_SUCCESS: success("Generation succeeded!"),
221
+ GENERATE_FAILED: failure("Generation failed!"),
222
+ GENERATE_WATCH_START: "Watching for changes...",
223
+ // --- Dev ---
224
+ DEV_START: "NanoForge Dev Mode",
208
225
  DEV_SUCCESS: "Dev mode ended",
209
- DEV_FAILED: `${Emojis.SCREAM} Dev failed !`,
210
- SCHEMATICS_START: "Schematics execution",
211
- SCHEMATIC_IN_PROGRESS: /* @__PURE__ */ __name((name) => `Executing schematic ${name}...`, "SCHEMATIC_IN_PROGRESS"),
212
- SCHEMATIC_WATCH_IN_PROGRESS: /* @__PURE__ */ __name((name) => `Update watched. Executing schematic ${name}...`, "SCHEMATIC_WATCH_IN_PROGRESS"),
213
- SCHEMATIC_SUCCESS: /* @__PURE__ */ __name((name) => `${Emojis.ROCKET} Schematic ${name} executed successfully !`, "SCHEMATIC_SUCCESS"),
214
- SCHEMATIC_FAILED: /* @__PURE__ */ __name((name) => `${Emojis.SCREAM} Schematic ${name} execution failed. See error below for more details.`, "SCHEMATIC_FAILED"),
215
- PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS: `Installation in progress... ${Emojis.COFFEE}`,
216
- PACKAGE_MANAGER_INSTALLATION_NOTHING: "Nothing to install, terminated.",
217
- PACKAGE_MANAGER_INSTALLATION_SUCCEED: /* @__PURE__ */ __name((names) => names ? `${Emojis.ROCKET} Packages successfully installed : ${names.map((name) => green(name)).join(", ")} !` : `${Emojis.ROCKET} Packages successfully installed !`, "PACKAGE_MANAGER_INSTALLATION_SUCCEED"),
218
- PACKAGE_MANAGER_INSTALLATION_FAILED: /* @__PURE__ */ __name((commandToRunManually) => `${Emojis.SCREAM} Packages installation failed !
219
- In case you don't see any errors above, consider manually running the failed command ${commandToRunManually} to see more details on why it errored out.`, "PACKAGE_MANAGER_INSTALLATION_FAILED"),
220
- RUN_START: "NanoForge Run",
221
- RUN_PART_IN_PROGRESS: /* @__PURE__ */ __name((part) => `Running ${part}...`, "RUN_PART_IN_PROGRESS"),
222
- RUN_PART_SUCCESS: /* @__PURE__ */ __name((part) => `${Emojis.ROCKET} Run of ${part} terminated.`, "RUN_PART_SUCCESS"),
223
- RUN_PART_FAILED: /* @__PURE__ */ __name((part) => `${Emojis.SCREAM} Run of ${part} failed !`, "RUN_PART_FAILED"),
226
+ DEV_FAILED: failure("Dev mode failed!"),
227
+ // --- Start ---
228
+ START_START: "NanoForge Start",
229
+ START_SUCCESS: success("Start completed!"),
230
+ START_FAILED: failure("Start failed!"),
231
+ START_PART_IN_PROGRESS: /* @__PURE__ */ __name((part) => `Starting ${part}...`, "START_PART_IN_PROGRESS"),
232
+ START_PART_SUCCESS: /* @__PURE__ */ __name((part) => success(`${part} terminated.`), "START_PART_SUCCESS"),
233
+ START_PART_FAILED: /* @__PURE__ */ __name((part) => failure(`${part} failed!`), "START_PART_FAILED"),
234
+ // --- Schematics ---
235
+ SCHEMATICS_START: "Running schematics",
236
+ SCHEMATIC_IN_PROGRESS: /* @__PURE__ */ __name((name) => `Generating ${name}...`, "SCHEMATIC_IN_PROGRESS"),
237
+ SCHEMATIC_WATCH_IN_PROGRESS: /* @__PURE__ */ __name((name) => `Change detected, regenerating ${name}...`, "SCHEMATIC_WATCH_IN_PROGRESS"),
238
+ SCHEMATIC_SUCCESS: /* @__PURE__ */ __name((name) => success(`${name} generated successfully!`), "SCHEMATIC_SUCCESS"),
239
+ SCHEMATIC_FAILED: /* @__PURE__ */ __name((name) => failure(`${name} generation failed.`), "SCHEMATIC_FAILED"),
240
+ // --- Package Manager ---
241
+ PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS: `Installing dependencies... ${Emojis.COFFEE}`,
242
+ PACKAGE_MANAGER_INSTALLATION_NOTHING: "Nothing to install.",
243
+ PACKAGE_MANAGER_INSTALLATION_SUCCEED: /* @__PURE__ */ __name((names) => names ? success(`Packages installed: ${names.map((n) => green(n)).join(", ")}`) : success("Packages installed!"), "PACKAGE_MANAGER_INSTALLATION_SUCCEED"),
244
+ PACKAGE_MANAGER_INSTALLATION_FAILED: /* @__PURE__ */ __name((command) => failure(`Package installation failed!
245
+ Try running manually: ${command}`), "PACKAGE_MANAGER_INSTALLATION_FAILED"),
246
+ // --- Runner ---
224
247
  RUNNER_EXECUTION_ERROR: /* @__PURE__ */ __name((command) => `
225
248
  Failed to execute command: ${command}`, "RUNNER_EXECUTION_ERROR")
226
249
  };
@@ -232,11 +255,13 @@ var Prefixes = {
232
255
  ERROR: bgRgb(210, 0, 75).bold.rgb(0, 0, 0)(" Error ")
233
256
  };
234
257
 
258
+ // src/lib/ui/spinner.ts
259
+ import ora from "ora";
260
+ var getSpinner = /* @__PURE__ */ __name((message) => ora({ text: message }), "getSpinner");
261
+
235
262
  // src/action/actions/build.action.ts
236
- import * as ansis from "ansis";
237
263
  import { watch } from "chokidar";
238
- import * as console2 from "console";
239
- import { dirname, join as join3 } from "path";
264
+ import { dirname, join as join4 } from "path";
240
265
 
241
266
  // src/lib/input/base-inputs.ts
242
267
  var getStringInput = /* @__PURE__ */ __name((input2, field) => {
@@ -299,12 +324,24 @@ var getDevGenerateInput = /* @__PURE__ */ __name((inputs) => {
299
324
  import { confirm } from "@inquirer/prompts";
300
325
 
301
326
  // src/lib/utils/errors.ts
327
+ import { red } from "ansis";
328
+ var getErrorMessage = /* @__PURE__ */ __name((error) => {
329
+ if (error instanceof Error) return error.message;
330
+ if (typeof error === "string") return error;
331
+ return void 0;
332
+ }, "getErrorMessage");
333
+ var handleActionError = /* @__PURE__ */ __name((context, error) => {
334
+ console.error();
335
+ console.error(red(context));
336
+ const msg = getErrorMessage(error);
337
+ if (msg) console.error(msg);
338
+ process.exit(1);
339
+ }, "handleActionError");
302
340
  var promptError = /* @__PURE__ */ __name((err) => {
303
341
  if (err.name === "ExitPromptError") {
304
342
  process.exit(1);
305
- } else {
306
- throw err;
307
343
  }
344
+ throw err;
308
345
  }, "promptError");
309
346
 
310
347
  // src/lib/question/questions/confirm.question.ts
@@ -376,82 +413,31 @@ var getInstallNamesInputOrAsk = /* @__PURE__ */ __name((inputs) => {
376
413
  );
377
414
  }, "getInstallNamesInputOrAsk");
378
415
 
379
- // src/lib/package-manager/package-manager.factory.ts
380
- import fs from "fs";
381
- import { resolve as resolve2 } from "path";
416
+ // src/lib/package-manager/package-manager.ts
417
+ import { bold, red as red3 } from "ansis";
382
418
 
383
- // src/lib/runner/runner.factory.ts
384
- import { yellow } from "ansis";
385
-
386
- // src/lib/runner/abstract.runner.ts
387
- import { red } from "ansis";
388
- import { spawn } from "child_process";
389
- import * as process2 from "process";
390
- var AbstractRunner = class {
391
- constructor(binary, args = []) {
392
- this.binary = binary;
393
- this.args = args;
419
+ // src/lib/runner/process-logger.ts
420
+ import { green as green2, red as red2, yellow } from "ansis";
421
+ var formatLines = /* @__PURE__ */ __name((chunk) => {
422
+ return chunk.toString().replace(/\r\n|\n/g, "\n").replace(/^\n+|\n+$/g, "").split("\n");
423
+ }, "formatLines");
424
+ var timestamp = /* @__PURE__ */ __name(() => yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}]`), "timestamp");
425
+ var createStdoutLogger = /* @__PURE__ */ __name((name) => (chunk) => {
426
+ const prefix = green2(`(${name}) INFO -`);
427
+ for (const line of formatLines(chunk)) {
428
+ console.info(`${timestamp()} ${prefix} ${line}`);
394
429
  }
395
- static {
396
- __name(this, "AbstractRunner");
430
+ }, "createStdoutLogger");
431
+ var createStderrLogger = /* @__PURE__ */ __name((name) => (chunk) => {
432
+ const prefix = red2(`(${name}) ERROR -`);
433
+ for (const line of formatLines(chunk)) {
434
+ console.error(`${timestamp()} ${prefix} ${line}`);
397
435
  }
398
- async run(args, collect = false, cwd2 = process2.cwd(), env2, listeners, failSpinner) {
399
- const options = {
400
- cwd: cwd2,
401
- stdio: collect ? "pipe" : "inherit",
402
- shell: true,
403
- env: { ...process2.env, ...env2 }
404
- };
405
- return new Promise((resolve3, reject) => {
406
- const child = spawn(
407
- `${this.binary} ${[...this.args, ...args].join(" ")}`,
408
- options
409
- );
410
- const res = [];
411
- child.stdout?.on(
412
- "data",
413
- listeners?.onStdout ?? ((data) => res.push(data.toString().replace(/\r\n|\n/, "")))
414
- );
415
- child.stderr?.on(
416
- "data",
417
- listeners?.onStderr ?? ((data) => res.push(data.toString().replace(/\r\n|\n/, "")))
418
- );
419
- child.on("close", (code) => {
420
- if (code === 0) {
421
- resolve3(collect && res.length ? res.join("\n") : null);
422
- } else {
423
- if (failSpinner) failSpinner();
424
- console.error(
425
- red(Messages.RUNNER_EXECUTION_ERROR([this.binary, ...this.args, ...args].join(" ")))
426
- );
427
- if (res.length) {
428
- console.error();
429
- console.error(res.join("\n"));
430
- console.error();
431
- }
432
- reject();
433
- }
434
- });
435
- });
436
- }
437
- rawFullCommand(args) {
438
- const commandArgs = [...this.args, ...args];
439
- return `${this.binary} ${commandArgs.join(" ")}`;
440
- }
441
- };
442
-
443
- // src/lib/runner/runners/bun.runner.ts
444
- var BunRunner = class extends AbstractRunner {
445
- static {
446
- __name(this, "BunRunner");
447
- }
448
- constructor() {
449
- super("bun");
450
- }
451
- };
436
+ }, "createStderrLogger");
452
437
 
453
438
  // src/lib/utils/path.ts
454
- import { resolve } from "path";
439
+ import fs from "fs";
440
+ import { join as join2, resolve } from "path";
455
441
  var getCwd = /* @__PURE__ */ __name((directory) => {
456
442
  return resolve(directory);
457
443
  }, "getCwd");
@@ -460,430 +446,394 @@ var getModulePath = /* @__PURE__ */ __name((name, removeLast = false) => {
460
446
  if (removeLast) return path.split("/").slice(0, -1).join("/");
461
447
  return path;
462
448
  }, "getModulePath");
463
- var getNodeBinaryPath = /* @__PURE__ */ __name((name) => {
464
- return resolve("node_modules", ".bin", name);
465
- }, "getNodeBinaryPath");
466
-
467
- // src/lib/runner/runners/local-bun.runner.ts
468
- var LocalBunRunner = class extends AbstractRunner {
469
- static {
470
- __name(this, "LocalBunRunner");
471
- }
472
- constructor() {
473
- super(getNodeBinaryPath("bun"));
474
- }
475
- };
476
-
477
- // src/lib/runner/runners/npm.runner.ts
478
- var NpmRunner = class extends AbstractRunner {
479
- static {
480
- __name(this, "NpmRunner");
481
- }
482
- constructor() {
483
- super("npm");
484
- }
485
- };
486
-
487
- // src/lib/runner/runners/pnpm.runner.ts
488
- var PnpmRunner = class extends AbstractRunner {
489
- static {
490
- __name(this, "PnpmRunner");
491
- }
492
- constructor() {
493
- super("pnpm");
494
- }
495
- };
496
-
497
- // src/lib/runner/runners/schematic.runner.ts
498
- var SchematicRunner = class _SchematicRunner extends AbstractRunner {
499
- static {
500
- __name(this, "SchematicRunner");
501
- }
502
- static getModulePaths() {
503
- return module.paths;
504
- }
505
- static findClosestSchematicsBinary() {
449
+ var resolveCLINodeBinaryPath = /* @__PURE__ */ __name((name) => {
450
+ let base = join2(getModulePath(".", true), "..");
451
+ while (base.length >= 1) {
452
+ const path = join2(base, "node_modules", ".bin", name);
506
453
  try {
507
- return getModulePath("@angular-devkit/schematics-cli/bin/schematics.js");
508
- } catch (e) {
509
- console.error(e);
510
- throw new Error("'schematics' binary path could not be found!");
511
- }
512
- }
513
- constructor() {
514
- super(`node`, [`"${_SchematicRunner.findClosestSchematicsBinary()}"`]);
515
- }
516
- };
517
-
518
- // src/lib/runner/runners/yarn.runner.ts
519
- var YarnRunner = class extends AbstractRunner {
520
- static {
521
- __name(this, "YarnRunner");
522
- }
523
- constructor() {
524
- super("yarn");
525
- }
526
- };
527
-
528
- // src/lib/runner/runner.factory.ts
529
- var RunnerFactory = class {
530
- static {
531
- __name(this, "RunnerFactory");
532
- }
533
- static create(runner) {
534
- switch (runner) {
535
- case 0 /* BUN */:
536
- return new BunRunner();
537
- case 1 /* LOCAL_BUN */:
538
- return new LocalBunRunner();
539
- case 2 /* NPM */:
540
- return new NpmRunner();
541
- case 3 /* PNPM */:
542
- return new PnpmRunner();
543
- case 4 /* SCHEMATIC */:
544
- return new SchematicRunner();
545
- case 5 /* YARN */:
546
- return new YarnRunner();
547
- default:
548
- console.info(yellow`[WARN] Unsupported runner: ${runner}`);
549
- throw Error(`Unsupported runner: ${runner}`);
454
+ fs.accessSync(path);
455
+ return path;
456
+ } catch {
457
+ base = join2(base, "..");
550
458
  }
551
459
  }
552
- };
460
+ throw new Error("Could not find module path");
461
+ }, "resolveCLINodeBinaryPath");
553
462
 
554
- // src/lib/package-manager/abstract.package-manager.ts
555
- import { bold, green as green2, red as red2, yellow as yellow2 } from "ansis";
556
- import ora from "ora";
557
- var SPINNER = /* @__PURE__ */ __name((message) => ora({
558
- text: message
559
- }), "SPINNER");
560
- var FAIL_SPINNER = /* @__PURE__ */ __name((spinner) => () => spinner.fail(), "FAIL_SPINNER");
561
- var AbstractPackageManager = class {
562
- constructor(runner) {
463
+ // src/lib/package-manager/package-manager.ts
464
+ var PackageManager = class {
465
+ constructor(name, commands, runner) {
466
+ this.name = name;
467
+ this.commands = commands;
563
468
  this.runner = runner;
564
469
  }
565
470
  static {
566
- __name(this, "AbstractPackageManager");
471
+ __name(this, "PackageManager");
567
472
  }
568
473
  async install(directory) {
569
- const spinner = SPINNER(Messages.PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS);
570
- spinner.start();
571
- try {
572
- const commandArgs = [this.cli.install, this.cli.silentFlag];
573
- const collect = true;
574
- await this.runner.run(
575
- commandArgs,
576
- collect,
577
- getCwd(directory),
578
- void 0,
579
- void 0,
580
- () => spinner.fail()
581
- );
582
- spinner.succeed();
583
- this.printInstallSuccess();
584
- } catch {
585
- const commandArgs = [this.cli.install];
586
- const commandToRun = this.runner.rawFullCommand(commandArgs);
587
- this.printInstallFailure(commandToRun);
588
- }
589
- }
590
- version() {
591
- const commandArguments = ["--version"];
592
- const collect = true;
593
- return this.runner.run(commandArguments, collect);
594
- }
595
- addProduction(directory, dependencies) {
596
- const command = [this.cli.add, this.cli.saveFlag];
597
- return this.add(command, directory, dependencies);
598
- }
599
- addDevelopment(directory, dependencies) {
600
- const command = [this.cli.add, this.cli.saveDevFlag];
601
- return this.add(command, directory, dependencies);
602
- }
603
- async build(name, directory, entry, output, flags, watch3) {
604
- if (!this.cli.build) throw new Error(`Package manager ${this.name} does not support building`);
605
- const spinner = SPINNER(
606
- (watch3 ? Messages.BUILD_PART_WATCH_IN_PROGRESS : Messages.BUILD_PART_IN_PROGRESS)(name)
474
+ const args = [this.commands.install, this.commands.silentFlag];
475
+ const result = await this.withSpinner(
476
+ Messages.PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS,
477
+ async (spinner) => {
478
+ await this.exec(args, directory, { onFail: /* @__PURE__ */ __name(() => spinner.fail(), "onFail") });
479
+ this.logSuccess(Messages.PACKAGE_MANAGER_INSTALLATION_SUCCEED());
480
+ },
481
+ () => this.logFailure(this.formatFailCommand([this.commands.install]))
607
482
  );
608
- spinner.start();
609
- try {
610
- const commandArgs = [
611
- this.cli.build,
612
- this.cli.silentFlag,
613
- entry,
614
- "--outdir",
615
- output,
616
- ...flags ?? []
617
- ];
618
- const collect = true;
619
- await this.runner.run(
620
- commandArgs,
621
- collect,
622
- getCwd(directory),
623
- void 0,
624
- void 0,
625
- FAIL_SPINNER(spinner)
626
- );
627
- spinner.succeed();
628
- return true;
629
- } catch {
630
- const commandArgs = [this.cli.install];
631
- const commandToRun = this.runner.rawFullCommand(commandArgs);
632
- console.error(red2(Messages.BUILD_PART_FAILED(name, bold(commandToRun))));
633
- return false;
634
- }
483
+ return result.success;
484
+ }
485
+ async addProduction(directory, dependencies) {
486
+ return this.addDependencies(this.commands.saveFlag, directory, dependencies);
487
+ }
488
+ async addDevelopment(directory, dependencies) {
489
+ return this.addDependencies(this.commands.saveDevFlag, directory, dependencies);
490
+ }
491
+ async build(name, directory, entry, output, flags = [], watch3 = false) {
492
+ this.assertSupports("build");
493
+ const message = watch3 ? Messages.BUILD_PART_WATCH_IN_PROGRESS(name) : Messages.BUILD_PART_IN_PROGRESS(name);
494
+ const args = [
495
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
496
+ this.commands.build,
497
+ this.commands.silentFlag,
498
+ entry,
499
+ "--outdir",
500
+ output,
501
+ ...flags
502
+ ];
503
+ const result = await this.withSpinner(
504
+ message,
505
+ async (spinner) => {
506
+ await this.exec(args, directory, { onFail: /* @__PURE__ */ __name(() => spinner.fail(), "onFail") });
507
+ },
508
+ () => this.logBuildFailure(name)
509
+ );
510
+ return result.success;
635
511
  }
636
- async run(name, directory, file, env2 = {}, flags = [], silent = false) {
512
+ async run(name, directory, script, env2 = {}, flags = [], silent = false) {
513
+ console.info(Messages.START_PART_IN_PROGRESS(name));
637
514
  try {
638
- console.info(Messages.RUN_PART_IN_PROGRESS(name));
639
- const commandArgs = [...flags, this.cli.run];
640
- if (silent) commandArgs.push(this.cli.silentFlag);
641
- commandArgs.push(file);
642
- await this.runner.run(commandArgs, true, getCwd(directory), env2, {
643
- onStdout: this.onRunStdout(name),
644
- onStderr: this.onRunStderr(name)
515
+ const args = this.buildRunArgs(script, flags, silent);
516
+ await this.exec(args, directory, {
517
+ env: env2,
518
+ listeners: {
519
+ onStdout: createStdoutLogger(name),
520
+ onStderr: createStderrLogger(name)
521
+ }
645
522
  });
646
- console.info(Messages.RUN_PART_SUCCESS(name));
523
+ console.info(Messages.START_PART_SUCCESS(name));
647
524
  return true;
648
525
  } catch {
649
- console.error(red2(Messages.RUN_PART_FAILED(name)));
526
+ console.error(red3(Messages.START_PART_FAILED(name)));
650
527
  return false;
651
528
  }
652
529
  }
653
530
  async runDev(directory, command, env2 = {}, flags = [], collect = true) {
654
531
  try {
655
- const commandArgs = [this.cli.run, command, ...flags];
656
- await this.runner.run(commandArgs, collect, getCwd(directory), env2);
532
+ await this.exec([this.commands.run, command, ...flags], directory, { collect, env: env2 });
657
533
  return true;
658
534
  } catch {
659
535
  return false;
660
536
  }
661
537
  }
662
- async add(args, directory, dependencies) {
663
- if (!dependencies.length) {
664
- console.info();
665
- console.info(Messages.PACKAGE_MANAGER_INSTALLATION_NOTHING);
666
- console.info();
667
- return true;
668
- }
669
- const commandArguments = [...args, ...dependencies];
670
- const spinner = SPINNER(Messages.PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS);
538
+ async withSpinner(message, task, onError) {
539
+ const spinner = getSpinner(message);
671
540
  spinner.start();
672
541
  try {
673
- const collect = true;
674
- await this.runner.run(
675
- commandArguments,
676
- collect,
677
- getCwd(directory),
678
- void 0,
679
- void 0,
680
- FAIL_SPINNER(spinner)
681
- );
542
+ const value = await task(spinner);
682
543
  spinner.succeed();
683
- this.printInstallSuccess(dependencies);
684
- return true;
544
+ return { success: true, value };
685
545
  } catch {
686
546
  spinner.fail();
687
- const commandToRun = this.runner.rawFullCommand(commandArguments);
688
- this.printInstallFailure(commandToRun);
689
- return false;
547
+ if (onError) onError();
548
+ return { success: false };
690
549
  }
691
550
  }
692
- printInstallSuccess(dependencies) {
551
+ async addDependencies(saveFlag, directory, dependencies) {
552
+ if (!dependencies.length) {
553
+ this.logEmpty(Messages.PACKAGE_MANAGER_INSTALLATION_NOTHING);
554
+ return true;
555
+ }
556
+ const args = [this.commands.add, saveFlag, ...dependencies];
557
+ const result = await this.withSpinner(
558
+ Messages.PACKAGE_MANAGER_INSTALLATION_IN_PROGRESS,
559
+ async (spinner) => {
560
+ await this.exec(args, directory, { onFail: /* @__PURE__ */ __name(() => spinner.fail(), "onFail") });
561
+ this.logSuccess(Messages.PACKAGE_MANAGER_INSTALLATION_SUCCEED(dependencies));
562
+ },
563
+ () => this.logFailure(this.formatFailCommand(args))
564
+ );
565
+ return result.success;
566
+ }
567
+ assertSupports(feature) {
568
+ if (!this.commands[feature]) {
569
+ throw new Error(`Package manager "${this.name}" does not support "${feature}"`);
570
+ }
571
+ }
572
+ buildRunArgs(script, flags, silent) {
573
+ const args = [...flags, this.commands.run];
574
+ if (silent) args.push(this.commands.silentFlag);
575
+ args.push(script);
576
+ return args;
577
+ }
578
+ exec(args, directory, options = {}) {
579
+ return this.runner.run(args, {
580
+ collect: options.collect ?? true,
581
+ cwd: getCwd(directory),
582
+ env: options.env,
583
+ listeners: options.listeners,
584
+ onFail: options.onFail
585
+ });
586
+ }
587
+ formatFailCommand(args) {
588
+ return this.runner.fullCommand(args);
589
+ }
590
+ logSuccess(message) {
693
591
  console.info();
694
- console.info(Messages.PACKAGE_MANAGER_INSTALLATION_SUCCEED(dependencies));
592
+ console.info(message);
695
593
  console.info();
696
594
  }
697
- printInstallFailure(command) {
698
- console.error(red2(Messages.PACKAGE_MANAGER_INSTALLATION_FAILED(bold(command))));
595
+ logFailure(command) {
596
+ console.error(red3(Messages.PACKAGE_MANAGER_INSTALLATION_FAILED(bold(command))));
597
+ }
598
+ logBuildFailure(name) {
599
+ const command = this.formatFailCommand([this.commands.install]);
600
+ console.error(red3(Messages.BUILD_PART_FAILED(name, bold(command))));
601
+ }
602
+ logEmpty(message) {
603
+ console.info();
604
+ console.info(message);
605
+ console.info();
699
606
  }
700
- onRunStdout = /* @__PURE__ */ __name((name) => (chunk) => {
701
- chunk.toString().replace(/\r\n|\n/g, "\n").replace(/^\n+|\n+$/g, "").split("\n").forEach((line) => {
702
- const date = yellow2(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
703
- const prompt = green2(`(${name}) INFO -`);
704
- console.info(`${date} ${prompt} ${line}`);
705
- });
706
- }, "onRunStdout");
707
- onRunStderr = /* @__PURE__ */ __name((name) => (chunk) => {
708
- chunk.toString().replace(/\r\n|\n/g, "\n").replace(/^\n+|\n+$/g, "").split("\n").forEach((line) => {
709
- const date = yellow2(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
710
- const prompt = red2(`(${name}) ERROR -`);
711
- console.error(`${date} ${prompt} ${line}`);
712
- });
713
- }, "onRunStderr");
714
607
  };
715
608
 
716
- // src/lib/package-manager/package-managers/bun.package-manager.ts
717
- var BunPackageManager = class extends AbstractPackageManager {
609
+ // src/lib/package-manager/package-manager.factory.ts
610
+ import fs2 from "fs";
611
+ import { resolve as resolve2 } from "path";
612
+
613
+ // src/lib/runner/runner.ts
614
+ import { red as red4 } from "ansis";
615
+ import { spawn } from "child_process";
616
+ import * as process2 from "process";
617
+ var Runner = class {
618
+ constructor(binary, baseArgs = []) {
619
+ this.binary = binary;
620
+ this.baseArgs = baseArgs;
621
+ }
718
622
  static {
719
- __name(this, "BunPackageManager");
623
+ __name(this, "Runner");
720
624
  }
721
- constructor() {
722
- super(RunnerFactory.create(0 /* BUN */));
625
+ async run(args, options = {}) {
626
+ const { collect = false, cwd: cwd2 = process2.cwd(), env: env2, listeners, onFail } = options;
627
+ const spawnOpts = this.buildSpawnOptions(collect, cwd2, env2);
628
+ const fullArgs = [...this.baseArgs, ...args];
629
+ return new Promise((resolve3, reject) => {
630
+ const child = spawn(`${this.binary} ${fullArgs.join(" ")}`, spawnOpts);
631
+ const output = this.attachOutputHandlers(child, listeners);
632
+ child.on("close", (code) => {
633
+ if (code === 0) {
634
+ resolve3(this.formatOutput(output, collect));
635
+ } else {
636
+ this.handleFailure(output, fullArgs, onFail);
637
+ reject(this.createError(fullArgs, code));
638
+ }
639
+ });
640
+ });
723
641
  }
724
- get name() {
725
- return "bun" /* BUN */.toUpperCase();
642
+ fullCommand(args) {
643
+ return [this.binary, ...this.baseArgs, ...args].join(" ");
726
644
  }
727
- get cli() {
645
+ buildSpawnOptions(collect, cwd2, env2) {
728
646
  return {
729
- install: "install",
730
- add: "add",
731
- update: "update",
732
- remove: "remove",
733
- run: "run",
734
- saveFlag: "--save",
735
- saveDevFlag: "--dev",
736
- silentFlag: "--silent"
647
+ cwd: cwd2,
648
+ stdio: collect ? "pipe" : "inherit",
649
+ shell: true,
650
+ env: { ...process2.env, ...env2 }
737
651
  };
738
652
  }
653
+ attachOutputHandlers(child, listeners) {
654
+ const output = [];
655
+ const defaultHandler = /* @__PURE__ */ __name((data) => output.push(data.toString().replace(/\r\n|\n/, "")), "defaultHandler");
656
+ child.stdout?.on("data", listeners?.onStdout ?? defaultHandler);
657
+ child.stderr?.on("data", listeners?.onStderr ?? defaultHandler);
658
+ return output;
659
+ }
660
+ formatOutput(output, collect) {
661
+ return collect && output.length ? output.join("\n") : null;
662
+ }
663
+ handleFailure(output, args, onFail) {
664
+ if (onFail) onFail();
665
+ this.logFailedCommand(args);
666
+ this.logCapturedOutput(output);
667
+ }
668
+ logFailedCommand(args) {
669
+ console.error(red4(`
670
+ Failed to execute command: ${this.binary} ${args.join(" ")}`));
671
+ }
672
+ logCapturedOutput(output) {
673
+ if (output.length) {
674
+ console.error();
675
+ console.error(output.join("\n"));
676
+ console.error();
677
+ }
678
+ }
679
+ createError(args, code) {
680
+ return new Error(`Command "${this.binary} ${args.join(" ")}" exited with code ${code}`);
681
+ }
739
682
  };
740
683
 
741
- // src/lib/package-manager/package-managers/local-bun.package-manager.ts
742
- var LocalBunPackageManager = class extends AbstractPackageManager {
684
+ // src/lib/runner/runner.factory.ts
685
+ var RunnerFactory = class {
743
686
  static {
744
- __name(this, "LocalBunPackageManager");
687
+ __name(this, "RunnerFactory");
745
688
  }
746
- constructor() {
747
- super(RunnerFactory.create(1 /* LOCAL_BUN */));
689
+ static create(binary, args) {
690
+ return new Runner(binary, args);
748
691
  }
749
- get name() {
750
- return "local_bun" /* LOCAL_BUN */.toUpperCase();
692
+ static createLocal(binary, args) {
693
+ return new Runner(resolveCLINodeBinaryPath(binary), args);
751
694
  }
752
- get cli() {
753
- return {
695
+ static createSchematic() {
696
+ const binaryPath = this.resolveSchematicBinary();
697
+ return new Runner("node", [`"${binaryPath}"`]);
698
+ }
699
+ static resolveSchematicBinary() {
700
+ try {
701
+ return getModulePath("@angular-devkit/schematics-cli/bin/schematics.js");
702
+ } catch {
703
+ throw new Error("'schematics' binary path could not be found!");
704
+ }
705
+ }
706
+ };
707
+
708
+ // src/lib/package-manager/package-manager-configs.ts
709
+ var PM_CONFIGS = {
710
+ ["bun" /* BUN */]: {
711
+ binary: "bun",
712
+ commands: {
713
+ install: "install",
714
+ add: "add",
715
+ update: "update",
716
+ remove: "remove",
717
+ exec: "exec",
718
+ run: "run",
719
+ saveFlag: "--save",
720
+ saveDevFlag: "--dev",
721
+ silentFlag: "--silent"
722
+ }
723
+ },
724
+ ["local_bun" /* LOCAL_BUN */]: {
725
+ binary: "bun",
726
+ commands: {
754
727
  install: "install",
755
728
  add: "add",
756
729
  update: "update",
757
730
  remove: "remove",
731
+ exec: "exec",
758
732
  run: "run",
759
733
  build: "build",
760
734
  runFile: "run",
761
735
  saveFlag: "--save",
762
736
  saveDevFlag: "--dev",
763
737
  silentFlag: "--silent"
764
- };
765
- }
766
- };
767
-
768
- // src/lib/package-manager/package-managers/npm.package-manager.ts
769
- var NpmPackageManager = class extends AbstractPackageManager {
770
- static {
771
- __name(this, "NpmPackageManager");
772
- }
773
- constructor() {
774
- super(RunnerFactory.create(2 /* NPM */));
775
- }
776
- get name() {
777
- return "npm" /* NPM */.toUpperCase();
778
- }
779
- get cli() {
780
- return {
738
+ }
739
+ },
740
+ ["npm" /* NPM */]: {
741
+ binary: "npm",
742
+ commands: {
781
743
  install: "install",
782
744
  add: "install",
783
745
  update: "update",
784
746
  remove: "uninstall",
747
+ exec: "exec",
785
748
  run: "run",
786
749
  saveFlag: "--save",
787
750
  saveDevFlag: "--save-dev",
788
751
  silentFlag: "--silent"
789
- };
790
- }
791
- };
792
-
793
- // src/lib/package-manager/package-managers/pnpm.package-manager.ts
794
- var PnpmPackageManager = class extends AbstractPackageManager {
795
- static {
796
- __name(this, "PnpmPackageManager");
797
- }
798
- constructor() {
799
- super(RunnerFactory.create(3 /* PNPM */));
800
- }
801
- get name() {
802
- return "pnpm" /* PNPM */.toUpperCase();
803
- }
804
- get cli() {
805
- return {
752
+ }
753
+ },
754
+ ["pnpm" /* PNPM */]: {
755
+ binary: "pnpm",
756
+ commands: {
806
757
  install: "install",
807
758
  add: "add",
808
759
  update: "update",
809
760
  remove: "remove",
761
+ exec: "exec",
810
762
  run: "run",
811
763
  saveFlag: "-P",
812
764
  saveDevFlag: "-D",
813
765
  silentFlag: "--silent"
814
- };
815
- }
816
- };
817
-
818
- // src/lib/package-manager/package-managers/yarn.package-manager.ts
819
- var YarnPackageManager = class extends AbstractPackageManager {
820
- static {
821
- __name(this, "YarnPackageManager");
822
- }
823
- constructor() {
824
- super(RunnerFactory.create(5 /* YARN */));
825
- }
826
- get name() {
827
- return "yarn" /* YARN */.toUpperCase();
828
- }
829
- get cli() {
830
- return {
766
+ }
767
+ },
768
+ ["yarn" /* YARN */]: {
769
+ binary: "yarn",
770
+ commands: {
831
771
  install: "install",
832
772
  add: "add",
833
773
  update: "update",
834
774
  remove: "remove",
775
+ exec: "exec",
835
776
  run: "run",
836
777
  saveFlag: "",
837
778
  saveDevFlag: "-D",
838
779
  silentFlag: "--silent"
839
- };
780
+ }
840
781
  }
841
782
  };
842
783
 
843
784
  // src/lib/package-manager/package-manager.factory.ts
785
+ var LOCK_FILE_MAP = {
786
+ "bun.lock": "bun" /* BUN */,
787
+ "package-lock.json": "npm" /* NPM */,
788
+ "pnpm-lock.yaml": "pnpm" /* PNPM */,
789
+ "yarn.lock": "yarn" /* YARN */
790
+ };
844
791
  var PackageManagerFactory = class {
845
792
  static {
846
793
  __name(this, "PackageManagerFactory");
847
794
  }
848
795
  static create(name) {
849
- switch (name) {
850
- case "bun" /* BUN */:
851
- return new BunPackageManager();
852
- case "local_bun" /* LOCAL_BUN */:
853
- return new LocalBunPackageManager();
854
- case "npm" /* NPM */:
855
- return new NpmPackageManager();
856
- case "pnpm" /* PNPM */:
857
- return new PnpmPackageManager();
858
- case "yarn" /* YARN */:
859
- return new YarnPackageManager();
860
- default:
861
- throw new Error(`Package manager ${name} is not managed.`);
796
+ const config2 = PM_CONFIGS[name];
797
+ if (!config2) {
798
+ throw new Error(`Package manager ${name} is not managed.`);
862
799
  }
800
+ const runner = this.createRunner(name, config2.binary);
801
+ return new PackageManager(name, config2.commands, runner);
863
802
  }
864
803
  static async find(directory = ".") {
865
- const DEFAULT_PACKAGE_MANAGER = "npm" /* NPM */;
804
+ const detected = await this.detectFromLockFile(directory);
805
+ return this.create(detected);
806
+ }
807
+ static createRunner(name, binary) {
808
+ if (name === "local_bun" /* LOCAL_BUN */) {
809
+ return RunnerFactory.createLocal("bun");
810
+ }
811
+ return RunnerFactory.create(binary);
812
+ }
813
+ static async detectFromLockFile(directory) {
866
814
  try {
867
- const files = await fs.promises.readdir(resolve2(directory));
868
- if (files.includes("bun.lock")) {
869
- return this.create("bun" /* BUN */);
870
- }
871
- if (files.includes("package-lock.json")) {
872
- return this.create("npm" /* NPM */);
815
+ const files = await fs2.promises.readdir(resolve2(directory));
816
+ for (const [lockFile, pmName] of Object.entries(LOCK_FILE_MAP)) {
817
+ if (files.includes(lockFile)) return pmName;
873
818
  }
874
- if (files.includes("pnpm-lock.yaml")) {
875
- return this.create("pnpm" /* PNPM */);
876
- }
877
- if (files.includes("yarn.lock")) {
878
- return this.create("yarn" /* YARN */);
879
- }
880
- return this.create(DEFAULT_PACKAGE_MANAGER);
881
819
  } catch {
882
- return this.create(DEFAULT_PACKAGE_MANAGER);
883
820
  }
821
+ return "npm" /* NPM */;
884
822
  }
885
823
  };
886
824
 
825
+ // src/lib/utils/run-safe.ts
826
+ import { red as red5 } from "ansis";
827
+ var runSafe = /* @__PURE__ */ __name(async (fn, fallback) => {
828
+ try {
829
+ return await fn();
830
+ } catch (error) {
831
+ const msg = getErrorMessage(error);
832
+ if (msg) console.error(red5(msg));
833
+ return fallback;
834
+ }
835
+ }, "runSafe");
836
+
887
837
  // src/lib/config/config.type.ts
888
838
  import { Expose, Type } from "class-transformer";
889
839
  import { IsBoolean, IsEnum, IsNotEmpty, IsPort, IsString, ValidateNested } from "class-validator";
@@ -1007,7 +957,11 @@ __decorateClass([
1007
957
  import { plainToInstance } from "class-transformer";
1008
958
  import { validate } from "class-validator";
1009
959
  import { existsSync as existsSync2, readFileSync } from "fs";
1010
- import { join as join2 } from "path";
960
+ import { join as join3 } from "path";
961
+
962
+ // src/lib/constants.ts
963
+ var CONFIG_FILE_NAME = "nanoforge.config.json";
964
+ var NANOFORGE_DIR = ".nanoforge";
1011
965
 
1012
966
  // src/lib/utils/object.ts
1013
967
  var isObject = /* @__PURE__ */ __name((item) => {
@@ -1062,20 +1016,19 @@ var CONFIG_DEFAULTS = {
1062
1016
  var config;
1063
1017
  var getConfigPath = /* @__PURE__ */ __name((directory, name) => {
1064
1018
  if (name) {
1065
- return join2(directory, name);
1019
+ return join3(directory, name);
1066
1020
  } else {
1067
- for (const n of ["nanoforge.config.json"]) {
1068
- const path = join2(directory, n);
1021
+ for (const n of [CONFIG_FILE_NAME]) {
1022
+ const path = join3(directory, n);
1069
1023
  if (existsSync2(path)) return path;
1070
1024
  }
1071
- throw new Error(`Unsupported config: ${name}`);
1025
+ throw new Error(`No config file found in directory: ${directory}`);
1072
1026
  }
1073
1027
  }, "getConfigPath");
1074
1028
  var loadConfig = /* @__PURE__ */ __name(async (directory, name) => {
1075
1029
  if (config) return config;
1076
1030
  let rawData;
1077
1031
  const path = getConfigPath(directory, name);
1078
- if (!path) throw new Error("No config file found");
1079
1032
  try {
1080
1033
  rawData = deepMerge(CONFIG_DEFAULTS, JSON.parse(readFileSync(path, "utf-8")));
1081
1034
  } catch {
@@ -1103,6 +1056,32 @@ var AbstractAction = class {
1103
1056
  static {
1104
1057
  __name(this, "AbstractAction");
1105
1058
  }
1059
+ async run(args, options, extraFlags) {
1060
+ this.logStart();
1061
+ try {
1062
+ const result = await this.handle(args, options, extraFlags);
1063
+ this.resolveResult(result);
1064
+ } catch (error) {
1065
+ handleActionError(this.failureMessage, error);
1066
+ }
1067
+ }
1068
+ logStart() {
1069
+ console.info();
1070
+ console.info(`${Prefixes.INFO} ${this.startMessage}`);
1071
+ console.info();
1072
+ }
1073
+ resolveResult(result) {
1074
+ const success2 = result?.success !== false;
1075
+ const keepAlive = result?.keepAlive === true;
1076
+ if (keepAlive) return;
1077
+ console.info();
1078
+ if (!success2) {
1079
+ if (this.failureMessage) console.error(this.failureMessage);
1080
+ process.exit(1);
1081
+ }
1082
+ if (this.successMessage) console.info(this.successMessage);
1083
+ process.exit(0);
1084
+ }
1106
1085
  };
1107
1086
 
1108
1087
  // src/action/actions/build.action.ts
@@ -1110,119 +1089,122 @@ var BuildAction = class extends AbstractAction {
1110
1089
  static {
1111
1090
  __name(this, "BuildAction");
1112
1091
  }
1092
+ startMessage = Messages.BUILD_START;
1093
+ successMessage = Messages.BUILD_SUCCESS;
1094
+ failureMessage = Messages.BUILD_FAILED;
1113
1095
  async handle(_args, options) {
1114
- console2.info(Messages.BUILD_START);
1115
- console2.info();
1116
- try {
1117
- const directory = getDirectoryInput(options);
1118
- const config2 = await getConfig(options, directory);
1119
- const watch3 = getWatchInput(options);
1120
- const client = getPart(
1096
+ const directory = getDirectoryInput(options);
1097
+ const config2 = await getConfig(options, directory);
1098
+ const isWatch = getWatchInput(options);
1099
+ const targets = this.resolveTargets(config2, options);
1100
+ const results = await this.buildAll(targets, directory, isWatch);
1101
+ if (isWatch) {
1102
+ return this.enterWatchMode();
1103
+ }
1104
+ return { success: results.every(Boolean) };
1105
+ }
1106
+ resolveTargets(config2, options) {
1107
+ const targets = [
1108
+ this.createTarget(
1109
+ "Client",
1121
1110
  config2.client.build,
1122
- options.get("clientDirectory")?.value,
1123
- "client"
1124
- );
1125
- let res = await buildPart("Client", client, directory, { watch: watch3 });
1126
- if (config2.server.enable) {
1127
- const server = getPart(
1111
+ "browser",
1112
+ getStringInput(options, "clientDirectory")
1113
+ )
1114
+ ];
1115
+ if (config2.server.enable) {
1116
+ targets.push(
1117
+ this.createTarget(
1118
+ "Server",
1128
1119
  config2.server.build,
1129
- options.get("serverDirectory")?.value,
1130
- "server"
1131
- );
1132
- res = await buildPart("Server", server, directory, { watch: watch3 }) ? res : false;
1133
- }
1134
- console2.info();
1135
- if (watch3) {
1136
- console2.info(Messages.BUILD_WATCH_START);
1137
- console2.info();
1138
- return;
1139
- }
1140
- if (!res) console2.info(Messages.BUILD_FAILED);
1141
- else console2.info(Messages.BUILD_SUCCESS);
1142
- process.exit(0);
1143
- } catch (e) {
1144
- console2.error(e);
1145
- process.exit(1);
1120
+ "node",
1121
+ getStringInput(options, "serverDirectory")
1122
+ )
1123
+ );
1146
1124
  }
1125
+ return targets;
1147
1126
  }
1148
- };
1149
- var getPart = /* @__PURE__ */ __name((config2, directoryOption, target) => {
1150
- return {
1151
- entry: config2.entryFile,
1152
- output: directoryOption || config2.outDir,
1153
- target
1154
- };
1155
- }, "getPart");
1156
- var buildPart = /* @__PURE__ */ __name(async (name, part, directory, options) => {
1157
- const packageManagerName = "local_bun" /* LOCAL_BUN */;
1158
- const packageManager = PackageManagerFactory.create(packageManagerName);
1159
- const build = /* @__PURE__ */ __name(async (watch3 = false) => {
1160
- try {
1161
- return await packageManager.build(
1162
- name,
1127
+ createTarget(name, config2, platform, outDirOverride) {
1128
+ return {
1129
+ name,
1130
+ entry: config2.entryFile,
1131
+ output: outDirOverride || config2.outDir,
1132
+ platform
1133
+ };
1134
+ }
1135
+ async buildAll(targets, directory, isWatch) {
1136
+ const results = [];
1137
+ for (const target of targets) {
1138
+ const result = await this.buildTarget(target, directory, isWatch);
1139
+ results.push(result);
1140
+ }
1141
+ return results;
1142
+ }
1143
+ async buildTarget(target, directory, isWatch) {
1144
+ const packageManager = PackageManagerFactory.create("local_bun" /* LOCAL_BUN */);
1145
+ const executeBuild = /* @__PURE__ */ __name((rebuild = false) => runSafe(
1146
+ () => packageManager.build(
1147
+ target.name,
1163
1148
  directory,
1164
- part.entry,
1165
- part.output,
1166
- [
1167
- "--asset-naming",
1168
- "[name].[ext]",
1169
- "--target",
1170
- part.target === "client" ? "browser" : "node"
1171
- ],
1172
- watch3
1173
- );
1174
- } catch (error4) {
1175
- if (error4 && error4.message) {
1176
- console2.error(ansis.red(error4.message));
1177
- }
1178
- return false;
1149
+ target.entry,
1150
+ target.output,
1151
+ ["--asset-naming", "[name].[ext]", "--target", target.platform],
1152
+ rebuild
1153
+ ),
1154
+ false
1155
+ ), "executeBuild");
1156
+ if (isWatch) {
1157
+ this.watchDirectory(directory, target.entry, () => executeBuild(true));
1179
1158
  }
1180
- }, "build");
1181
- if (options?.watch)
1182
- watch(dirname(join3(getCwd(directory), part.entry))).on("change", () => build(true));
1183
- return await build();
1184
- }, "buildPart");
1159
+ const result = await executeBuild();
1160
+ return result !== false;
1161
+ }
1162
+ watchDirectory(directory, entry, onChange) {
1163
+ const watchPath = dirname(join4(getCwd(directory), entry));
1164
+ watch(watchPath).on("change", onChange);
1165
+ }
1166
+ enterWatchMode() {
1167
+ console.info();
1168
+ console.info(Messages.BUILD_WATCH_START);
1169
+ console.info();
1170
+ return { keepAlive: true };
1171
+ }
1172
+ };
1185
1173
 
1186
1174
  // src/action/actions/dev.action.ts
1187
- import * as ansis2 from "ansis";
1188
1175
  var DevAction = class extends AbstractAction {
1189
1176
  static {
1190
1177
  __name(this, "DevAction");
1191
1178
  }
1179
+ startMessage = Messages.DEV_START;
1180
+ successMessage = Messages.DEV_SUCCESS;
1181
+ failureMessage = Messages.DEV_FAILED;
1192
1182
  async handle(_args, options) {
1193
- console.info(Messages.DEV_START);
1194
- console.info();
1195
- try {
1196
- const directory = getDirectoryInput(options);
1197
- const generate = getDevGenerateInput(options);
1198
- await Promise.all([
1199
- generate ? runAction("generate", [], directory, false) : void 0,
1200
- runAction("build", [], directory, false),
1201
- runAction("start", [], directory, true)
1202
- ]);
1203
- console.info(Messages.DEV_SUCCESS);
1204
- process.exit(0);
1205
- } catch (e) {
1206
- console.error(Messages.DEV_FAILED);
1207
- console.error(e);
1208
- process.exit(1);
1183
+ const directory = getDirectoryInput(options);
1184
+ const generate = getDevGenerateInput(options);
1185
+ const tasks = this.buildTaskList(directory, generate);
1186
+ await Promise.all(tasks);
1187
+ return { keepAlive: true };
1188
+ }
1189
+ buildTaskList(directory, generate) {
1190
+ const tasks = [];
1191
+ if (generate) {
1192
+ tasks.push(this.runSubCommand("generate", directory, { silent: true }));
1209
1193
  }
1194
+ tasks.push(this.runSubCommand("build", directory, { silent: true }));
1195
+ tasks.push(this.runSubCommand("start", directory, { silent: false }));
1196
+ return tasks;
1197
+ }
1198
+ async runSubCommand(command, directory, options) {
1199
+ await runSafe(async () => {
1200
+ const packageManager = await PackageManagerFactory.find(directory);
1201
+ await packageManager.runDev(directory, "nf", {}, [command, "--watch"], options.silent);
1202
+ });
1210
1203
  }
1211
1204
  };
1212
- var runAction = /* @__PURE__ */ __name(async (command, params, directory, stdout = false) => {
1213
- try {
1214
- const packageManager = await PackageManagerFactory.find(directory);
1215
- await packageManager.runDev(directory, "nf", {}, [command, ...params, "--watch"], !stdout);
1216
- } catch (error4) {
1217
- if (error4 && error4.message) {
1218
- console.error(ansis2.red(error4.message));
1219
- }
1220
- }
1221
- }, "runAction");
1222
1205
 
1223
1206
  // src/action/actions/generate.action.ts
1224
- import * as console3 from "console";
1225
- import { join as join4 } from "path";
1207
+ import { join as join5 } from "path";
1226
1208
 
1227
1209
  // src/lib/schematics/abstract.collection.ts
1228
1210
  var AbstractCollection = class {
@@ -1234,25 +1216,19 @@ var AbstractCollection = class {
1234
1216
  static {
1235
1217
  __name(this, "AbstractCollection");
1236
1218
  }
1237
- async execute(name, options, flags, failSpinner) {
1219
+ async execute(name, options, flags, onFail) {
1238
1220
  const command = this.buildCommandLine(name, options, flags);
1239
- await this.runner.run(
1240
- command,
1241
- true,
1242
- this.cwd ? getCwd(this.cwd) : void 0,
1243
- void 0,
1244
- void 0,
1245
- failSpinner
1246
- );
1221
+ await this.runner.run(command, {
1222
+ collect: true,
1223
+ cwd: this.cwd ? getCwd(this.cwd) : void 0,
1224
+ onFail
1225
+ });
1247
1226
  }
1248
1227
  buildCommandLine(name, options, flags = []) {
1249
- return [`${this.collection}:${name}`, ...flags, ...this.buildOptions(options)];
1228
+ return [`${this.collection}:${name}`, ...flags, ...this.serializeOptions(options)];
1250
1229
  }
1251
- buildOptions(options) {
1252
- return options.reduce(
1253
- (old, option) => [...old, ...option.toCommandString()],
1254
- []
1255
- );
1230
+ serializeOptions(options) {
1231
+ return options.flatMap((option) => option.toCommandString());
1256
1232
  }
1257
1233
  };
1258
1234
 
@@ -1281,6 +1257,11 @@ var NanoforgeCollection = class _NanoforgeCollection extends AbstractCollection
1281
1257
  name: "part-main",
1282
1258
  alias: "main",
1283
1259
  description: "Generate a NanoForge Part Main file"
1260
+ },
1261
+ {
1262
+ name: "docker",
1263
+ alias: "docker",
1264
+ description: "Generate a Dockerfile for the application"
1284
1265
  }
1285
1266
  ];
1286
1267
  constructor(runner, cwd2) {
@@ -1312,12 +1293,11 @@ var CollectionFactory = class {
1312
1293
  __name(this, "CollectionFactory");
1313
1294
  }
1314
1295
  static create(collection, directory) {
1315
- const schematicRunner = RunnerFactory.create(4 /* SCHEMATIC */);
1296
+ const schematicRunner = RunnerFactory.createSchematic();
1316
1297
  if (collection === "@nanoforge-dev/schematics" /* NANOFORGE */) {
1317
1298
  return new NanoforgeCollection(schematicRunner, directory);
1318
- } else {
1319
- return new NanoforgeCollection(schematicRunner, directory);
1320
1299
  }
1300
+ throw new Error(`Unknown collection: ${collection}`);
1321
1301
  }
1322
1302
  };
1323
1303
 
@@ -1350,7 +1330,7 @@ var SchematicOption = class {
1350
1330
  }
1351
1331
  } else if (typeof this.value === "boolean") {
1352
1332
  const str = normalizedName;
1353
- return this.value ? [`--${str}`] : [`--no-${str}`];
1333
+ return this.value ? [`--${str}=true`] : [`--${str}=false`];
1354
1334
  } else if (Array.isArray(this.value)) {
1355
1335
  return this.value.reduce(
1356
1336
  (old, option) => [
@@ -1375,19 +1355,10 @@ var SchematicOption = class {
1375
1355
 
1376
1356
  // src/action/common/schematics.ts
1377
1357
  import { watch as watch2 } from "chokidar";
1378
-
1379
- // src/action/common/spinner.ts
1380
- import ora2 from "ora";
1381
- var getSpinner = /* @__PURE__ */ __name((message) => ora2({
1382
- text: message
1383
- }), "getSpinner");
1384
-
1385
- // src/action/common/schematics.ts
1386
1358
  var executeSchematic = /* @__PURE__ */ __name(async (name, collection, schematicName, options, fileToWatch) => {
1387
- const execute = /* @__PURE__ */ __name(async (watch3 = false) => {
1388
- const spinner = getSpinner(
1389
- (watch3 ? Messages.SCHEMATIC_WATCH_IN_PROGRESS : Messages.SCHEMATIC_IN_PROGRESS)(name)
1390
- );
1359
+ const execute = /* @__PURE__ */ __name(async (isRebuild = false) => {
1360
+ const message = isRebuild ? Messages.SCHEMATIC_WATCH_IN_PROGRESS(name) : Messages.SCHEMATIC_IN_PROGRESS(name);
1361
+ const spinner = getSpinner(message);
1391
1362
  spinner.start();
1392
1363
  await collection.execute(
1393
1364
  schematicName,
@@ -1397,16 +1368,17 @@ var executeSchematic = /* @__PURE__ */ __name(async (name, collection, schematic
1397
1368
  );
1398
1369
  spinner.succeed(Messages.SCHEMATIC_SUCCESS(name));
1399
1370
  }, "execute");
1400
- if (fileToWatch) watch2(fileToWatch).on("change", () => execute(true));
1401
- return await execute();
1371
+ if (fileToWatch) {
1372
+ watch2(fileToWatch).on("change", () => execute(true));
1373
+ }
1374
+ await execute();
1402
1375
  }, "executeSchematic");
1403
1376
  var mapSchematicOptions = /* @__PURE__ */ __name((inputs) => {
1404
- return Object.entries(inputs).reduce((old, [key, value]) => {
1405
- if (value === void 0) return old;
1406
- return [
1407
- ...old,
1408
- new SchematicOption(key, typeof value === "object" ? mapSchematicOptions(value) : value)
1409
- ];
1377
+ return Object.entries(inputs).reduce((acc, [key, value]) => {
1378
+ if (value === void 0) return acc;
1379
+ const mapped = typeof value === "object" ? new SchematicOption(key, mapSchematicOptions(value)) : new SchematicOption(key, value);
1380
+ acc.push(mapped);
1381
+ return acc;
1410
1382
  }, []);
1411
1383
  }, "mapSchematicOptions");
1412
1384
 
@@ -1415,108 +1387,98 @@ var GenerateAction = class extends AbstractAction {
1415
1387
  static {
1416
1388
  __name(this, "GenerateAction");
1417
1389
  }
1390
+ startMessage = Messages.GENERATE_START;
1391
+ successMessage = Messages.GENERATE_SUCCESS;
1392
+ failureMessage = Messages.GENERATE_FAILED;
1418
1393
  async handle(_args, options) {
1419
- console3.info(Messages.GENERATE_START);
1420
- console3.info();
1421
- try {
1422
- const directory = getDirectoryInput(options);
1423
- const config2 = await getConfig(options, directory);
1424
- const watch3 = getWatchInput(options);
1425
- const values = await getSchemaValues(config2);
1426
- await generateFiles(values, directory, watch3);
1427
- console3.info();
1428
- if (watch3) {
1429
- console3.info(Messages.GENERATE_WATCH_START);
1430
- console3.info();
1431
- return;
1432
- }
1433
- console3.info(Messages.GENERATE_SUCCESS);
1434
- process.exit(0);
1435
- } catch (e) {
1436
- console3.error(Messages.GENERATE_FAILED);
1437
- console3.error(e);
1438
- process.exit(1);
1394
+ const directory = getDirectoryInput(options);
1395
+ const config2 = await getConfig(options, directory);
1396
+ const isWatch = getWatchInput(options);
1397
+ const values = this.extractValues(config2);
1398
+ await this.generateParts(values, directory, isWatch);
1399
+ if (isWatch) {
1400
+ return this.enterWatchMode();
1439
1401
  }
1402
+ return {};
1440
1403
  }
1441
- };
1442
- var getSchemaValues = /* @__PURE__ */ __name(async (config2) => {
1443
- return {
1444
- name: config2.name,
1445
- directory: ".",
1446
- language: config2.language,
1447
- server: config2.server.enable,
1448
- initFunctions: config2.initFunctions
1449
- };
1450
- }, "getSchemaValues");
1451
- var generateFiles = /* @__PURE__ */ __name(async (values, directory, watch3) => {
1452
- const collection = CollectionFactory.create("@nanoforge-dev/schematics" /* NANOFORGE */, directory);
1453
- console3.info(Messages.SCHEMATICS_START);
1454
- console3.info();
1455
- await executeSchematic(
1456
- "Client main file",
1457
- collection,
1458
- "part-main",
1459
- {
1460
- name: values.name,
1461
- part: "client",
1462
- directory: values.directory,
1463
- language: values.language,
1464
- initFunctions: values.initFunctions
1465
- },
1466
- watch3 ? join4(getCwd(directory), values.directory, ".nanoforge", "client.save.json") : void 0
1467
- );
1468
- if (values.server) {
1404
+ extractValues(config2) {
1405
+ return {
1406
+ name: config2.name,
1407
+ directory: ".",
1408
+ language: config2.language,
1409
+ server: config2.server.enable,
1410
+ initFunctions: config2.initFunctions
1411
+ };
1412
+ }
1413
+ async generateParts(values, directory, watch3) {
1414
+ const collection = CollectionFactory.create("@nanoforge-dev/schematics" /* NANOFORGE */, directory);
1415
+ const baseOptions = this.baseSchematicOptions(values);
1469
1416
  await executeSchematic(
1470
- "Server main file",
1417
+ "Client main file",
1471
1418
  collection,
1472
1419
  "part-main",
1473
- {
1474
- name: values.name,
1475
- part: "server",
1476
- directory: values.directory,
1477
- language: values.language,
1478
- initFunctions: values.initFunctions
1479
- },
1480
- join4(getCwd(directory), values.directory, ".nanoforge", "server.save.json")
1420
+ { ...baseOptions, part: "client" },
1421
+ watch3 ? this.watchPath(directory, values.directory, "client") : void 0
1481
1422
  );
1423
+ if (values.server) {
1424
+ await executeSchematic(
1425
+ "Server main file",
1426
+ collection,
1427
+ "part-main",
1428
+ { ...baseOptions, part: "server" },
1429
+ this.watchPath(directory, values.directory, "server")
1430
+ );
1431
+ }
1482
1432
  }
1483
- }, "generateFiles");
1433
+ baseSchematicOptions(values) {
1434
+ return {
1435
+ name: values.name,
1436
+ directory: values.directory,
1437
+ language: values.language,
1438
+ initFunctions: values.initFunctions
1439
+ };
1440
+ }
1441
+ watchPath(directory, subDir, part) {
1442
+ return join5(getCwd(directory), subDir, NANOFORGE_DIR, `${part}.save.json`);
1443
+ }
1444
+ enterWatchMode() {
1445
+ console.info();
1446
+ console.info(Messages.GENERATE_WATCH_START);
1447
+ console.info();
1448
+ return { keepAlive: true };
1449
+ }
1450
+ };
1484
1451
 
1485
1452
  // src/action/actions/install.action.ts
1486
- import * as ansis3 from "ansis";
1487
- import * as process3 from "process";
1488
1453
  var InstallAction = class extends AbstractAction {
1489
1454
  static {
1490
1455
  __name(this, "InstallAction");
1491
1456
  }
1457
+ startMessage = Messages.INSTALL_START;
1458
+ successMessage = Messages.INSTALL_SUCCESS;
1459
+ failureMessage = Messages.INSTALL_FAILED;
1492
1460
  async handle(args, options) {
1493
- console.info(Messages.INSTALL_START);
1494
- console.info();
1495
- try {
1496
- const names = await getInstallNamesInputOrAsk(args);
1497
- const directory = getDirectoryInput(options);
1498
- await installPackages(names, directory);
1499
- process3.exit(0);
1500
- } catch (e) {
1501
- console.error(e);
1502
- process3.exit(1);
1503
- }
1504
- }
1505
- };
1506
- var installPackages = /* @__PURE__ */ __name(async (names, directory) => {
1507
- try {
1461
+ const names = await getInstallNamesInputOrAsk(args);
1462
+ const directory = getDirectoryInput(options);
1508
1463
  const packageManager = await PackageManagerFactory.find(directory);
1509
- await packageManager.addProduction(directory, names);
1510
- } catch (error4) {
1511
- if (error4 && error4.message) {
1512
- console.error(ansis3.red(error4.message));
1513
- }
1464
+ const success2 = await packageManager.addProduction(directory, names);
1465
+ return { success: success2 };
1514
1466
  }
1515
- }, "installPackages");
1467
+ };
1516
1468
 
1517
1469
  // src/action/actions/new.action.ts
1518
- import * as ansis4 from "ansis";
1519
- import console4 from "console";
1470
+ import { join as join6 } from "path";
1471
+
1472
+ // src/lib/input/inputs/new/docker.input.ts
1473
+ var getDockerInput = /* @__PURE__ */ __name((inputs) => {
1474
+ return getBooleanInput(inputs, "docker");
1475
+ }, "getDockerInput");
1476
+ var getDockerOrAsk = /* @__PURE__ */ __name((inputs) => {
1477
+ return getInputOrAsk(
1478
+ getDockerInput(inputs),
1479
+ () => askConfirm(Messages.NEW_DOCKER_QUESTION, { default: true })
1480
+ );
1481
+ }, "getDockerOrAsk");
1520
1482
 
1521
1483
  // src/lib/input/inputs/new/init-functions.input.ts
1522
1484
  var getNewInitFunctionsWithDefault = /* @__PURE__ */ __name((inputs) => {
@@ -1610,159 +1572,186 @@ var NewAction = class extends AbstractAction {
1610
1572
  static {
1611
1573
  __name(this, "NewAction");
1612
1574
  }
1575
+ startMessage = Messages.NEW_START;
1576
+ successMessage = Messages.NEW_SUCCESS;
1577
+ failureMessage = Messages.NEW_FAILED;
1613
1578
  async handle(_args, options) {
1614
- console4.info(Messages.NEW_START);
1615
- try {
1616
- const directory = getDirectoryInput(options);
1617
- const values = await getSchemaValues2(options);
1618
- await generateApplicationFiles(values, directory);
1619
- if (!values.skipInstall) await runInstall(directory, values.packageManager);
1620
- console4.info();
1621
- console4.info(Messages.NEW_SUCCESS);
1622
- process.exit(0);
1623
- } catch {
1624
- console4.error(Messages.NEW_FAILED);
1625
- process.exit(1);
1579
+ const directory = getDirectoryInput(options);
1580
+ const values = await this.collectValues(options);
1581
+ await this.scaffold(values, directory);
1582
+ let res = true;
1583
+ if (!values.skipInstall) {
1584
+ res = await this.installDependencies(values.packageManager, join6(directory, values.name));
1626
1585
  }
1586
+ return { success: res };
1627
1587
  }
1628
- };
1629
- var getSchemaValues2 = /* @__PURE__ */ __name(async (inputs) => {
1630
- return {
1631
- name: await getNewNameInputOrAsk(inputs),
1632
- directory: getNewPathInput(inputs),
1633
- packageManager: await getNewPackageManagerInputOrAsk(inputs),
1634
- language: await getNewLanguageInputOrAsk(inputs),
1635
- strict: await getNewStrictOrAsk(inputs),
1636
- server: await getNewServerOrAsk(inputs),
1637
- initFunctions: getNewInitFunctionsWithDefault(inputs),
1638
- skipInstall: await getNewSkipInstallOrAsk(inputs)
1639
- };
1640
- }, "getSchemaValues");
1641
- var generateApplicationFiles = /* @__PURE__ */ __name(async (values, directory) => {
1642
- console4.info();
1643
- const collection = CollectionFactory.create("@nanoforge-dev/schematics" /* NANOFORGE */, directory);
1644
- console4.info();
1645
- console4.info(Messages.SCHEMATICS_START);
1646
- console4.info();
1647
- await executeSchematic("Application", collection, "application", {
1648
- name: values.name,
1649
- directory: values.directory,
1650
- packageManager: values.packageManager,
1651
- language: values.language,
1652
- strict: values.strict,
1653
- server: values.server
1654
- });
1655
- await executeSchematic("Configuration", collection, "configuration", {
1656
- name: values.name,
1657
- directory: values.directory,
1658
- server: values.server
1659
- });
1660
- await executeSchematic("Base Client", collection, "part-base", {
1661
- name: values.name,
1662
- part: "client",
1663
- directory: values.directory,
1664
- language: values.language,
1665
- initFunctions: values.initFunctions
1666
- });
1667
- await executeSchematic("Client main file", collection, "part-main", {
1668
- name: values.name,
1669
- part: "client",
1670
- directory: values.directory,
1671
- language: values.language,
1672
- initFunctions: values.initFunctions
1673
- });
1674
- if (values.server) {
1675
- await executeSchematic("Base server", collection, "part-base", {
1588
+ async collectValues(inputs) {
1589
+ return {
1590
+ name: await getNewNameInputOrAsk(inputs),
1591
+ directory: getNewPathInput(inputs),
1592
+ packageManager: await getNewPackageManagerInputOrAsk(inputs),
1593
+ language: await getNewLanguageInputOrAsk(inputs),
1594
+ strict: await getNewStrictOrAsk(inputs),
1595
+ server: await getNewServerOrAsk(inputs),
1596
+ initFunctions: getNewInitFunctionsWithDefault(inputs),
1597
+ skipInstall: await getNewSkipInstallOrAsk(inputs),
1598
+ docker: await getDockerOrAsk(inputs)
1599
+ };
1600
+ }
1601
+ async scaffold(values, directory) {
1602
+ const collection = CollectionFactory.create("@nanoforge-dev/schematics" /* NANOFORGE */, directory);
1603
+ console.info(Messages.SCHEMATICS_START);
1604
+ console.info();
1605
+ await this.generateApplication(collection, values);
1606
+ await this.generateConfiguration(collection, values);
1607
+ await this.generateClientParts(collection, values);
1608
+ await this.generateDocker(collection, values);
1609
+ if (values.server) {
1610
+ await this.generateServerParts(collection, values);
1611
+ }
1612
+ }
1613
+ generateApplication(collection, values) {
1614
+ return executeSchematic("Application", collection, "application", {
1676
1615
  name: values.name,
1677
- part: "server",
1678
1616
  directory: values.directory,
1617
+ packageManager: values.packageManager,
1679
1618
  language: values.language,
1680
- initFunctions: values.initFunctions
1619
+ strict: values.strict,
1620
+ server: values.server
1621
+ });
1622
+ }
1623
+ generateConfiguration(collection, values) {
1624
+ return executeSchematic("Configuration", collection, "configuration", {
1625
+ name: values.name,
1626
+ directory: values.directory,
1627
+ server: values.server
1628
+ });
1629
+ }
1630
+ async generateClientParts(collection, values) {
1631
+ const partOptions = this.partOptions(values, "client");
1632
+ await executeSchematic("Client base", collection, "part-base", {
1633
+ ...partOptions,
1634
+ server: values.server
1635
+ });
1636
+ await executeSchematic("Client main file", collection, "part-main", partOptions);
1637
+ }
1638
+ async generateServerParts(collection, values) {
1639
+ const partOptions = this.partOptions(values, "server");
1640
+ await executeSchematic("Server base", collection, "part-base", {
1641
+ ...partOptions,
1642
+ server: values.server
1643
+ });
1644
+ await executeSchematic("Server main file", collection, "part-main", partOptions);
1645
+ }
1646
+ async generateDocker(collection, values) {
1647
+ await executeSchematic("Docker", collection, "docker", {
1648
+ name: values.name,
1649
+ directory: values.directory,
1650
+ packageManager: values.packageManager
1681
1651
  });
1682
- await executeSchematic("Server main file", collection, "part-main", {
1652
+ }
1653
+ partOptions(values, part) {
1654
+ return {
1683
1655
  name: values.name,
1684
- part: "server",
1656
+ part,
1685
1657
  directory: values.directory,
1686
1658
  language: values.language,
1687
1659
  initFunctions: values.initFunctions
1688
- });
1660
+ };
1689
1661
  }
1690
- }, "generateApplicationFiles");
1691
- var runInstall = /* @__PURE__ */ __name(async (directory, pkgManagerName) => {
1692
- try {
1693
- const packageManager = PackageManagerFactory.create(pkgManagerName);
1694
- await packageManager.install(directory);
1695
- } catch (error4) {
1696
- if (error4 && error4.message) {
1697
- console4.error(ansis4.red(error4.message));
1698
- }
1662
+ async installDependencies(packageManagerName, directory) {
1663
+ const packageManager = PackageManagerFactory.create(packageManagerName);
1664
+ return await packageManager.install(directory);
1699
1665
  }
1700
- }, "runInstall");
1666
+ };
1701
1667
 
1702
1668
  // src/action/actions/start.action.ts
1703
- import * as ansis5 from "ansis";
1704
- import * as console5 from "console";
1705
- import { join as join5 } from "path";
1669
+ import { join as join7 } from "path";
1706
1670
  var StartAction = class extends AbstractAction {
1707
1671
  static {
1708
1672
  __name(this, "StartAction");
1709
1673
  }
1674
+ startMessage = Messages.START_START;
1675
+ successMessage = Messages.START_SUCCESS;
1676
+ failureMessage = Messages.START_FAILED;
1710
1677
  async handle(_args, options) {
1711
- console5.info(Messages.RUN_START);
1712
- console5.info();
1713
- try {
1714
- const directory = getDirectoryInput(options);
1715
- const config2 = await getConfig(options, directory);
1716
- const clientDir = config2.client.runtime.dir;
1717
- const serverDir = config2.server.runtime.dir;
1718
- const clientPort = getStringInputWithDefault(options, "clientPort", config2.client.port);
1719
- const watch3 = getWatchInput(options);
1720
- await Promise.all([
1721
- config2.server.enable ? this.startServer(directory, serverDir, watch3) : void 0,
1722
- this.startClient(clientPort, directory, clientDir, {
1723
- watch: watch3,
1724
- serverGameDir: config2.server.enable ? serverDir : void 0
1725
- })
1726
- ]);
1727
- process.exit(0);
1728
- } catch (e) {
1729
- console5.error(e);
1730
- process.exit(1);
1731
- }
1678
+ const directory = getDirectoryInput(options);
1679
+ const config2 = await getConfig(options, directory);
1680
+ const watch3 = getWatchInput(options);
1681
+ const ports = this.resolvePorts(options, config2);
1682
+ const ssl = this.resolveSSL(options);
1683
+ const tasks = this.buildStartTasks(config2, directory, watch3, ports, ssl);
1684
+ await Promise.all(tasks);
1685
+ return { keepAlive: true };
1686
+ }
1687
+ resolvePorts(options, config2) {
1688
+ return {
1689
+ clientPort: getStringInputWithDefault(options, "clientPort", config2.client.port),
1690
+ gameExposurePort: getStringInput(options, "gameExposurePort"),
1691
+ serverPort: getStringInput(options, "serverPort")
1692
+ };
1693
+ }
1694
+ resolveSSL(options) {
1695
+ const cert = getStringInput(options, "cert");
1696
+ const key = getStringInput(options, "key");
1697
+ if (!cert && !key) return void 0;
1698
+ if (!cert) throw new Error("No cert entered for SSL. Please enter a key with --cert.");
1699
+ if (!key) throw new Error("No key entered for SSL. Please enter a key with --key.");
1700
+ return {
1701
+ cert,
1702
+ key
1703
+ };
1732
1704
  }
1733
- async startClient(port, directory, gameDir, options) {
1734
- const path = getModulePath("@nanoforge-dev/loader-client/package.json", true);
1735
- const params = {
1736
- PORT: port,
1737
- GAME_DIR: getCwd(join5(directory, gameDir))
1705
+ buildStartTasks(config2, directory, watch3, ports, ssl) {
1706
+ const tasks = [];
1707
+ if (config2.server.enable) {
1708
+ tasks.push(this.startServer(directory, config2.server.runtime.dir, watch3, ports.serverPort));
1709
+ }
1710
+ tasks.push(this.startClient(directory, config2, watch3, ports, ssl));
1711
+ return tasks;
1712
+ }
1713
+ async startClient(directory, config2, watch3, ports, ssl) {
1714
+ const loaderPath = getModulePath("@nanoforge-dev/loader-client/package.json", true);
1715
+ const gameDir = config2.client.runtime.dir;
1716
+ const env2 = this.buildClientEnv(directory, gameDir, watch3, config2, ports, ssl);
1717
+ await this.runLoader("Client", loaderPath, env2);
1718
+ }
1719
+ buildClientEnv(directory, gameDir, watch3, config2, ports, ssl) {
1720
+ const env2 = {
1721
+ PORT: ports.clientPort,
1722
+ GAME_DIR: getCwd(join7(directory, gameDir))
1738
1723
  };
1739
- if (options?.watch) {
1740
- params["WATCH"] = "true";
1741
- if (options?.serverGameDir) {
1742
- params["WATCH_SERVER_GAME_DIR"] = getCwd(join5(directory, options.serverGameDir));
1724
+ if (ports.gameExposurePort) {
1725
+ env2["GAME_EXPOSURE_PORT"] = ports.gameExposurePort;
1726
+ }
1727
+ if (watch3) {
1728
+ env2["WATCH"] = "true";
1729
+ if (config2.server.enable) {
1730
+ env2["WATCH_SERVER_GAME_DIR"] = getCwd(join7(directory, config2.server.runtime.dir));
1743
1731
  }
1744
1732
  }
1745
- return runPart("Client", path, params);
1733
+ if (ssl) {
1734
+ env2["CERT"] = ssl.cert;
1735
+ env2["KEY"] = ssl.key;
1736
+ }
1737
+ return env2;
1746
1738
  }
1747
- startServer(directory, gameDir, watch3) {
1748
- const path = getModulePath("@nanoforge-dev/loader-server/package.json", true);
1749
- const params = {
1750
- GAME_DIR: getCwd(join5(directory, gameDir))
1739
+ async startServer(directory, gameDir, watch3, port) {
1740
+ const loaderPath = getModulePath("@nanoforge-dev/loader-server/package.json", true);
1741
+ const env2 = {
1742
+ GAME_DIR: getCwd(join7(directory, gameDir))
1751
1743
  };
1752
- if (watch3) params["WATCH"] = "true";
1753
- return runPart("Server", path, params);
1744
+ if (port) env2["PORT"] = port;
1745
+ if (watch3) env2["WATCH"] = "true";
1746
+ await this.runLoader("Server", loaderPath, env2);
1747
+ }
1748
+ async runLoader(name, directory, env2) {
1749
+ await runSafe(async () => {
1750
+ const packageManager = await PackageManagerFactory.find(directory);
1751
+ await packageManager.run(name, directory, "start", env2, [], true);
1752
+ });
1754
1753
  }
1755
1754
  };
1756
- var runPart = /* @__PURE__ */ __name(async (part, directory, env2, flags) => {
1757
- try {
1758
- const packageManager = await PackageManagerFactory.find(directory);
1759
- await packageManager.run(part, directory, "start", env2, flags, true);
1760
- } catch (error4) {
1761
- if (error4 && error4.message) {
1762
- console5.error(ansis5.red(error4.message));
1763
- }
1764
- }
1765
- }, "runPart");
1766
1755
 
1767
1756
  // src/command/abstract.command.ts
1768
1757
  var AbstractCommand = class {
@@ -1772,6 +1761,13 @@ var AbstractCommand = class {
1772
1761
  static {
1773
1762
  __name(this, "AbstractCommand");
1774
1763
  }
1764
+ static mapToInput(mapping) {
1765
+ const input2 = /* @__PURE__ */ new Map();
1766
+ for (const [key, value] of Object.entries(mapping)) {
1767
+ input2.set(key, { value });
1768
+ }
1769
+ return input2;
1770
+ }
1775
1771
  };
1776
1772
 
1777
1773
  // src/command/commands/build.command.ts
@@ -1780,15 +1776,15 @@ var BuildCommand = class extends AbstractCommand {
1780
1776
  __name(this, "BuildCommand");
1781
1777
  }
1782
1778
  load(program2) {
1783
- program2.command("build").description("build your game").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", "nanoforge.config.json").option("--client-outDir [clientDirectory]", "specify the output directory of the client").option("--server-outDir [serverDirectory]", "specify the output directory of the server").option("--watch", "build app in watching mode", false).action(async (rawOptions) => {
1784
- const options = /* @__PURE__ */ new Map();
1785
- options.set("directory", { value: rawOptions.directory });
1786
- options.set("config", { value: rawOptions.config });
1787
- options.set("clientDirectory", { value: rawOptions.clientOutDir });
1788
- options.set("serverDirectory", { value: rawOptions.serverOutDir });
1789
- options.set("watch", { value: rawOptions.watch });
1790
- const args = /* @__PURE__ */ new Map();
1791
- await this.action.handle(args, options);
1779
+ program2.command("build").description("build your game").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME).option("--client-outDir [clientDirectory]", "specify the output directory of the client").option("--server-outDir [serverDirectory]", "specify the output directory of the server").option("--watch", "build app in watching mode", false).action(async (rawOptions) => {
1780
+ const options = AbstractCommand.mapToInput({
1781
+ directory: rawOptions.directory,
1782
+ config: rawOptions.config,
1783
+ clientDirectory: rawOptions.clientOutDir,
1784
+ serverDirectory: rawOptions.serverOutDir,
1785
+ watch: rawOptions.watch
1786
+ });
1787
+ await this.action.run(/* @__PURE__ */ new Map(), options);
1792
1788
  });
1793
1789
  }
1794
1790
  };
@@ -1799,13 +1795,13 @@ var DevCommand = class extends AbstractCommand {
1799
1795
  __name(this, "DevCommand");
1800
1796
  }
1801
1797
  load(program2) {
1802
- program2.command("dev").description("run your game in dev mode").option("-d, --directory [directory]", "specify the directory of your project").option("--generate", "generate app from config", false).action(async (rawOptions) => {
1803
- const options = /* @__PURE__ */ new Map();
1804
- options.set("directory", { value: rawOptions.directory });
1805
- options.set("config", { value: rawOptions.config });
1806
- options.set("generate", { value: rawOptions.generate });
1807
- const args = /* @__PURE__ */ new Map();
1808
- await this.action.handle(args, options);
1798
+ program2.command("dev").description("run your game in dev mode").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME).option("--generate", "generate app from config", false).action(async (rawOptions) => {
1799
+ const options = AbstractCommand.mapToInput({
1800
+ directory: rawOptions.directory,
1801
+ config: rawOptions.config,
1802
+ generate: rawOptions.generate
1803
+ });
1804
+ await this.action.run(/* @__PURE__ */ new Map(), options);
1809
1805
  });
1810
1806
  }
1811
1807
  };
@@ -1816,13 +1812,13 @@ var GenerateCommand = class extends AbstractCommand {
1816
1812
  __name(this, "GenerateCommand");
1817
1813
  }
1818
1814
  load(program2) {
1819
- program2.command("generate").description("generate nanoforge files from config").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", "nanoforge.config.json").option("--watch", "generate app in watching mode", false).action(async (rawOptions) => {
1820
- const options = /* @__PURE__ */ new Map();
1821
- options.set("directory", { value: rawOptions.directory });
1822
- options.set("config", { value: rawOptions.config });
1823
- options.set("watch", { value: rawOptions.watch });
1824
- const args = /* @__PURE__ */ new Map();
1825
- await this.action.handle(args, options);
1815
+ program2.command("generate").description("generate nanoforge files from config").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME).option("--watch", "generate app in watching mode", false).action(async (rawOptions) => {
1816
+ const options = AbstractCommand.mapToInput({
1817
+ directory: rawOptions.directory,
1818
+ config: rawOptions.config,
1819
+ watch: rawOptions.watch
1820
+ });
1821
+ await this.action.run(/* @__PURE__ */ new Map(), options);
1826
1822
  });
1827
1823
  }
1828
1824
  };
@@ -1834,11 +1830,13 @@ var InstallCommand = class extends AbstractCommand {
1834
1830
  }
1835
1831
  load(program2) {
1836
1832
  program2.command("install [names...]").alias("add").description("add NanoForge library to your project").option("-d, --directory [directory]", "specify the directory of your project").action(async (names, rawOptions) => {
1837
- const options = /* @__PURE__ */ new Map();
1838
- options.set("directory", { value: rawOptions.directory });
1839
- const args = /* @__PURE__ */ new Map();
1840
- args.set("names", { value: names.length ? names : void 0 });
1841
- await this.action.handle(args, options);
1833
+ const options = AbstractCommand.mapToInput({
1834
+ directory: rawOptions.directory
1835
+ });
1836
+ const args = AbstractCommand.mapToInput({
1837
+ names: names.length ? names : void 0
1838
+ });
1839
+ await this.action.run(args, options);
1842
1840
  });
1843
1841
  }
1844
1842
  };
@@ -1849,19 +1847,20 @@ var NewCommand = class extends AbstractCommand {
1849
1847
  __name(this, "NewCommand");
1850
1848
  }
1851
1849
  load(program2) {
1852
- program2.command("new").description("create a new nanoforge project").option("-d, --directory [directory]", "specify the directory of your project").option("--name [name]", "specify the name of your project").option("--path [path]", "specify the path of your project").option("--package-manager [packageManager]", "specify the package manager of your project").option("--language [language]", "specify the language of your project").option("--strict", "use strict mode").option("--no-strict", "do not use strict mode").option("--server", "create a server").option("--no-server", "do not create a server").option("--init-functions", "initialize functions").option("--no-init-functions", "do not initialize functions").option("--skip-install", "skip installing dependencies").option("--no-skip-install", "do not skip installing dependencies").action(async (rawOptions) => {
1853
- const options = /* @__PURE__ */ new Map();
1854
- options.set("directory", { value: rawOptions.directory });
1855
- options.set("name", { value: rawOptions.name });
1856
- options.set("path", { value: rawOptions.path });
1857
- options.set("packageManager", { value: rawOptions.packageManager });
1858
- options.set("language", { value: rawOptions.language });
1859
- options.set("strict", { value: rawOptions.strict });
1860
- options.set("server", { value: rawOptions.server });
1861
- options.set("initFunctions", { value: rawOptions.initFunctions });
1862
- options.set("skipInstall", { value: rawOptions.skipInstall });
1863
- const args = /* @__PURE__ */ new Map();
1864
- await this.action.handle(args, options);
1850
+ program2.command("new").description("create a new nanoforge project").option("-d, --directory [directory]", "specify the directory of your project").option("--name [name]", "specify the name of your project").option("--path [path]", "specify the path of your project").option("--package-manager [packageManager]", "specify the package manager of your project").option("--language [language]", "specify the language of your project").option("--strict", "use strict mode").option("--no-strict", "do not use strict mode").option("--server", "create a server").option("--no-server", "do not create a server").option("--init-functions", "initialize functions").option("--no-init-functions", "do not initialize functions").option("--skip-install", "skip installing dependencies").option("--no-skip-install", "do not skip installing dependencies").option("--docker", "generate docker files").option("--no-docker", "do not generate docker files").action(async (rawOptions) => {
1851
+ const options = AbstractCommand.mapToInput({
1852
+ directory: rawOptions.directory,
1853
+ name: rawOptions.name,
1854
+ path: rawOptions.path,
1855
+ packageManager: rawOptions.packageManager,
1856
+ language: rawOptions.language,
1857
+ strict: rawOptions.strict,
1858
+ server: rawOptions.server,
1859
+ initFunctions: rawOptions.initFunctions,
1860
+ skipInstall: rawOptions.skipInstall,
1861
+ docker: rawOptions.docker
1862
+ });
1863
+ await this.action.run(/* @__PURE__ */ new Map(), options);
1865
1864
  });
1866
1865
  }
1867
1866
  };
@@ -1872,21 +1871,21 @@ var StartCommand = class extends AbstractCommand {
1872
1871
  __name(this, "StartCommand");
1873
1872
  }
1874
1873
  load(program2) {
1875
- program2.command("start").description("start your game").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", "nanoforge.config.json").option(
1874
+ program2.command("start").description("start your game").option("-d, --directory [directory]", "specify the directory of your project").option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME).option(
1876
1875
  "-p, --client-port [clientPort]",
1877
1876
  "specify the port of the loader (the website to load the game)"
1878
- ).option("--game-exposure-port [gameExposurePort]", "specify the port of the game exposure").option("--server-port [serverPort]", "specify the port of the server").option("--watch", "run app in watching mode", false).action(async (rawOptions) => {
1879
- const options = /* @__PURE__ */ new Map();
1880
- options.set("directory", { value: rawOptions.directory });
1881
- options.set("config", { value: rawOptions.config });
1882
- options.set("clientPort", { value: rawOptions.clientPort });
1883
- options.set("gameExposurePort", {
1884
- value: rawOptions.gameExposurePort
1877
+ ).option("--game-exposure-port [gameExposurePort]", "specify the port of the game exposure").option("--server-port [serverPort]", "specify the port of the server").option("--watch", "run app in watching mode", false).option("--cert [cert]", "path to the SSL certificate for HTTPS").option("--key [key]", "path to the SSL key for HTTPS").action(async (rawOptions) => {
1878
+ const options = AbstractCommand.mapToInput({
1879
+ directory: rawOptions.directory,
1880
+ config: rawOptions.config,
1881
+ clientPort: rawOptions.clientPort,
1882
+ gameExposurePort: rawOptions.gameExposurePort,
1883
+ serverPort: rawOptions.serverPort,
1884
+ watch: rawOptions.watch,
1885
+ cert: rawOptions.cert,
1886
+ key: rawOptions.key
1885
1887
  });
1886
- options.set("serverPort", { value: rawOptions.serverPort });
1887
- options.set("watch", { value: rawOptions.watch });
1888
- const args = /* @__PURE__ */ new Map();
1889
- await this.action.handle(args, options);
1888
+ await this.action.run(/* @__PURE__ */ new Map(), options);
1890
1889
  });
1891
1890
  }
1892
1891
  };
@@ -1908,8 +1907,8 @@ var CommandLoader = class {
1908
1907
  static handleInvalidCommand(program2) {
1909
1908
  program2.on("command:*", () => {
1910
1909
  console.error(`
1911
- ${Prefixes.ERROR} Invalid command: ${red8`%s`}`, program2.args.join(" "));
1912
- console.log(`See ${red8`--help`} for a list of available commands.
1910
+ ${Prefixes.ERROR} Invalid command: ${red6`%s`}`, program2.args.join(" "));
1911
+ console.log(`See ${red6`--help`} for a list of available commands.
1913
1912
  `);
1914
1913
  process.exit(1);
1915
1914
  });