@stacksjs/buddy 0.70.77 → 0.70.79

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.
@@ -731,6 +731,8 @@ async function runHetznerDeploy(args) {
731
731
  ".git",
732
732
  ".github",
733
733
  ".cache",
734
+ "bin",
735
+ "dist",
734
736
  ".stx",
735
737
  "tmp",
736
738
  "temp",
@@ -1,2 +1,4 @@
1
+ import { confirm } from '@stacksjs/cli';
1
2
  import type { CLI } from '@stacksjs/types';
2
3
  export declare function migrate(buddy: CLI): void;
4
+ declare type FreshGuard = 'allow' | 'confirm' | 'disabled';
@@ -1,6 +1,6 @@
1
1
  import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import process from "node:process";
3
- import { confirm, intro, log, onUnknownSubcommand, outro } from "@stacksjs/cli";
3
+ import { confirm, intro, log, onUnknownSubcommand, outro, text } from "@stacksjs/cli";
4
4
  import { Action } from "@stacksjs/enums";
5
5
  import { hasTTY, isCI } from "@stacksjs/env";
6
6
  import { appPath, frameworkPath, projectPath } from "@stacksjs/path";
@@ -132,6 +132,37 @@ async function confirmDestructiveMigrations(opts) {
132
132
  }
133
133
  return confirm({ message: "Apply these destructive changes?", initial: !1 });
134
134
  }
135
+ function parseGuardBool(raw) {
136
+ if (raw == null || raw === "")
137
+ return;
138
+ const v = raw.toLowerCase().trim();
139
+ if (v === "1" || v === "true" || v === "yes" || v === "on")
140
+ return !0;
141
+ if (v === "0" || v === "false" || v === "no" || v === "off")
142
+ return !1;
143
+ return;
144
+ }
145
+ function parseFreshGuard(raw) {
146
+ const v = raw?.toLowerCase().trim();
147
+ return v === "allow" || v === "confirm" || v === "disabled" ? v : void 0;
148
+ }
149
+ async function resolveMigrationGuards() {
150
+ const isProd = /^prod/i.test(process.env.APP_ENV || "local");
151
+ let cfg = {};
152
+ try {
153
+ const { awaitConfig } = await import("@stacksjs/config");
154
+ cfg = (await awaitConfig()).database?.safety ?? {};
155
+ } catch (error) {
156
+ log.debug(`[migrate] safety config unavailable, using defaults: ${error instanceof Error ? error.message : String(error)}`);
157
+ }
158
+ const confirmMigrate = parseGuardBool(process.env.DB_MIGRATE_CONFIRM) ?? cfg.confirmMigrate ?? !0, migrateFresh = parseFreshGuard(process.env.DB_MIGRATE_FRESH) ?? cfg.migrateFresh ?? (isProd ? "disabled" : "allow");
159
+ return { confirmMigrate, migrateFresh };
160
+ }
161
+ function currentDatabaseLabel() {
162
+ if ((process.env.DB_CONNECTION || "sqlite").toLowerCase() === "sqlite")
163
+ return process.env.DB_DATABASE_PATH || "database/stacks.sqlite";
164
+ return process.env.DB_DATABASE || "stacks";
165
+ }
135
166
  export function migrate(buddy) {
136
167
  const descriptions = {
137
168
  migrate: "Migrates your database",
@@ -172,6 +203,19 @@ export function migrate(buddy) {
172
203
  await outro("Diff complete \u2014 no changes applied.", { startTime: perf, useSeconds: !0 });
173
204
  process.exit(ExitCode.Success);
174
205
  }
206
+ if ((await resolveMigrationGuards()).confirmMigrate && !options.force)
207
+ if (isCI || !hasTTY)
208
+ log.debug("[migrate] confirmMigrate guard skipped \u2014 non-interactive environment.");
209
+ else {
210
+ const APP_ENV = process.env.APP_ENV || "local";
211
+ if (!await confirm({
212
+ message: `Run migrations against the ${APP_ENV} database "${currentDatabaseLabel()}"?`,
213
+ initial: !0
214
+ })) {
215
+ await outro("Migration cancelled \u2014 no changes applied.", { startTime: perf, useSeconds: !0 });
216
+ process.exit(ExitCode.Success);
217
+ }
218
+ }
175
219
  const lock = acquireMigrationLock();
176
220
  if (!lock.acquired) {
177
221
  log.error("Another migration is already running (.stacks/migrations.lock exists). Wait for it to finish, or remove the lockfile if it is stale.");
@@ -226,7 +270,7 @@ export function migrate(buddy) {
226
270
  });
227
271
  process.exit(ExitCode.Success);
228
272
  });
