create-seedcord 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  </div>
7
7
 
8
8
  <div align="center">
9
- <h3>The whole Discord bot, wired and typed</h3>
9
+ <h3>The whole Discord bot, typed end to end</h3>
10
10
  <a href="https://seedcord.org">Website</a> ·
11
11
  <a href="https://guide.seedcord.org">Guide</a> ·
12
12
  <a href="https://docs.seedcord.org">Reference</a> ·
@@ -35,7 +35,7 @@ npm create seedcord my-bot
35
35
  yarn create seedcord my-bot
36
36
  ```
37
37
 
38
- It asks where the project goes, how Discord reaches your bot, what the bot should react to, your bot token, and an accent color. Then it writes the project, installs, formats, generates your command types, and makes the first commit.
38
+ It asks where the project goes, how Discord reaches your bot, what the bot should react to, your bot token, and an accent color. Then it writes the project, installs, formats, generates your command types, and makes the first commit. A step that fails after the write becomes a warning. The summary still prints.
39
39
 
40
40
  On Windows, run it from Windows Terminal. The prompts fall back to ASCII in `cmd.exe` making the boxes draw as `T`, `|`, and `o`.
41
41
 
@@ -50,9 +50,9 @@ npm create seedcord my-bot -- --transport gateway --capabilities reactions
50
50
  pnpm create seedcord my-bot --transport gateway --capabilities reactions
51
51
  ```
52
52
 
53
- `--no-install` and `--no-git` turn off those two steps.
53
+ `--no-install` and `--no-git` turn off those two steps. `-v` prints the version and `-h` prints the flag list.
54
54
 
55
- With no terminal to ask on, it reads the flags alone. It names the flag that would supply any answer you left out.
55
+ With no terminal to ask on, it reads the flags alone. It prints the flag that would supply any answer you left out.
56
56
 
57
57
  ## What you get
58
58
 
package/dist/index.mjs CHANGED
@@ -1922,7 +1922,7 @@ const chalk = createChalk();
1922
1922
  const chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
1923
1923
 
1924
1924
  //#endregion
1925
- //#region ../../packages/errors/dist/SeedcordError-C3HkJAhQ.mjs
1925
+ //#region ../../packages/errors/dist/SeedcordError-BfC-202u.mjs
1926
1926
  /**
1927
1927
  * All Seedcord error codes.
1928
1928
  */
