@stacksjs/buddy 0.70.113 → 0.70.114

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.
@@ -10,7 +10,7 @@ export function extension(buddy) {
10
10
  const pkg = await Bun.file(`${process.cwd()}/package.json`).json().catch(() => ({}));
11
11
  return { config, version: pkg.version ?? "0.0.0" };
12
12
  };
13
- buddy.command("extension:build", "Build the browser extension (Chrome + Firefox) from config/extension.ts").option("--target <target>", "Build a single target (chrome | firefox); omit to build all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
13
+ buddy.command("extension:build", "Build the browser extension (Chrome + Firefox + Safari) from config/extension.ts").option("--target <target>", "Build a single target (chrome | firefox | safari); omit to build all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
14
14
  const { buildExtension, buildAllTargets } = await import("@stacksjs/browser-extension"), { config, version } = await load(), v = options.version ?? version;
15
15
  if (options.target) {
16
16
  const { outdir } = await buildExtension(config, { target: options.target, version: v });
@@ -68,11 +68,47 @@ console.log('[${name}] popup')
68
68
  `);
69
69
  log.info("Next: add icons under public/icons, then `buddy extension:build`.");
70
70
  });
71
- buddy.command("extension:package", "Build + zip the browser extension into store-ready archives").option("--target <target>", "Package a single target (chrome | firefox); omit to package all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
71
+ buddy.command("extension:package", "Build + zip the browser extension into store-ready archives").option("--target <target>", "Package a single target (chrome | firefox | safari); omit to package all").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
72
72
  const { packageExtension } = await import("@stacksjs/browser-extension"), { config, version } = await load(), v = options.version ?? version, targets = options.target ? [options.target] : config.targets ?? ["chrome", "firefox"];
73
73
  for (const target of targets) {
74
74
  const out = await packageExtension(config, { target, version: v });
75
75
  log.success(`Packaged ${config.name} (${target}) \u2192 ${out}`);
76
76
  }
77
77
  });
78
+ buddy.command("extension:safari:init", "Scaffold the Safari container app (Xcode project) from the template").option("--bundle-id <id>", "Base bundle identifier (defaults to config safariBundleId)").option("--dir <dir>", "Output directory for the Xcode project (default safari)").option("--force", "Overwrite existing scaffold files").option("--team-id <id>", "Apple Developer team used for signing").action(async (options) => {
79
+ const { scaffoldSafariApp } = await import("@stacksjs/browser-extension"), { config } = await load(), { dir, written, skipped } = await scaffoldSafariApp(config, {
80
+ bundleId: options.bundleId,
81
+ dir: options.dir,
82
+ force: Boolean(options.force),
83
+ teamId: options.teamId
84
+ });
85
+ log.success(`Scaffolded the Safari container app \u2192 ${dir} (${written.length} files)`);
86
+ if (skipped.length)
87
+ log.info(`kept ${skipped.length} existing files (use --force to overwrite)`);
88
+ });
89
+ buddy.command("extension:safari:app", "Build the extension, sync it into the appex, and xcodebuild the container app").option("--release", "Build the Release configuration (default Debug)").option("--signed", "Allow code signing (needs an Apple Development identity)").option("--skip-xcodebuild", "Only build + sync the extension payload").option("--version <version>", "Override the extension version (defaults to package.json)").action(async (options) => {
90
+ const { buildSafariApp } = await import("@stacksjs/browser-extension"), { config, version } = await load(), { appPath, resources } = await buildSafariApp(config, {
91
+ version: options.version ?? version,
92
+ release: Boolean(options.release),
93
+ signed: Boolean(options.signed),
94
+ skipXcodebuild: Boolean(options.skipXcodebuild)
95
+ });
96
+ if (appPath) {
97
+ log.success(`Built ${appPath}`);
98
+ log.info("Open the app once, then enable the extension in Safari > Settings > Extensions.");
99
+ } else
100
+ log.success(`Extension payload synced \u2192 ${resources}`);
101
+ });
102
+ buddy.command("extension:safari:publish", "Archive and validate or upload the Safari app to App Store Connect").option("--version <version>", "Override the marketing version (defaults to package.json)").option("--build-number <number>", "CFBundleVersion (defaults to GITHUB_RUN_NUMBER or Unix time)").option("--team-id <id>", "Apple Developer team (defaults to config safariTeamId)").option("--api-key-id <id>", "App Store Connect API key ID").option("--api-issuer-id <id>", "App Store Connect API issuer ID").option("--api-key-path <path>", "Path to the App Store Connect AuthKey_*.p8 file").option("--validate-only", "Create and validate the archive without uploading it").action(async (options) => {
103
+ const { publishSafariApp } = await import("@stacksjs/browser-extension"), { config, version } = await load(), result = await publishSafariApp(config, {
104
+ version: options.version ?? version,
105
+ buildNumber: options.buildNumber,
106
+ teamId: options.teamId,
107
+ keyId: options.apiKeyId,
108
+ issuerId: options.apiIssuerId,
109
+ keyPath: options.apiKeyPath,
110
+ validateOnly: Boolean(options.validateOnly)
111
+ });
112
+ log.success(options.validateOnly ? `Validated Safari archive ${result.archivePath} (build ${result.buildNumber})` : `Uploaded Safari build ${result.buildNumber} to App Store Connect`);
113
+ });
78
114
  }
@@ -1,17 +1,24 @@
1
1
  import process from "node:process";
2
2
  import { runAction } from "@stacksjs/actions";
3
3
  import { intro, log, onUnknownSubcommand, outro } from "@stacksjs/cli";
4
+ import { hasTTY, isCI } from "@stacksjs/env";
4
5
  import { Action } from "@stacksjs/enums";
5
6
  import { ExitCode } from "@stacksjs/types";
6
7
  export function fresh(buddy) {
7
8
  const descriptions = {
8
9
  fresh: "Re-installs your npm dependencies",
9
10
  project: "Target a specific project",
11
+ force: "Skip the confirmation prompt (required in CI/non-interactive shells)",
10
12
  verbose: "Enable verbose output"
11
13
  };
12
- buddy.command("fresh", descriptions.fresh).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
14
+ buddy.command("fresh", descriptions.fresh).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-f, --force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
13
15
  log.debug("Running `buddy fresh` ...", options);
14
- if (!buddy.isForce && !buddy.isNoInteraction) {
16
+ const skipConfirm = options.force === !0 || Boolean(buddy.isForce) || Boolean(buddy.isNoInteraction);
17
+ if (!skipConfirm && (isCI || !hasTTY || !process.stdin.isTTY)) {
18
+ log.syncError("Refusing to run `buddy fresh` from a non-interactive shell without confirmation.");
19
+ log.fatal(" \u27A1\uFE0F Re-run with `--force` to proceed: `buddy fresh --force`");
20
+ }
21
+ if (!skipConfirm) {
15
22
  const { confirm } = await import("@stacksjs/cli");
16
23
  if (!await confirm({
17
24
  message: "This will remove and reinstall all dependencies. Continue?",
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
  import {
3
+ createFactory,
3
4
  createMiddleware,
4
5
  createMigration,
5
6
  createModel,
@@ -45,16 +46,17 @@ export function make(buddy) {
45
46
  resource: "Create a new API resource",
46
47
  name: "The name of the action",
47
48
  queue: "Make queue migration",
49
+ queueTable: "Create the queue jobs table migration",
48
50
  stack: "Create a new stack",
49
51
  certificate: "Create a new SSL Certificate",
50
52
  select: "What are you trying to make?",
51
53
  project: "Target a specific project",
52
54
  verbose: "Enable verbose output"
53
55
  };
54
- buddy.command("make [make]", "The make command").option("-a, --action [action]", descriptions.action, { default: !1 }).option("-c, --component [component]", descriptions.component, { default: !1 }).option("-d, --database [database]", descriptions.database, { default: !1 }).option("-f, --factory [factory]", descriptions.factory, { default: !1 }).option("-f, --function [function]", descriptions.function, { default: !1 }).option("-l, --language [language]", descriptions.language, { default: !1 }).option("-m, --model [model]", descriptions.model, { default: !1 }).option("-mw, --middleware [middleware]", descriptions.middleware, { default: !1 }).option("-p, --page [page]", descriptions.page, { default: !1 }).option("-m, --migration [migration]", descriptions.migration, { default: !1 }).option("-n, --notification [notification]", descriptions.notification, { default: !1 }).option("-qt, --queue-table", descriptions.queue, { default: !1 }).option("-s, --stack [stack]", descriptions.stack, { default: !1 }).option("--dry-run", "Preview the files that would be generated without writing", { default: !1 }).option("--with-validation", "Include a validation rules block in the generated stub", { default: !1 }).option("--with-auth", "Include auth-aware boilerplate in the generated stub", { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (make, options) => {
56
+ buddy.command("make [make]", "The make command").option("-a, --action [action]", descriptions.action, { default: !1 }).option("-c, --component [component]", descriptions.component, { default: !1 }).option("-d, --database [database]", descriptions.database, { default: !1 }).option("-f, --factory [factory]", descriptions.factory, { default: !1 }).option("-fn, --function [function]", descriptions.function, { default: !1 }).option("-l, --language [language]", descriptions.language, { default: !1 }).option("-m, --model [model]", descriptions.model, { default: !1 }).option("-mw, --middleware [middleware]", descriptions.middleware, { default: !1 }).option("-p, --page [page]", descriptions.page, { default: !1 }).option("-mg, --migration [migration]", descriptions.migration, { default: !1 }).option("-n, --notification [notification]", descriptions.notification, { default: !1 }).option("-qt, --queue-table", descriptions.queue, { default: !1 }).option("-s, --stack [stack]", descriptions.stack, { default: !1 }).option("--dry-run", "Preview the files that would be generated without writing", { default: !1 }).option("--with-validation", "Include a validation rules block in the generated stub", { default: !1 }).option("--with-auth", "Include auth-aware boilerplate in the generated stub", { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (make, options) => {
55
57
  log.debug("Running `buddy make` ...", options);
56
58
  if (!buddy.args[0]) {
57
- log.error("You need to specify a name. Read more about the documentation here.");
59
+ console.error("You need to specify a name. Read more about the documentation here.");
58
60
  process.exit(ExitCode.FatalError);
59
61
  }
60
62
  setDryRun(Boolean(options.dryRun || options["dry-run"]));
@@ -115,7 +117,14 @@ export function make(buddy) {
115
117
  case "stack":
116
118
  await makeStack(options);
117
119
  break;
118
- default:
120
+ case "factory":
121
+ await createFactory(options);
122
+ break;
123
+ default: {
124
+ console.error(`Unknown make subcommand: ${make}`);
125
+ console.error("Valid subcommands: action, certificate, command, component, database, factory, function, job, language, mail, middleware, migration, model, notification, page, policy, queue-table, resource, stack");
126
+ process.exit(ExitCode.InvalidArgument);
127
+ }
119
128
  }
120
129
  }
121
130
  await invoke(options);
@@ -127,7 +136,7 @@ export function make(buddy) {
127
136
  name = name ?? options.name;
128
137
  options.name = name;
129
138
  if (!name) {
130
- log.error("You need to specify a name. Read more about the documentation here.");
139
+ console.error("You need to specify a name. Read more about the documentation here.");
131
140
  process.exit(ExitCode.FatalError);
132
141
  }
133
142
  setDryRun(Boolean(options.dryRun || options["dry-run"]));
@@ -140,7 +149,7 @@ export function make(buddy) {
140
149
  buddy.command("scaffold:crud [name]", "Generate model, migration, and CRUD actions").alias("make:crud").alias("make:scaffold").option("-n, --name [name]", "Resource name (PascalCase)", { default: !1 }).option("-f, --fields [fields]", "Comma-separated field list, e.g. title:string,body:text,published:boolean", { default: "" }).option("--dry-run", "Preview the generated files without writing", { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).example("buddy scaffold:crud Post --fields=title:string,body:text,published:boolean").action(async (name, options) => {
141
150
  name = name ?? options.name;
142
151
  if (!name) {
143
- log.error("scaffold:crud requires a resource name. Example: buddy scaffold:crud Post --fields=title:string,body:text");
152
+ console.error("scaffold:crud requires a resource name. Example: buddy scaffold:crud Post --fields=title:string,body:text");
144
153
  process.exit(ExitCode.FatalError);
145
154
  }
146
155
  const { scaffoldCrud } = await import("@stacksjs/actions");
@@ -148,7 +157,7 @@ export function make(buddy) {
148
157
  try {
149
158
  await scaffoldCrud(name, options);
150
159
  } catch (err) {
151
- log.error("scaffold:crud failed:", err);
160
+ console.error("scaffold:crud failed:", err);
152
161
  process.exit(ExitCode.FatalError);
153
162
  }
154
163
  });
@@ -158,8 +167,8 @@ export function make(buddy) {
158
167
  name = name ?? options.name;
159
168
  options.name = name;
160
169
  if (!name) {
161
- log.error("You need to specify a command name.");
162
- log.info("Example: buddy make:command SendEmails");
170
+ console.error("You need to specify a command name.");
171
+ console.error("Example: buddy make:command SendEmails");
163
172
  process.exit(ExitCode.FatalError);
164
173
  }
165
174
  if (!await makeCommand(options)) {
@@ -180,7 +189,7 @@ export function make(buddy) {
180
189
  name = name ?? options.name;
181
190
  options.name = name;
182
191
  if (!name) {
183
- log.error("You need to specify a name. Read more about the documentation here.");
192
+ console.error("You need to specify a name. Read more about the documentation here.");
184
193
  process.exit(ExitCode.FatalError);
185
194
  }
186
195
  await makeComponent(options);
@@ -190,25 +199,32 @@ export function make(buddy) {
190
199
  name = name ?? options.name;
191
200
  options.name = name;
192
201
  if (!name) {
193
- log.error("You need to specify a database name via the `--name` option, or as the command\u2019s argument.");
194
- log.info("Example: `buddy make:database my-cool-database`");
195
- log.info("Or: `buddy make:database --name=my-cool-database`");
196
- log.info("Read more about the documentation here: https://stacksjs.com/docs/make/database");
202
+ console.error("You need to specify a database name via the `--name` option, or as the command\u2019s argument.");
203
+ console.error("Example: `buddy make:database my-cool-database`");
204
+ console.error("Or: `buddy make:database --name=my-cool-database`");
205
+ console.error("Read more about the documentation here: https://stacksjs.com/docs/make/database");
197
206
  process.exit(ExitCode.FatalError);
198
207
  }
199
208
  makeDatabase(options);
200
209
  });
201
- buddy.command("make:factory [name]", descriptions.factory).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action((name, options) => {
210
+ buddy.command("make:factory [name]", descriptions.factory).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
202
211
  log.debug("Running `buddy make:factory` ...", options);
203
212
  name = name ?? options.name;
204
213
  options.name = name;
205
214
  if (!name) {
206
- log.error("You need to specify a name. Read more about the documentation here.");
215
+ console.error("You need to specify a name. Read more about the documentation here.");
207
216
  process.exit(ExitCode.FatalError);
208
217
  }
218
+ await createFactory(options);
209
219
  });
210
- buddy.command("make:function [name]", descriptions.function).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
220
+ buddy.command("make:function [name]", descriptions.function).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
211
221
  log.debug("Running `buddy make:function` ...", options);
222
+ name = name ?? options.name;
223
+ options.name = name;
224
+ if (!name) {
225
+ console.error("You need to specify a name. Read more about the documentation here.");
226
+ process.exit(ExitCode.FatalError);
227
+ }
212
228
  await makeFunction(options);
213
229
  });
214
230
  buddy.command("make:lang [name]", descriptions.language).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
@@ -216,26 +232,27 @@ export function make(buddy) {
216
232
  name = name ?? options.name;
217
233
  options.name = name;
218
234
  if (!name) {
219
- log.error("You need to specify a name. Read more about the documentation here.");
235
+ console.error("You need to specify a name. Read more about the documentation here.");
220
236
  process.exit(ExitCode.FatalError);
221
237
  }
222
238
  await makeLanguage(options);
223
239
  });
224
- buddy.command("make:migration [name]", descriptions.migration).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action((name, options) => {
240
+ buddy.command("make:migration [name]", descriptions.migration).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
225
241
  log.debug("Running `buddy make:migration` ...", options);
226
242
  name = name ?? options.name;
227
243
  options.name = name;
228
244
  if (!name) {
229
- log.error("You need to specify a migration name");
245
+ console.error("You need to specify a migration name");
230
246
  process.exit(ExitCode.FatalError);
231
247
  }
248
+ await createMigration(options);
232
249
  });
233
250
  buddy.command("make:model [name]", descriptions.model).option("-n, --name [name]", descriptions.name, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
234
251
  log.debug("Running `buddy make:model` ...", options);
235
252
  name = name ?? options.name;
236
253
  options.name = name;
237
254
  if (!name) {
238
- log.error("You need to specify a model name");
255
+ console.error("You need to specify a model name");
239
256
  process.exit(ExitCode.FatalError);
240
257
  }
241
258
  await createModel(options);
@@ -246,7 +263,7 @@ export function make(buddy) {
246
263
  name = name ?? options.name;
247
264
  options.name = name;
248
265
  if (!name) {
249
- log.error("You need to specify a name (e.g. `buddy make:mail OrderShipped`).");
266
+ console.error("You need to specify a name (e.g. `buddy make:mail OrderShipped`).");
250
267
  process.exit(ExitCode.FatalError);
251
268
  }
252
269
  await makeMail(options);
@@ -262,7 +279,7 @@ export function make(buddy) {
262
279
  name = name ?? options.name;
263
280
  options.name = name;
264
281
  if (!name) {
265
- log.error("You need to specify a name. Read more about the documentation here.");
282
+ console.error("You need to specify a name. Read more about the documentation here.");
266
283
  process.exit(ExitCode.FatalError);
267
284
  }
268
285
  if (!await createNotification(options)) {
@@ -284,8 +301,8 @@ export function make(buddy) {
284
301
  name = name ?? options.name;
285
302
  options.name = name;
286
303
  if (!name) {
287
- log.error("You need to specify a policy name.");
288
- log.info("Example: buddy make:policy PostPolicy");
304
+ console.error("You need to specify a policy name.");
305
+ console.error("Example: buddy make:policy PostPolicy");
289
306
  process.exit(ExitCode.FatalError);
290
307
  }
291
308
  if (!await makePolicy(options)) {
@@ -307,8 +324,8 @@ export function make(buddy) {
307
324
  name = name ?? options.name;
308
325
  options.name = name;
309
326
  if (!name) {
310
- log.error("You need to specify a resource name.");
311
- log.info("Example: buddy make:resource UserResource");
327
+ console.error("You need to specify a resource name.");
328
+ console.error("Example: buddy make:resource UserResource");
312
329
  process.exit(ExitCode.FatalError);
313
330
  }
314
331
  if (!await makeResource(options)) {
@@ -324,7 +341,7 @@ export function make(buddy) {
324
341
  });
325
342
  process.exit(ExitCode.Success);
326
343
  });
327
- buddy.command("make:queue-table", descriptions.migration).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
344
+ buddy.command("make:queue-table", descriptions.queueTable).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
328
345
  log.debug("Running `buddy make queue:table` ...", options);
329
346
  await makeQueueTable();
330
347
  });
@@ -333,7 +350,7 @@ export function make(buddy) {
333
350
  name = name ?? options.name;
334
351
  options.name = name;
335
352
  if (!name) {
336
- log.error("You need to specify a name. Read more about the documentation here.");
353
+ console.error("You need to specify a name. Read more about the documentation here.");
337
354
  process.exit(ExitCode.FatalError);
338
355
  }
339
356
  await makeStack(options);
@@ -343,7 +360,7 @@ export function make(buddy) {
343
360
  name = name ?? options.name;
344
361
  options.name = name;
345
362
  if (!name) {
346
- log.error("You need to specify a name. Read more about the documentation here.");
363
+ console.error("You need to specify a name. Read more about the documentation here.");
347
364
  process.exit(ExitCode.FatalError);
348
365
  }
349
366
  await makePage(options);
@@ -354,8 +371,8 @@ export function make(buddy) {
354
371
  name = name ?? options.name;
355
372
  options.name = name;
356
373
  if (!name) {
357
- log.error("You need to specify a job name.");
358
- log.info("Example: buddy make:job SendWelcomeEmail");
374
+ console.error("You need to specify a job name.");
375
+ console.error("Example: buddy make:job SendWelcomeEmail");
359
376
  process.exit(ExitCode.FatalError);
360
377
  }
361
378
  if (!await makeJob(options)) {
@@ -181,6 +181,7 @@ function currentDatabaseLabel() {
181
181
  export function migrate(buddy) {
182
182
  const descriptions = {
183
183
  migrate: "Migrates your database",
184
+ fresh: "Drop all tables and re-run every migration (destroys all data)",
184
185
  project: "Target a specific project",
185
186
  verbose: "Enable verbose output",
186
187
  auth: "Also migrate auth tables (oauth_clients, oauth_access_tokens, oauth_refresh_tokens, password_resets)",
@@ -291,7 +292,7 @@ export function migrate(buddy) {
291
292
  });
292
293
  process.exit(ExitCode.Success);
293
294
  });
294
- buddy.command("migrate:fresh", descriptions.migrate).alias("db:fresh").option("-d, --diff", "Show the SQL that would be run", { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-s, --seed", "Run database seeders after migration", { default: !1 }).option("-a, --auth", descriptions.auth, { default: !0 }).option("--no-auth", "Skip auth/oauth table migrations").option("-f, --force", 'Skip the drop-database confirmation (only honored when the migrateFresh guard is "allow")', { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
295
+ buddy.command("migrate:fresh", descriptions.fresh).alias("db:fresh").option("-d, --diff", "Show the SQL that would be run", { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("-s, --seed", "Run database seeders after migration", { default: !1 }).option("-a, --auth", descriptions.auth, { default: !0 }).option("--no-auth", "Skip auth/oauth table migrations").option("-f, --force", 'Skip the drop-database confirmation (only honored when the migrateFresh guard is "allow")', { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
295
296
  log.debug("Running `buddy migrate:fresh` ...", options);
296
297
  const perf = await intro("buddy migrate:fresh"), validation = validateModelsExist();
297
298
  if (!validation.valid) {
@@ -2,6 +2,7 @@ import type { CLI, CliOptions } from '@stacksjs/types';
2
2
  export declare function setup(buddy: CLI): void;
3
3
  export declare function ensurePantryInstalled(): Promise<void>;
4
4
  export declare function ensurePantryDependencies(cwd: string): Promise<void>;
5
+ export declare function ensureNodeDependencies(cwd: string): Promise<void>;
5
6
  export declare function ensureAppKey(cwd: string): Promise<void>;
6
7
  export declare function pantryDatabasePackage(connection: string): DatabasePackage | undefined;
7
8
  /**
@@ -14,7 +14,7 @@ function getTimeoutMs(envVar, fallbackMs) {
14
14
  return value;
15
15
  return fallbackMs;
16
16
  }
17
- const PANTRY_CHECK_TIMEOUT_MS = getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS", 15000), PANTRY_INSTALL_TIMEOUT_MS = getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS", 600000), PANTRY_DEPENDENCIES_TIMEOUT_MS = getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS", 1200000), KEYGEN_TIMEOUT_MS = getTimeoutMs("KEYGEN_TIMEOUT_MS", 120000), AWS_CONFIG_TIMEOUT_MS = getTimeoutMs("AWS_CONFIG_TIMEOUT_MS", 900000);
17
+ const PANTRY_CHECK_TIMEOUT_MS = getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS", 15000), PANTRY_INSTALL_TIMEOUT_MS = getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS", 600000), PANTRY_DEPENDENCIES_TIMEOUT_MS = getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS", 1200000), BUN_INSTALL_TIMEOUT_MS = getTimeoutMs("BUN_INSTALL_TIMEOUT_MS", 600000), KEYGEN_TIMEOUT_MS = getTimeoutMs("KEYGEN_TIMEOUT_MS", 120000), AWS_CONFIG_TIMEOUT_MS = getTimeoutMs("AWS_CONFIG_TIMEOUT_MS", 900000);
18
18
  export function setup(buddy) {
19
19
  const descriptions = {
20
20
  setup: "This command ensures your project is setup correctly",
@@ -103,6 +103,25 @@ export async function ensurePantryDependencies(cwd) {
103
103
  handleError(result.error);
104
104
  process.exit(ExitCode.FatalError);
105
105
  }
106
+ export async function ensureNodeDependencies(cwd) {
107
+ if (existsSync(join(cwd, "node_modules"))) {
108
+ log.success("node_modules existed, skipping bun install");
109
+ return;
110
+ }
111
+ log.info("Running bun install...");
112
+ const result = await runCommand("bun install", {
113
+ cwd,
114
+ timeoutMs: BUN_INSTALL_TIMEOUT_MS
115
+ }).catch((error) => {
116
+ handleError(error);
117
+ process.exit(ExitCode.FatalError);
118
+ });
119
+ if (result.isErr) {
120
+ handleError(result.error);
121
+ process.exit(ExitCode.FatalError);
122
+ }
123
+ log.success("Installed node dependencies");
124
+ }
106
125
  function hasAppKey(cwd) {
107
126
  const envPath = join(cwd, ".env");
108
127
  if (!existsSync(envPath))
@@ -124,26 +143,53 @@ export async function ensureAppKey(cwd) {
124
143
  }
125
144
  log.success("Generated application key");
126
145
  }
146
+ async function runInitialMigration(cwd) {
147
+ const appEnv = (process.env.APP_ENV || process.env.NODE_ENV || "local").toLowerCase();
148
+ if (!["local", "development", "dev", "test"].includes(appEnv)) {
149
+ log.info(`Skipping initial migration in the ${appEnv} environment`);
150
+ return;
151
+ }
152
+ log.info("Running initial database migration...");
153
+ try {
154
+ const result = await runAction(Action.Migrate, { cwd });
155
+ if (result.isErr) {
156
+ log.warn("Initial migration did not complete - you can run it later via ./buddy migrate");
157
+ log.debug(result.error);
158
+ return;
159
+ }
160
+ log.success("Database is migrated");
161
+ } catch (error) {
162
+ log.warn("Initial migration did not complete - you can run it later via ./buddy migrate");
163
+ log.debug(error);
164
+ }
165
+ }
127
166
  async function initializeProject(options) {
128
167
  const cwd = options.cwd || p.projectPath();
129
168
  await ensurePantryDependencies(cwd);
169
+ await ensureNodeDependencies(cwd);
130
170
  await ensureEnvIsSet(options);
131
171
  if (!options.skipKeygen)
132
172
  await ensureAppKey(cwd);
173
+ await runInitialMigration(cwd);
133
174
  if (!options.skipAws) {
134
175
  log.info("Ensuring AWS is connected...");
135
- const awsResult = await runCommand("./buddy configure:aws", {
136
- cwd,
137
- timeoutMs: AWS_CONFIG_TIMEOUT_MS
138
- });
139
- if (awsResult.isErr) {
140
- handleError(awsResult.error);
141
- process.exit(ExitCode.FatalError);
176
+ try {
177
+ const awsResult = await runCommand("./buddy configure:aws", {
178
+ cwd,
179
+ timeoutMs: AWS_CONFIG_TIMEOUT_MS
180
+ });
181
+ if (awsResult.isErr) {
182
+ log.warn("AWS not configured - you can do this later via ./buddy configure:aws");
183
+ log.debug(awsResult.error);
184
+ } else
185
+ log.success("Configured AWS");
186
+ } catch (error) {
187
+ log.warn("AWS not configured - you can do this later via ./buddy configure:aws");
188
+ log.debug(error);
142
189
  }
143
- log.success("Configured AWS");
144
190
  }
145
191
  log.success("Project is setup");
146
- log.info("Happy coding! \uD83D\uDC99");
192
+ log.info("Run `./buddy doctor` anytime to check your setup. Happy coding! \uD83D\uDC99");
147
193
  }
148
194
  const DB_CONNECTION_PACKAGES = {
149
195
  postgres: { name: "postgresql.org", version: "^17.10" },
@@ -5,7 +5,7 @@ import { onUnknownSubcommand } from "@stacksjs/cli";
5
5
  export function types(buddy) {
6
6
  const descriptions = {
7
7
  generate: "Generate the types of & for your library/libraries",
8
- fix: "wip",
8
+ fix: "Fix the generated types of & for your library/libraries (not yet implemented)",
9
9
  project: "Target a specific project",
10
10
  verbose: "Enable verbose output"
11
11
  };
@@ -12,6 +12,7 @@ const commandRegistry = {
12
12
  "config:migrate": { path: "./commands/config-migrate.js", exportName: "configMigrate" },
13
13
  configure: { path: "./commands/configure.js", exportName: "configure" },
14
14
  create: { path: "./commands/create.js", exportName: "create" },
15
+ new: { path: "./commands/create.js", exportName: "create" },
15
16
  deploy: { path: "./commands/deploy.js", exportName: "deploy" },
16
17
  dev: { path: "./commands/dev.js", exportName: "dev" },
17
18
  dns: { path: "./commands/dns.js", exportName: "dns" },
@@ -25,6 +26,9 @@ const commandRegistry = {
25
26
  "extension:init": { path: "./commands/extension.js", exportName: "extension" },
26
27
  "extension:build": { path: "./commands/extension.js", exportName: "extension" },
27
28
  "extension:package": { path: "./commands/extension.js", exportName: "extension" },
29
+ "extension:safari:init": { path: "./commands/extension.js", exportName: "extension" },
30
+ "extension:safari:app": { path: "./commands/extension.js", exportName: "extension" },
31
+ "extension:safari:publish": { path: "./commands/extension.js", exportName: "extension" },
28
32
  "dashboard:install": { path: "./commands/features.js", exportName: "features" },
29
33
  "dashboard:uninstall": { path: "./commands/features.js", exportName: "features" },
30
34
  "commerce:install": { path: "./commands/features.js", exportName: "features" },
@@ -45,6 +49,8 @@ const commandRegistry = {
45
49
  install: { path: "./commands/install.js", exportName: "install" },
46
50
  key: { path: "./commands/key.js", exportName: "key" },
47
51
  lint: { path: "./commands/lint.js", exportName: "lint" },
52
+ format: { path: "./commands/lint.js", exportName: "lint" },
53
+ "format:check": { path: "./commands/lint.js", exportName: "lint" },
48
54
  list: { path: "./commands/list.js", exportName: "list" },
49
55
  mail: { path: "./commands/mail.js", exportName: "mailCommands" },
50
56
  "mail:preview": { path: "./commands/mail.js", exportName: "mailCommands" },
@@ -52,7 +58,11 @@ const commandRegistry = {
52
58
  down: { path: "./commands/maintenance.js", exportName: "maintenance" },
53
59
  up: { path: "./commands/maintenance.js", exportName: "maintenance" },
54
60
  status: { path: "./commands/maintenance.js", exportName: "maintenance" },
61
+ "coming-soon": { path: "./commands/maintenance.js", exportName: "maintenance" },
62
+ "coming-soon:status": { path: "./commands/maintenance.js", exportName: "maintenance" },
63
+ launch: { path: "./commands/maintenance.js", exportName: "maintenance" },
55
64
  make: { path: "./commands/make.js", exportName: "make" },
65
+ "scaffold:crud": { path: "./commands/make.js", exportName: "make" },
56
66
  migrate: { path: "./commands/migrate.js", exportName: "migrate" },
57
67
  "migrate:fresh": { path: "./commands/migrate.js", exportName: "migrate" },
58
68
  "migrate:switch": { path: "./commands/migrate.js", exportName: "migrate" },
@@ -74,8 +84,11 @@ const commandRegistry = {
74
84
  release: { path: "./commands/release.js", exportName: "release" },
75
85
  route: { path: "./commands/route.js", exportName: "route" },
76
86
  saas: { path: "./commands/saas.js", exportName: "saas" },
87
+ "stripe:setup": { path: "./commands/saas.js", exportName: "saas" },
77
88
  schedule: { path: "./commands/schedule.js", exportName: "schedule" },
78
89
  search: { path: "./commands/search.js", exportName: "search" },
90
+ "search-engine:update": { path: "./commands/search.js", exportName: "search" },
91
+ "search-engine:settings": { path: "./commands/search.js", exportName: "search" },
79
92
  seed: { path: "./commands/seed.js", exportName: "seed" },
80
93
  "seed:roles": { path: "./commands/seed.js", exportName: "seed" },
81
94
  "roles:seed": { path: "./commands/seed.js", exportName: "seed" },
@@ -177,12 +190,9 @@ export function getCommandsToLoad(args) {
177
190
  const baseCommand = requestedCommand.split(":")[0];
178
191
  if (baseCommand === "list")
179
192
  return ["list", ...Object.keys(commandRegistry).filter((k) => k !== "list")];
193
+ if (commandRegistry[requestedCommand])
194
+ return [requestedCommand];
180
195
  if (commandRegistry[baseCommand])
181
196
  return [baseCommand];
182
- if (requestedCommand.includes(":")) {
183
- const namespace = baseCommand;
184
- if (commandRegistry[namespace])
185
- return [namespace];
186
- }
187
197
  return Object.keys(commandRegistry);
188
198
  }