229
- 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("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
273
+ 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) => {
230
274
  log.debug("Running `buddy migrate:fresh` ...", options);
231
275
  const perf = await intro("buddy migrate:fresh"), validation = validateModelsExist();
232
276
  if (!validation.valid) {
@@ -235,6 +279,28 @@ export function migrate(buddy) {
235
279
  `);
236
280
  process.exit(ExitCode.FatalError);
237
281
  }
282
+ const guards = await resolveMigrationGuards(), dbLabel = currentDatabaseLabel(), APP_ENV = process.env.APP_ENV || "local";
283
+ if (guards.migrateFresh === "disabled") {
284
+ log.error(`\`buddy migrate:fresh\` is disabled by your migration safety guards (it DROPS every table).
285
+ Target: ${APP_ENV} database "${dbLabel}"
286
+ To allow it, set database.safety.migrateFresh to 'allow' in config/database.ts,
287
+ or run once with: DB_MIGRATE_FRESH=allow ./buddy migrate:fresh`);
288
+ await outro('migrate:fresh refused \u2014 the migrateFresh guard is set to "disabled".', { startTime: perf, useSeconds: !0 });
289
+ process.exit(ExitCode.FatalError);
290
+ }
291
+ if (!(guards.migrateFresh === "allow" && options.force === !0)) {
292
+ if (isCI || !hasTTY) {
293
+ const hint = guards.migrateFresh === "confirm" ? 'Guard is "confirm": migrate:fresh must be run interactively.' : "Re-run with --force to drop the database non-interactively.";
294
+ log.error(`Refusing to drop the ${APP_ENV} database "${dbLabel}" in a non-interactive environment. ${hint}`);
295
+ await outro("migrate:fresh cancelled.", { startTime: perf, useSeconds: !0 });
296
+ process.exit(ExitCode.FatalError);
297
+ }
298
+ log.warn(`This will DROP ALL TABLES in the ${APP_ENV} database "${dbLabel}" and rebuild them from scratch. All data will be lost.`);
299
+ if ((await text({ message: `Type the database name "${dbLabel}" to confirm (blank to cancel):` })).trim() !== dbLabel) {
300
+ await outro("migrate:fresh cancelled \u2014 confirmation did not match.", { startTime: perf, useSeconds: !0 });
301
+ process.exit(ExitCode.Success);
302
+ }
303
+ }
238
304
  const result = await runAction(Action.MigrateFresh, options);
239
305
  if (result.isErr)
240
306
  log.error("Model migrations failed \u2014 applying auth/notification/RBAC table guarantees before exiting.");
@@ -81,6 +81,11 @@ export function serve(buddy) {
81
81
  log.error(`API proxy to ${apiBase} failed: ${error.message}`);
82
82
  return new Response("Bad Gateway", { status: 502 });
83
83
  }
84
+ if (existsSync(join(process.cwd(), "resources/views/blog.stx"))) {
85
+ const { renderBlogFeed } = await import("@stacksjs/actions/blog"), feed = await renderBlogFeed(req);
86
+ if (feed)
87
+ return feed;
88
+ }
84
89
  globalThis.__stxServeSearch = url.search;
85
90
  globalThis.__stxServeCookies = parseCookies(req);
86
91
  return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
- "version": "0.70.77",
4
+ "version": "0.70.79",
5
5
  "description": "Meet Buddy. The Stacks runtime.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -135,7 +135,7 @@
135
135
  "@stacksjs/ui": "^0.70.23",
136
136
  "@stacksjs/utils": "^0.70.23",
137
137
  "@stacksjs/validation": "^0.70.23",
138
- "@stacksjs/ts-cloud": "^0.7.16"
138
+ "@stacksjs/ts-cloud": "^0.7.17"
139
139
  },
140
140
  "devDependencies": {
141
141
  "better-dx": "^0.2.16"