@@ -1995,6 +1995,8 @@ let SeedcordErrorCode = /* @__PURE__ */ function(SeedcordErrorCode) {
1995
1995
  SeedcordErrorCode[SeedcordErrorCode["CorePluginKeyHoldsGroup"] = 1216] = "CorePluginKeyHoldsGroup";
1996
1996
  /** A plugin key is empty, has an empty part, or carries more than one dot. */
1997
1997
  SeedcordErrorCode[SeedcordErrorCode["CorePluginKeyMalformed"] = 1217] = "CorePluginKeyMalformed";
1998
+ /** An attached plugin doesn't extend the `Plugin` class of the bot's `@seedcord/core`. */
1999
+ SeedcordErrorCode[SeedcordErrorCode["CorePluginFromOtherCore"] = 1218] = "CorePluginFromOtherCore";
1998
2000
  /** A command decorator attempted to re-register an existing command scope. */
1999
2001
  SeedcordErrorCode[SeedcordErrorCode["DecoratorCommandAlreadyRegistered"] = 1301] = "DecoratorCommandAlreadyRegistered";
2000
2002
  /** A global command decorator specified guild IDs, which is not allowed. */
@@ -2135,8 +2137,8 @@ let SeedcordErrorCode = /* @__PURE__ */ function(SeedcordErrorCode) {
2135
2137
  SeedcordErrorCode[SeedcordErrorCode["CliConfigNotFound"] = 3103] = "CliConfigNotFound";
2136
2138
  /** CLI entry file does not exist. */
2137
2139
  SeedcordErrorCode[SeedcordErrorCode["CliEntryNotFound"] = 3104] = "CliEntryNotFound";
2138
- /** tsx failed to import the provided entry file. */
2139
- SeedcordErrorCode[SeedcordErrorCode["CliTsxImportFailed"] = 3105] = "CliTsxImportFailed";
2140
+ /** Failed to import a TypeScript entry file. */
2141
+ SeedcordErrorCode[SeedcordErrorCode["CliTsImportFailed"] = 3105] = "CliTsImportFailed";
2140
2142
  /** Native import and jiti fallback both failed. */
2141
2143
  SeedcordErrorCode[SeedcordErrorCode["CliImportFailed"] = 3106] = "CliImportFailed";
2142
2144
  /** Seedcord instance export is missing a start() method. */
@@ -2242,6 +2244,7 @@ const messages = {
2242
2244
  [1207]: (dir) => `${dir} could not be read.`,
2243
2245
  [1215]: (head, key) => `${paint.sky(head)} is already taken on this bot, so ${paint.sky(key)} cannot nest under it. Pick another group name.`,
2244
2246
  [1217]: (key, reason) => `${paint.sky(key)} ${reason} Write a group and a plugin name around one dot, like 'services.users'.`,
2247
+ [1218]: (plugin) => `${paint.sky(plugin)} doesn't extend the ${paint.bold("Plugin")} class your bot uses. That usually means two copies of ${paint.bold("@seedcord/core")} are installed. Update the plugin and your ${paint.bold("@seedcord")} packages to versions that need the same ${paint.bold("@seedcord/core")}. Your package manager can list each installed copy and the package that pulled it in.`,
2245
2248
  [1216]: (key) => `${paint.sky(key)} already holds a group of plugins. Attach this one under a name of its own.`,
2246
2249
  [1208]: (key) => `Plugin key "${key}" is a channel the framework logs on. Pick another key.`,
2247
2250
  [1209]: () => "The bot's application id resolves during startup. You read it before that. Read it inside a handler, inside a plugin's ready(), or after start() resolves.",
@@ -2322,7 +2325,7 @@ const messages = {
2322
2325
  [3109]: () => "Config must include an `entry` string that points to your startup script.",
2323
2326
  [3110]: (entryPath, root) => `Entry file ${entryPath} must reside inside configured root ${root}.`,
2324
2327
  [3104]: (entryPath) => `Cannot find entry file at ${entryPath}.`,
2325
- [3105]: (entryPath, reason) => `Failed to import ${entryPath} via tsx: ${reason}`,
2328
+ [3105]: (entryPath, reason) => `Failed to import TypeScript file ${entryPath}: ${reason}`,
2326
2329
  [3106]: (entryPath, nativeReason, fallbackReason) => `Failed to import ${entryPath}: ${nativeReason} (the jiti fallback also failed: ${fallbackReason})`,
2327
2330
  [3107]: () => "Seedcord instance must default export an object with a start() method.",
2328
2331
  [3108]: (instancePath, reason) => `Failed to start Seedcord from ${instancePath}: ${reason}`,
@@ -2465,7 +2468,7 @@ function isSeedcordError(error, type, code) {
2465
2468
 
2466
2469
  //#endregion
2467
2470
  //#region src/cli/banner.ts
2468
- function banner(version = "0.3.1") {
2471
+ function banner(version = "0.3.3") {
2469
2472
  const wordmark = `${paint.flesh.bold("seed")}${paint.rind.bold("cord")}${paint.mute(" create")}`;
2470
2473
  if (version === void 0) return wordmark;
2471
2474
  return `${wordmark}${paint.pith(` • v${version}`)}`;
@@ -2897,20 +2900,29 @@ const EXTRA = [
2897
2900
  name: "no-git",
2898
2901
  description: "skip git init and the first commit"
2899
2902
  },
2903
+ {
2904
+ name: "version",
2905
+ short: "v",
2906
+ description: "print the version"
2907
+ },
2900
2908
  {
2901
2909
  name: "help",
2910
+ short: "h",
2902
2911
  description: "print this"
2903
2912
  }
2904
2913
  ];
2914
+ function spelling(flag) {
2915
+ return flag.short === void 0 ? `--${flag.name}` : `-${flag.short}, --${flag.name}`;
2916
+ }
2905
2917
  function helpText() {
2906
2918
  const flags = [...STEPS.map((step) => step.flag), ...EXTRA];
2907
- const width = Math.max(...flags.map((flag) => flag.name.length));
2919
+ const width = Math.max(...flags.map((flag) => spelling(flag).length));
2908
2920
  return [
2909
2921
  "Usage: create-seedcord [directory] [flags]",
2910
2922
  "",
2911
2923
  "Every question the interview asks has a flag. Pass them all to skip the interview.",
2912
2924
  "",
2913
- ...flags.map((flag) => ` --${flag.name.padEnd(width)} ${flag.description}`),
2925
+ ...flags.map((flag) => ` ${spelling(flag).padEnd(width)} ${flag.description}`),
2914
2926
  "",
2915
2927
  "npm needs a -- before the flags: npm create seedcord my-bot -- --transport gateway"
2916
2928
  ].join("\n");
@@ -3550,9 +3562,13 @@ const OPTIONS = {
3550
3562
  "no-git": { type: "boolean" }
3551
3563
  };
3552
3564
  const HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
3565
+ const VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v"]);
3553
3566
  function wantsHelp(argv) {
3554
3567
  return argv.some((argument) => HELP_FLAGS.has(argument));
3555
3568
  }
3569
+ function wantsVersion(argv) {
3570
+ return argv.some((argument) => VERSION_FLAGS.has(argument));
3571
+ }
3556
3572
  function readArgv(argv) {
3557
3573
  try {
3558
3574
  return parseArgs({
@@ -3594,11 +3610,10 @@ const CANCELLED = {
3594
3610
  };
3595
3611
  function reportFailure(error) {
3596
3612
  if (isSeedcordError(error, void 0, SeedcordErrorCode.CreateCancelled)) return CANCELLED;
3597
- const kept = isSeedcordError(error, void 0, SeedcordErrorCode.CreateStepFailed);
3598
3613
  return {
3599
3614
  code: 1,
3600
3615
  message: isSeedcordError(error) || Error.isError(error) ? error.message : String(error),
3601
- closing: kept ? "Project kept. Run the command manually." : "Nothing was created."
3616
+ closing: "Nothing was created."
3602
3617
  };
3603
3618
  }
3604
3619
 
@@ -3630,6 +3645,10 @@ function silentSteps() {
3630
3645
  };
3631
3646
  }
3632
3647
 
3648
+ //#endregion
3649
+ //#region src/cli/version.ts
3650
+ const version = "0.3.3";
3651
+
3633
3652
  //#endregion
3634
3653
  //#region src/cli/summary.ts
3635
3654
  const PORTAL = "https://discord.com/developers/applications";
@@ -3667,7 +3686,7 @@ function reproducingCommand(answers, agent) {
3667
3686
  `--color ${answers.botColor}`
3668
3687
  ];
3669
3688
  const separator = agent === "npm" ? "-- " : "";
3670
- return `${agent} create seedcord ${answers.directory} ${separator}${flags.join(" ")}`;
3689
+ return `${agent} create seedcord@${version} ${answers.directory} ${separator}${flags.join(" ")}`;
3671
3690
  }
3672
3691
 
3673
3692
  //#endregion
@@ -3705,17 +3724,33 @@ async function probeCloudflared(binary = "cloudflared", timeout = PROBE_TIMEOUT_
3705
3724
 
3706
3725
  //#endregion
3707
3726
  //#region src/scaffold/exec.ts
3708
- const KEPT_LINES = 12;
3709
- const NAMES_THE_CAUSE = /error|ERR!/i;
3727
+ const NAMES_THE_CAUSE = /error|ERR[_!]/i;
3728
+ const BLOCKED_BUILD = /ERR_PNPM_IGNORED_BUILDS/;
3729
+ const INDENTED = /^\s/;
3710
3730
  const LINE_BREAK = /[\r\n]+/;
3711
3731
  const PIPED_COLUMNS = 100;
3712
3732
  const GUTTER = 3;
3713
3733
  function lineWidth() {
3714
3734
  return (process$1.stdout.columns > 0 ? process$1.stdout.columns : PIPED_COLUMNS) - GUTTER;
3715
3735
  }
3736
+ function blockFrom(header, rest) {
3737
+ const detail = [];
3738
+ for (const line of rest) {
3739
+ if (!INDENTED.test(line)) break;
3740
+ detail.push(line);
3741
+ }
3742
+ return [header, ...detail];
3743
+ }
3716
3744
  function failureLines(captured) {
3717
- const named = captured.filter((line) => NAMES_THE_CAUSE.test(line));
3718
- return named.length > 0 ? named.slice(0, KEPT_LINES) : captured.slice(-12);
3745
+ const blocks = [];
3746
+ let consumedThrough = -1;
3747
+ for (const [index, line] of captured.entries()) {
3748
+ if (index <= consumedThrough || !NAMES_THE_CAUSE.test(line)) continue;
3749
+ const block = blockFrom(line, captured.slice(index + 1));
3750
+ blocks.push(...block);
3751
+ consumedThrough = index + block.length - 1;
3752
+ }
3753
+ return (blocks.length > 0 ? blocks : captured).slice(-12);
3719
3754
  }
3720
3755
  const SHIMS = /* @__PURE__ */ new Set([
3721
3756
  "npm",
@@ -3750,8 +3785,8 @@ async function execRunner(...[command, args, cwd]) {
3750
3785
  const captured = [];
3751
3786
  const take = (chunk) => {
3752
3787
  for (const line of chunk.toString("utf8").split(LINE_BREAK)) {
3753
- const text = line.trim().slice(0, width);
3754
- if (text === "") continue;
3788
+ const text = line.trimEnd().slice(0, width);
3789
+ if (text.trim() === "") continue;
3755
3790
  captured.push(text);
3756
3791
  }
3757
3792
  };
@@ -3762,10 +3797,14 @@ async function execRunner(...[command, args, cwd]) {
3762
3797
  });
3763
3798
  child.on("close", (code) => {
3764
3799
  if (code === 0) {
3765
- resolve();
3800
+ resolve(null);
3766
3801
  return;
3767
3802
  }
3768
3803
  const shown = failureLines(captured).join("\n");
3804
+ if (captured.some((line) => BLOCKED_BUILD.test(line))) {
3805
+ resolve(shown);
3806
+ return;
3807
+ }
3769
3808
  reject(new SeedcordError(SeedcordErrorCode.CreateStepFailed, [spoken, shown || `exited with ${code}`]));
3770
3809
  });
3771
3810
  });
@@ -11117,10 +11156,25 @@ async function renderTemplates(templatesRoot, context) {
11117
11156
 
11118
11157
  //#endregion
11119
11158
  //#region src/scaffold/scaffold.ts
11159
+ const INSTALL_STEPS = {
11160
+ install: {
11161
+ running: "Installing dependencies",
11162
+ done: "Dependencies installed"
11163
+ },
11164
+ format: {
11165
+ running: "Formatting",
11166
+ done: "Code formatted"
11167
+ },
11168
+ codegen: {
11169
+ running: "Generating types",
11170
+ done: "Types generated"
11171
+ }
11172
+ };
11120
11173
  const DEV_PACKAGES = [
11121
11174
  "@seedcord/eslint-config",
11122
11175
  "@seedcord/tsconfig",
11123
11176
  "@types/node",
11177
+ "eslint",
11124
11178
  "jiti",
11125
11179
  "prettier",
11126
11180
  "seedcord",
@@ -11137,25 +11191,42 @@ async function writeTree(target, files) {
11137
11191
  await writeFile(destination, file.contents, "utf8");
11138
11192
  }
11139
11193
  }
11140
- async function runGitSteps(input, run) {
11194
+ async function runStep(steps, labels, work) {
11141
11195
  try {
11142
- await input.steps.run({
11143
- running: "Setting up git",
11144
- done: "Committed"
11145
- }, async () => {
11146
- await run("git", ["init"], input.target);
11147
- await run("git", ["add", "."], input.target);
11148
- await run("git", [
11149
- "commit",
11150
- "-m",
11151
- "chore: create seedcord bot"
11152
- ], input.target);
11153
- });
11196
+ await steps.run(labels, work);
11154
11197
  return null;
11155
11198
  } catch (error) {
11156
- return `${Error.isError(error) ? error.message : String(error)} The project is complete and uncommitted.`;
11199
+ return Error.isError(error) ? error.message : String(error);
11157
11200
  }
11158
11201
  }
11202
+ async function gitNoticeFor(input, run, plan) {
11203
+ if (!plan.init) {
11204
+ input.steps.skip("Committed");
11205
+ return plan.notice;
11206
+ }
11207
+ const reason = await runStep(input.steps, {
11208
+ running: "Setting up git",
11209
+ done: "Committed"
11210
+ }, async () => {
11211
+ await run("git", ["init"], input.target);
11212
+ await run("git", ["add", "."], input.target);
11213
+ await run("git", [
11214
+ "commit",
11215
+ "-m",
11216
+ "chore: create seedcord bot"
11217
+ ], input.target);
11218
+ });
11219
+ if (reason === null) return plan.notice;
11220
+ return `${reason} The project is complete and uncommitted.`;
11221
+ }
11222
+ function skipInstallSteps(steps) {
11223
+ for (const labels of Object.values(INSTALL_STEPS)) steps.skip(labels.done);
11224
+ return {
11225
+ installed: false,
11226
+ notices: [],
11227
+ failed: false
11228
+ };
11229
+ }
11159
11230
  async function runInstallSteps(input, run, isGateway) {
11160
11231
  const { agent, steps, target } = input;
11161
11232
  const deps = addCommand(agent, runtimePackages(isGateway), false);
@@ -11166,66 +11237,67 @@ async function runInstallSteps(input, run, isGateway) {
11166
11237
  "."
11167
11238
  ]);
11168
11239
  const codegen = execCommand(agent, ["seedcord", "codegen"]);
11169
- await steps.run({
11170
- running: "Installing dependencies",
11171
- done: "Dependencies installed"
11172
- }, async () => {
11173
- await run(deps.command, deps.args, target);
11174
- await run(dev.command, dev.args, target);
11240
+ const warnings = [];
11241
+ const collect = (warning) => {
11242
+ if (warning !== null) warnings.push(warning);
11243
+ };
11244
+ const install = await runStep(steps, INSTALL_STEPS.install, async () => {
11245
+ collect(await run(deps.command, deps.args, target));
11246
+ collect(await run(dev.command, dev.args, target));
11175
11247
  });
11176
- await steps.run({
11177
- running: "Formatting",
11178
- done: "Code formatted"
11179
- }, () => run(format.command, format.args, target));
11180
- await steps.run({
11181
- running: "Generating types",
11182
- done: "Types generated"
11183
- }, () => run(codegen.command, codegen.args, target));
11248
+ if (install !== null) {
11249
+ steps.skip(INSTALL_STEPS.format.done);
11250
+ steps.skip(INSTALL_STEPS.codegen.done);
11251
+ return {
11252
+ installed: false,
11253
+ notices: [install, ...warnings],
11254
+ failed: true
11255
+ };
11256
+ }
11257
+ const failures = [await runStep(steps, INSTALL_STEPS.format, () => run(format.command, format.args, target)), await runStep(steps, INSTALL_STEPS.codegen, () => run(codegen.command, codegen.args, target))].filter((reason) => reason !== null);
11258
+ return {
11259
+ installed: true,
11260
+ notices: [...warnings, ...failures],
11261
+ failed: failures.length > 0
11262
+ };
11184
11263
  }
11185
- async function scaffold(input, run) {
11186
- const { existed } = await claimTarget(input.target);
11187
- const parent = dirname(input.target);
11188
- await mkdir(parent, { recursive: true });
11189
- const plan = gitPlanFrom(await probeGit(parent), input.git);
11190
- let written = false;
11264
+ async function writeProject(input, plan, existed) {
11265
+ const context = buildContext(input.answers, {
11266
+ developerUsername: plan.developerUsername,
11267
+ runCommand: runPrefix(input.agent)
11268
+ });
11191
11269
  try {
11192
- const context = buildContext(input.answers, {
11193
- developerUsername: plan.developerUsername,
11194
- runCommand: runPrefix(input.agent)
11195
- });
11196
- const { steps } = input;
11197
- await steps.run({
11270
+ await input.steps.run({
11198
11271
  running: "Writing files",
11199
11272
  done: "Files written"
11200
11273
  }, async () => {
11201
11274
  await mkdir(input.target, { recursive: true });
11202
11275
  await writeTree(input.target, await renderTemplates(input.templatesRoot, context));
11203
11276
  });
11204
- written = true;
11205
- if (input.install) await runInstallSteps(input, run, context.isGateway);
11206
- else {
11207
- steps.skip("Dependencies installed");
11208
- steps.skip("Code formatted");
11209
- steps.skip("Types generated");
11210
- }
11211
- let gitNotice = plan.notice;
11212
- if (plan.init) gitNotice = await runGitSteps(input, run) ?? gitNotice;
11213
- else steps.skip("Committed");
11214
- return {
11215
- installed: input.install,
11216
- gitNotice
11217
- };
11277
+ return context;
11218
11278
  } catch (error) {
11219
- if (!written) {
11220
- await rm(input.target, {
11221
- recursive: true,
11222
- force: true
11223
- });
11224
- if (existed) await mkdir(input.target, { recursive: true });
11225
- }
11279
+ await rm(input.target, {
11280
+ recursive: true,
11281
+ force: true
11282
+ });
11283
+ if (existed) await mkdir(input.target, { recursive: true });
11226
11284
  throw error;
11227
11285
  }
11228
11286
  }
11287
+ async function scaffold(input, run) {
11288
+ const { existed } = await claimTarget(input.target);
11289
+ const parent = dirname(input.target);
11290
+ await mkdir(parent, { recursive: true });
11291
+ const plan = gitPlanFrom(await probeGit(parent), input.git);
11292
+ const context = await writeProject(input, plan, existed);
11293
+ const outcome = input.install ? await runInstallSteps(input, run, context.isGateway) : skipInstallSteps(input.steps);
11294
+ const gitNotice = await gitNoticeFor(input, run, plan);
11295
+ return {
11296
+ installed: outcome.installed,
11297
+ notices: gitNotice === null ? outcome.notices : [...outcome.notices, gitNotice],
11298
+ failed: outcome.failed
11299
+ };
11300
+ }
11229
11301
 
11230
11302
  //#endregion
11231
11303
  //#region src/index.ts
@@ -11242,6 +11314,10 @@ async function main() {
11242
11314
  process$1.stdout.write(`${helpText()}\n`);
11243
11315
  return;
11244
11316
  }
11317
+ if (wantsVersion(argv)) {
11318
+ process$1.stdout.write(`${version}\n`);
11319
+ return;
11320
+ }
11245
11321
  const interactive = isInteractive();
11246
11322
  if (interactive) intro(banner());
11247
11323
  const input = parseInput(argv);
@@ -11257,8 +11333,9 @@ async function main() {
11257
11333
  git: input.git,
11258
11334
  steps: interactive ? clackSteps() : silentSteps()
11259
11335
  }, execRunner);
11260
- if (result.gitNotice !== null) log.warn(result.gitNotice);
11336
+ for (const notice of result.notices) log.warn(notice);
11261
11337
  await reportOutcome(answers, agent, result.installed, interactive);
11338
+ if (result.failed) process$1.exitCode = 1;
11262
11339
  }
11263
11340
  function copyable(label, value) {
11264
11341
  log.message(paint.mute(label));