@stacksjs/buddy 0.70.256 → 0.70.258
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/commands/deploy.d.ts +78 -4
- package/dist/commands/deploy.js +91 -5
- package/dist/commands/generate.js +14 -0
- package/dist/commands/lint.js +39 -1
- package/dist/commands/migrate.js +29 -14
- package/dist/production-server.d.ts +1 -35
- package/package.json +44 -44
|
@@ -43,6 +43,74 @@ export declare function resolveDeployEnvValues(environment: 'production' | 'stag
|
|
|
43
43
|
* the process env).
|
|
44
44
|
*/
|
|
45
45
|
export declare function mergeSiteDeployEnv(sites: Record<string, any>, resolvedDeployEnv: Record<string, string>): Record<string, any>;
|
|
46
|
+
/**
|
|
47
|
+
* The SQLite file a site will open, relative to its release root — or `null`
|
|
48
|
+
* when the site is not on SQLite, or its database already lives outside the
|
|
49
|
+
* release tree (an absolute `DB_DATABASE_PATH` is the operator saying "I have
|
|
50
|
+
* placed this somewhere persistent myself").
|
|
51
|
+
*
|
|
52
|
+
* Mirrors config/database.ts' resolution order. `DB_DATABASE` is only a path
|
|
53
|
+
* when it looks like one — for every other driver it is a database NAME, and
|
|
54
|
+
* linking `stacks` as a file would be nonsense.
|
|
55
|
+
*/
|
|
56
|
+
export declare function siteSqlitePath(siteEnv?: Record<string, any>): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Does the loaded ts-cloud actually keep persistent state the way
|
|
59
|
+
* {@link applyPersistentStatePaths} assumes?
|
|
60
|
+
*
|
|
61
|
+
* Declaring shared paths against a ts-cloud that lacks these is worse than not
|
|
62
|
+
* declaring them at all — it is the difference between "the database dies with
|
|
63
|
+
* its release" and "the deploy places an empty database over the live one and
|
|
64
|
+
* migrates into it". So this probes the two capabilities the declaration
|
|
65
|
+
* depends on and lets the caller refuse the deploy.
|
|
66
|
+
*
|
|
67
|
+
* Probed by generating a script and reading what it DOES, not by a version
|
|
68
|
+
* number or an exported symbol name: a version range goes stale the moment
|
|
69
|
+
* someone installs a fork, and an internal helper can be renamed without
|
|
70
|
+
* changing behaviour.
|
|
71
|
+
*
|
|
72
|
+
* @param buildSiteDeployScript ts-cloud's script builder, as loaded at runtime.
|
|
73
|
+
*/
|
|
74
|
+
export declare function tsCloudPersistentStateSupport(buildSiteDeployScript: unknown): { ok: boolean, missing: string[] };
|
|
75
|
+
/**
|
|
76
|
+
* Where a project's app database lives on the box: one directory per project,
|
|
77
|
+
* outside every site's release tree.
|
|
78
|
+
*
|
|
79
|
+
* Deliberately derived from the project slug ALONE. ts-cloud installs each site
|
|
80
|
+
* under `/var/www/<slug>-<site>`, so anything derived from the site name gives
|
|
81
|
+
* each site a database of its own — which is exactly how `main` and `api` ended
|
|
82
|
+
* up on separate files, with only `main` ever migrated. Two sites of one project
|
|
83
|
+
* cannot disagree about a path that does not mention which site is asking.
|
|
84
|
+
*/
|
|
85
|
+
export declare function projectDatabaseTarget(slug: string, relativePath: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Declare the paths a Stacks server-app WRITES at runtime as ts-cloud shared
|
|
88
|
+
* paths, so they are symlinked into each release instead of dying with it.
|
|
89
|
+
*
|
|
90
|
+
* Two separate failures are closed here.
|
|
91
|
+
*
|
|
92
|
+
* **A release owns the database.** `releases/<sha>/database/stacks.sqlite` was a
|
|
93
|
+
* real file inside the release the deploy had just unpacked: `migrate` (in
|
|
94
|
+
* preStart) built a brand new empty database, the release went live against it,
|
|
95
|
+
* and every production row written since the previous deploy was gone the moment
|
|
96
|
+
* the old release was pruned — silently, with nothing in the deploy output to
|
|
97
|
+
* say so. Verified on a live box: a sentinel row was gone after the next deploy.
|
|
98
|
+
*
|
|
99
|
+
* **A site owns the database.** Each site installs under its own base, so `main`
|
|
100
|
+
* and `api` both opened `database/stacks.sqlite` relative to their OWN release
|
|
101
|
+
* — two files, only one of them ever migrated. The database is therefore pointed
|
|
102
|
+
* at a project-level target ({@link projectDatabaseTarget}) that no site's name
|
|
103
|
+
* appears in, so siblings share one file by construction. Only the site that
|
|
104
|
+
* runs `migrate` may create or seed it; the rest link at it (`seed: false`), so
|
|
105
|
+
* deploy order cannot decide whose data wins.
|
|
106
|
+
*
|
|
107
|
+
* Logs stay site-scoped — `main` and `api` are different services and their logs
|
|
108
|
+
* should not interleave.
|
|
109
|
+
*
|
|
110
|
+
* Additive: a site's own `sharedPaths` in config/cloud.ts always survive, so an
|
|
111
|
+
* app can persist its own state (upload dirs, generated assets) the same way.
|
|
112
|
+
*/
|
|
113
|
+
export declare function applyPersistentStatePaths(sites: Record<string, any>, slug: string): Record<string, any>;
|
|
46
114
|
/**
|
|
47
115
|
* Make the site model environment-aware. For a non-production environment that
|
|
48
116
|
* declares a `domainPrefix` (staging → `staging`, development → `dev`), every
|
|
@@ -54,10 +122,16 @@ export declare function mergeSiteDeployEnv(sites: Record<string, any>, resolvedD
|
|
|
54
122
|
*/
|
|
55
123
|
export declare function applyEnvironmentToSites(sites: Record<string, any>, environment: string, config: any): Record<string, any>;
|
|
56
124
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
125
|
+
* Resolve the app's direct ts-cloud dependency before Buddy's own dependency.
|
|
126
|
+
* Package managers may retain a stale nested copy below Buddy even after the
|
|
127
|
+
* app explicitly updates ts-cloud; resolving from the project manifest keeps
|
|
128
|
+
* the deploy engine selected by the application authoritative.
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveProjectTsCloudModule(projectRoot?: unknown): string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Load the ts-cloud driver used by Buddy. An explicit TS_CLOUD_MODULE remains
|
|
133
|
+
* available for framework development; published apps otherwise use their
|
|
134
|
+
* direct dependency before falling back to Buddy's declared dependency.
|
|
61
135
|
*/
|
|
62
136
|
export declare function loadTsCloudDeployApi(): Promise<typeof import('@stacksjs/ts-cloud')>;
|
|
63
137
|
/**
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { runAction } from "@stacksjs/actions";
|
|
@@ -479,6 +479,74 @@ export function mergeSiteDeployEnv(sites, resolvedDeployEnv) {
|
|
|
479
479
|
return [siteName, { ...site, env: { ...base, ...site.env || {} } }];
|
|
480
480
|
}));
|
|
481
481
|
}
|
|
482
|
+
function looksLikeSqliteFile(value) {
|
|
483
|
+
return typeof value === "string" && /\.(?:sqlite3?|db)$/i.test(value.trim());
|
|
484
|
+
}
|
|
485
|
+
export function siteSqlitePath(siteEnv = {}) {
|
|
486
|
+
if (String(siteEnv.DB_CONNECTION ?? "sqlite").trim().toLowerCase() !== "sqlite")
|
|
487
|
+
return null;
|
|
488
|
+
const configured = typeof siteEnv.DB_DATABASE_PATH === "string" && siteEnv.DB_DATABASE_PATH.trim() ? siteEnv.DB_DATABASE_PATH : looksLikeSqliteFile(siteEnv.DB_DATABASE) ? siteEnv.DB_DATABASE : "database/stacks.sqlite", path = String(configured).trim().replace(/^\.\//, "");
|
|
489
|
+
if (!path || path.startsWith("/") || path.startsWith("~") || path.split("/").includes(".."))
|
|
490
|
+
return null;
|
|
491
|
+
return path;
|
|
492
|
+
}
|
|
493
|
+
export function tsCloudPersistentStateSupport(buildSiteDeployScript) {
|
|
494
|
+
const missing = [];
|
|
495
|
+
if (typeof buildSiteDeployScript !== "function")
|
|
496
|
+
return { ok: !1, missing: ["buildSiteDeployScript"] };
|
|
497
|
+
const target = "/var/www/probe-shared/database/probe.sqlite";
|
|
498
|
+
let script = "";
|
|
499
|
+
try {
|
|
500
|
+
script = buildSiteDeployScript({
|
|
501
|
+
siteName: "probe",
|
|
502
|
+
slug: "probe",
|
|
503
|
+
appDir: "/var/www/probe-probe",
|
|
504
|
+
artifactFetch: [],
|
|
505
|
+
releaseId: "probe",
|
|
506
|
+
execStart: "/bin/true",
|
|
507
|
+
envEntries: {},
|
|
508
|
+
port: 3000,
|
|
509
|
+
sharedPaths: [{ path: "database/probe.sqlite", target, seed: !0 }]
|
|
510
|
+
}).join(`
|
|
511
|
+
`);
|
|
512
|
+
} catch {
|
|
513
|
+
return { ok: !1, missing: ["shared paths with an explicit target"] };
|
|
514
|
+
}
|
|
515
|
+
if (!(script.includes("cp -a") && script.includes("/current/")))
|
|
516
|
+
missing.push("adoption of existing state into shared/");
|
|
517
|
+
if (!script.includes(`ln -sfn ${target} `))
|
|
518
|
+
missing.push("shared paths with an explicit target");
|
|
519
|
+
return { ok: missing.length === 0, missing };
|
|
520
|
+
}
|
|
521
|
+
export function projectDatabaseTarget(slug, relativePath) {
|
|
522
|
+
return `/var/www/${slug}-shared/${relativePath}`;
|
|
523
|
+
}
|
|
524
|
+
function runsMigrations(site) {
|
|
525
|
+
return Array.isArray(site?.preStart) && site.preStart.some((cmd) => typeof cmd === "string" && /\bmigrate\b/.test(cmd));
|
|
526
|
+
}
|
|
527
|
+
export function applyPersistentStatePaths(sites, slug) {
|
|
528
|
+
const isServerApp = (site) => !!site && typeof site.start === "string", appSites = Object.entries(sites).filter(([, site]) => isServerApp(site)), owner = (appSites.find(([, site]) => runsMigrations(site)) ?? appSites[0])?.[0], out = {};
|
|
529
|
+
for (const [name, site] of Object.entries(sites)) {
|
|
530
|
+
if (!isServerApp(site)) {
|
|
531
|
+
out[name] = site;
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const sqlite = siteSqlitePath(site.env || {}), framework = [
|
|
535
|
+
"storage/logs"
|
|
536
|
+
];
|
|
537
|
+
if (sqlite)
|
|
538
|
+
framework.unshift({
|
|
539
|
+
path: sqlite,
|
|
540
|
+
target: projectDatabaseTarget(slug, sqlite),
|
|
541
|
+
seed: name === owner
|
|
542
|
+
});
|
|
543
|
+
const declared = Array.isArray(site.sharedPaths) ? site.sharedPaths.filter(Boolean) : [], byPath = new Map;
|
|
544
|
+
for (const entry of [...declared, ...framework])
|
|
545
|
+
byPath.set(typeof entry === "string" ? entry : entry.path, entry);
|
|
546
|
+
out[name] = { ...site, sharedPaths: [...byPath.values()] };
|
|
547
|
+
}
|
|
548
|
+
return out;
|
|
549
|
+
}
|
|
482
550
|
function prefixHostForEnv(host, prefix) {
|
|
483
551
|
if (host.startsWith(`${prefix}.`) || host.startsWith(`www.${prefix}.`))
|
|
484
552
|
return host;
|
|
@@ -546,7 +614,13 @@ async function deployToHetzner(tsCloudConfig, deployEnv, options) {
|
|
|
546
614
|
log.info("Generate one with: ssh-keygen -t ed25519");
|
|
547
615
|
process.exit(ExitCode.FatalError);
|
|
548
616
|
}
|
|
549
|
-
const { createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind } = await loadTsCloudDeployApi();
|
|
617
|
+
const { createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, buildSiteDeployScript } = await loadTsCloudDeployApi(), support = tsCloudPersistentStateSupport(buildSiteDeployScript);
|
|
618
|
+
if (!support.ok) {
|
|
619
|
+
log.error("This ts-cloud cannot keep your database across deploys, and deploying anyway would destroy it.");
|
|
620
|
+
log.error(`Missing: ${support.missing.join(", ")}.`);
|
|
621
|
+
log.error("Upgrade @stacksjs/ts-cloud (bun update @stacksjs/ts-cloud), or point TS_CLOUD_MODULE at a build that has it.");
|
|
622
|
+
process.exit(ExitCode.FatalError);
|
|
623
|
+
}
|
|
550
624
|
try {
|
|
551
625
|
await runHetznerDeploy({ tsCloudConfig, environment, verbose, docker: options.docker === !0, createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, onlySite: options.site || void 0, persistedAttachBox });
|
|
552
626
|
} catch (err) {
|
|
@@ -555,10 +629,22 @@ async function deployToHetzner(tsCloudConfig, deployEnv, options) {
|
|
|
555
629
|
process.exit(ExitCode.FatalError);
|
|
556
630
|
}
|
|
557
631
|
}
|
|
632
|
+
export function resolveProjectTsCloudModule(projectRoot = process.cwd()) {
|
|
633
|
+
const manifestPath = join(projectRoot, "node_modules", "@stacksjs", "ts-cloud", "package.json");
|
|
634
|
+
if (!existsSync(manifestPath))
|
|
635
|
+
return;
|
|
636
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")), rootExport = manifest.exports?.["."], entry = manifest.module ?? (typeof rootExport === "string" ? rootExport : rootExport?.import);
|
|
637
|
+
if (!entry)
|
|
638
|
+
return;
|
|
639
|
+
const modulePath = resolve(dirname(manifestPath), entry);
|
|
640
|
+
return existsSync(modulePath) ? modulePath : void 0;
|
|
641
|
+
}
|
|
558
642
|
export async function loadTsCloudDeployApi() {
|
|
559
643
|
const requested = process.env.TS_CLOUD_MODULE?.trim();
|
|
560
|
-
if (!requested)
|
|
561
|
-
|
|
644
|
+
if (!requested) {
|
|
645
|
+
const projectModule = resolveProjectTsCloudModule();
|
|
646
|
+
return projectModule ? import(pathToFileURL(projectModule).href) : import("@stacksjs/ts-cloud");
|
|
647
|
+
}
|
|
562
648
|
const modulePath = isAbsolute(requested) ? requested : resolve(process.cwd(), requested);
|
|
563
649
|
if (!existsSync(modulePath))
|
|
564
650
|
throw Error(`TS_CLOUD_MODULE points to a missing module: ${modulePath}`);
|
|
@@ -865,7 +951,7 @@ async function runHetznerDeploy(args) {
|
|
|
865
951
|
}
|
|
866
952
|
if (docker)
|
|
867
953
|
await buildContainerImageWithPantry({ slug, sites, verbose });
|
|
868
|
-
const resolvedDeployEnv = await resolveDeployEnvValues(environment, tsCloudConfig), sitesWithResolvedEnv = mergeSiteDeployEnv(sites, resolvedDeployEnv);
|
|
954
|
+
const resolvedDeployEnv = await resolveDeployEnvValues(environment, tsCloudConfig), sitesWithResolvedEnv = applyPersistentStatePaths(mergeSiteDeployEnv(sites, resolvedDeployEnv), slug);
|
|
869
955
|
for (const [envKey, envValue] of Object.entries(resolvedDeployEnv))
|
|
870
956
|
if (process.env[envKey] === void 0)
|
|
871
957
|
process.env[envKey] = envValue;
|
|
@@ -60,6 +60,20 @@ export function generate(buddy) {
|
|
|
60
60
|
log.warn(`[generate:db-types] ${e.file}: ${e.error}`);
|
|
61
61
|
log.info(`[generate:db-types] resolved ${result.tables.length} table(s)`);
|
|
62
62
|
});
|
|
63
|
+
buddy.command("generate:vschema", "Derive a Vitess VSchema from your models (writes database/vschema.json)").option("--dry-run", "Print the VSchema without writing it", { default: !1 }).option("--out [path]", "Where to write the VSchema", { default: "database/vschema.json" }).action(async (options) => {
|
|
64
|
+
const { generateVSchema } = await import("@stacksjs/actions"), result = await generateVSchema({ dryRun: options.dryRun, out: options.out });
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
console.error(`
|
|
67
|
+
\u274C ${result.error}
|
|
68
|
+
`);
|
|
69
|
+
process.exit(ExitCode.FatalError);
|
|
70
|
+
}
|
|
71
|
+
console.log(result.report);
|
|
72
|
+
if (options.dryRun)
|
|
73
|
+
console.log(JSON.stringify(result.vschema, null, 2));
|
|
74
|
+
else
|
|
75
|
+
log.success(`Wrote ${result.path} (${result.tableCount} tables)`);
|
|
76
|
+
});
|
|
63
77
|
buddy.command("generate:entries", descriptions.entries).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
64
78
|
log.debug("Running `buddy generate:entries` ...", options);
|
|
65
79
|
await generateLibEntries(options);
|
package/dist/commands/lint.js
CHANGED
|
@@ -8,6 +8,39 @@ async function runStyleAction(entry, label, options) {
|
|
|
8
8
|
process.exit(ExitCode.FatalError);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
async function runStxChecks(startTime) {
|
|
12
|
+
const { runStxLint } = await import("@stacksjs/actions"), report = await runStxLint(), out = [""];
|
|
13
|
+
for (const r of report.results)
|
|
14
|
+
if (r.status === "fail") {
|
|
15
|
+
out.push(` FAIL ${r.label}`);
|
|
16
|
+
out.push(` ${r.count} found, baseline ${r.baseline}${r.why ? ` (${r.why})` : ""}`);
|
|
17
|
+
for (const line of r.detail.slice(0, 8))
|
|
18
|
+
out.push(` ${line}`);
|
|
19
|
+
} else if (r.status === "loosened")
|
|
20
|
+
out.push(` DROP ${r.label}: ${r.count} < baseline ${r.baseline} \u2014 lower it in config/lint.ts`);
|
|
21
|
+
else
|
|
22
|
+
out.push(` ok ${r.label}${r.baseline > 0 ? ` (${r.count}, held)` : ""}`);
|
|
23
|
+
if (report.distMissing)
|
|
24
|
+
out.push("", " note: no build output found \u2014 the dist checks did not run. Run `./buddy build` first.");
|
|
25
|
+
if (report.loosened > 0) {
|
|
26
|
+
out.push("", " Current counts, for config/lint.ts:");
|
|
27
|
+
for (const [id, count] of Object.entries(report.counts).sort(([a], [b]) => a.localeCompare(b)))
|
|
28
|
+
out.push(` '${id}': ${count},`);
|
|
29
|
+
}
|
|
30
|
+
console.log(out.join(`
|
|
31
|
+
`));
|
|
32
|
+
if (report.failed > 0) {
|
|
33
|
+
log.error(`${report.failed} stx check(s) failed.`);
|
|
34
|
+
await outro("stx checks failed", { startTime, useSeconds: !0 });
|
|
35
|
+
process.exit(ExitCode.FatalError);
|
|
36
|
+
}
|
|
37
|
+
if (report.loosened > 0) {
|
|
38
|
+
log.warn(`No regressions, but ${report.loosened} baseline(s) are now stale.`);
|
|
39
|
+
await outro("stx baselines stale", { startTime, useSeconds: !0 });
|
|
40
|
+
process.exit(ExitCode.FatalError);
|
|
41
|
+
}
|
|
42
|
+
await outro("All stx checks pass", { startTime, useSeconds: !0 });
|
|
43
|
+
}
|
|
11
44
|
export function lint(buddy) {
|
|
12
45
|
const descriptions = {
|
|
13
46
|
lint: "Automagically lints your project codebase",
|
|
@@ -15,11 +48,16 @@ export function lint(buddy) {
|
|
|
15
48
|
format: "Format your project codebase",
|
|
16
49
|
formatCheck: "Check formatting without making changes",
|
|
17
50
|
project: "Target a specific project",
|
|
51
|
+
stx: "Run the stx conformance checks instead of code style",
|
|
18
52
|
verbose: "Enable verbose output"
|
|
19
53
|
};
|
|
20
|
-
buddy.command("lint", descriptions.lint).option("-f, --fix", descriptions.lintFix, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
54
|
+
buddy.command("lint", descriptions.lint).option("-f, --fix", descriptions.lintFix, { default: !1 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--stx", descriptions.stx, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
21
55
|
log.debug("Running `buddy lint` ...", options);
|
|
22
56
|
const startTime = await intro("buddy lint");
|
|
57
|
+
if (options.stx) {
|
|
58
|
+
await runStxChecks(startTime);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
23
61
|
await runStyleAction(options.fix ? "lintFix" : "lintProject", "lint");
|
|
24
62
|
await outro("Linted your project", { startTime, useSeconds: !0 });
|
|
25
63
|
});
|
package/dist/commands/migrate.js
CHANGED
|
@@ -6,7 +6,7 @@ import { hasTTY, isCI } from "@stacksjs/env";
|
|
|
6
6
|
import { appPath, frameworkPath, frameworkRuntimePath, projectPath } from "@stacksjs/path";
|
|
7
7
|
import { ExitCode } from "@stacksjs/types";
|
|
8
8
|
import { preflightDatabase } from "../database-preflight";
|
|
9
|
-
import { DIALECT_OVERRIDE_ENV, auditMigrationCorpus, formatMigrationDialectError, stripSqlNoise } from "@stacksjs/database";
|
|
9
|
+
import { DDL_CONSTRAINT_OVERRIDE_ENV, DIALECT_OVERRIDE_ENV, auditDdlConstraints, auditMigrationCorpus, dialectCapabilities, formatDdlConstraintError, formatMigrationDialectError, stripSqlNoise } from "@stacksjs/database";
|
|
10
10
|
let _runAction;
|
|
11
11
|
async function runAction(...args) {
|
|
12
12
|
if (!_runAction)
|
|
@@ -85,15 +85,21 @@ function validateModelsExist() {
|
|
|
85
85
|
return { valid: !0 };
|
|
86
86
|
}
|
|
87
87
|
function validateMigrationDialect() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
const driver = String(process.env.DB_CONNECTION || "sqlite").toLowerCase(), dir = projectPath("database/migrations");
|
|
89
|
+
if (process.env[DIALECT_OVERRIDE_ENV] !== "1") {
|
|
90
|
+
const caps = dialectCapabilities(driver), target = caps.wire === "mysql" ? "mysql" : caps.wire === "postgres" ? "postgres" : "sqlite";
|
|
91
|
+
if (driver === "sqlite" || driver === "postgres" || caps.wire === "mysql") {
|
|
92
|
+
const audit = auditMigrationCorpus({ dir, target });
|
|
93
|
+
if (!audit.empty && audit.incompatible.length > 0)
|
|
94
|
+
return { valid: !1, error: formatMigrationDialectError(audit, target, "database/migrations") };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (process.env[DDL_CONSTRAINT_OVERRIDE_ENV] !== "1") {
|
|
98
|
+
const constraints = auditDdlConstraints({ dir, dialect: driver });
|
|
99
|
+
if (!constraints.empty && constraints.violations.length > 0)
|
|
100
|
+
return { valid: !1, error: formatDdlConstraintError(constraints, driver, "database/migrations") };
|
|
101
|
+
}
|
|
102
|
+
return { valid: !0 };
|
|
97
103
|
}
|
|
98
104
|
async function reportMissingForeignKeys() {
|
|
99
105
|
try {
|
|
@@ -289,14 +295,20 @@ export function migrate(buddy) {
|
|
|
289
295
|
log.error("Model migrations failed \u2014 applying notification/RBAC table guarantees before exiting.");
|
|
290
296
|
if (options.auth !== !1)
|
|
291
297
|
try {
|
|
292
|
-
const { migrateNotificationTables, migrateRbacTables } = await import("@stacksjs/database"), notifResult = await migrateNotificationTables({ verbose: options.verbose });
|
|
298
|
+
const { ensureUtcDatetimeColumns, migrateNotificationTables, migrateRbacTables, migrateTraitTables } = await import("@stacksjs/database"), notifResult = await migrateNotificationTables({ verbose: options.verbose });
|
|
293
299
|
if (!notifResult.success)
|
|
294
300
|
log.error(`Failed to migrate notification tables: ${notifResult.error}`);
|
|
295
301
|
const rbacResult = await migrateRbacTables({ verbose: options.verbose });
|
|
296
302
|
if (!rbacResult.success)
|
|
297
303
|
log.error(`Failed to migrate RBAC tables: ${rbacResult.error}`);
|
|
304
|
+
const traitResult = await migrateTraitTables({ verbose: options.verbose });
|
|
305
|
+
if (!traitResult.success)
|
|
306
|
+
log.error(`Failed to migrate polymorphic trait tables: ${traitResult.error}`);
|
|
307
|
+
const datetimeResult = await ensureUtcDatetimeColumns({ verbose: options.verbose });
|
|
308
|
+
if (!datetimeResult.success)
|
|
309
|
+
log.error(`Failed to convert TIMESTAMP columns to DATETIME: ${datetimeResult.error}`);
|
|
298
310
|
} catch (error) {
|
|
299
|
-
log.error("Failed to migrate notification/RBAC tables:", error);
|
|
311
|
+
log.error("Failed to migrate notification/RBAC/trait tables:", error);
|
|
300
312
|
}
|
|
301
313
|
if (result.isErr) {
|
|
302
314
|
await outro("While running the migrate command, there was an issue", { startTime: perf, useSeconds: !0 }, result.error);
|
|
@@ -371,7 +383,7 @@ export function migrate(buddy) {
|
|
|
371
383
|
if (options.auth !== !1) {
|
|
372
384
|
log.debug("Migrating auth tables...");
|
|
373
385
|
try {
|
|
374
|
-
const { migrateAuthTables, migrateNotificationTables, migrateRbacTables } = await import("@stacksjs/database"), authResult = await migrateAuthTables({ verbose: options.verbose });
|
|
386
|
+
const { migrateAuthTables, migrateNotificationTables, migrateRbacTables, migrateTraitTables } = await import("@stacksjs/database"), authResult = await migrateAuthTables({ verbose: options.verbose });
|
|
375
387
|
if (!authResult.success)
|
|
376
388
|
log.error(`Failed to migrate auth tables: ${authResult.error}`);
|
|
377
389
|
const notifResult = await migrateNotificationTables({ verbose: options.verbose });
|
|
@@ -380,8 +392,11 @@ export function migrate(buddy) {
|
|
|
380
392
|
const rbacResult = await migrateRbacTables({ verbose: options.verbose });
|
|
381
393
|
if (!rbacResult.success)
|
|
382
394
|
log.error(`Failed to migrate RBAC tables: ${rbacResult.error}`);
|
|
395
|
+
const traitResult = await migrateTraitTables({ verbose: options.verbose });
|
|
396
|
+
if (!traitResult.success)
|
|
397
|
+
log.error(`Failed to migrate polymorphic trait tables: ${traitResult.error}`);
|
|
383
398
|
} catch (error) {
|
|
384
|
-
log.error("Failed to migrate auth/notification/RBAC tables:", error);
|
|
399
|
+
log.error("Failed to migrate auth/notification/RBAC/trait tables:", error);
|
|
385
400
|
}
|
|
386
401
|
}
|
|
387
402
|
try {
|
|
@@ -1,35 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Resolve the project's includes directory.
|
|
3
|
-
*
|
|
4
|
-
* A configured `config/stx.ts#partialsDir` wins outright. The convention list
|
|
5
|
-
* below is only a fallback for apps that never set one, and it cannot stand in
|
|
6
|
-
* for the config: the candidates are probed for existence in order, so an app
|
|
7
|
-
* that keeps its includes in `resources/components` but also has an unrelated
|
|
8
|
-
* `resources/partials` directory silently resolved to the wrong one and every
|
|
9
|
-
* `@include` failed with ENOENT at runtime.
|
|
10
|
-
*
|
|
11
|
-
* The configured value is relative to the stx root (`resources`), matching how
|
|
12
|
-
* stx itself reads it, but an app-root-relative path is accepted too so either
|
|
13
|
-
* spelling resolves.
|
|
14
|
-
*/
|
|
15
|
-
export declare function resolveUserPartialsPath(cwd?: unknown, configuredDir?: string): string | undefined;
|
|
16
|
-
/**
|
|
17
|
-
* Read `partialsDir` off the app's stx config. Failure is non-fatal: without
|
|
18
|
-
* it {@link resolveUserPartialsPath} falls back to the conventions.
|
|
19
|
-
*/
|
|
20
|
-
export declare function loadStxPartialsDir(cwd?: unknown): Promise<string | undefined>;
|
|
21
|
-
/**` proxy target, or `null` when it cannot be
|
|
22
|
-
* known safely.
|
|
23
|
-
*
|
|
24
|
-
* `API_URL` and `PORT_API` are explicit operator intent and always win. The
|
|
25
|
-
* framework-wide `ports.api` default is only trusted *outside* deployed
|
|
26
|
-
* environments: locally one app owns the machine, so `127.0.0.1:3008` really is
|
|
27
|
-
* its own API. On a deployed box that assumption does not hold — ts-cloud runs
|
|
28
|
-
* many SSR sites side by side, each on its own port, and an unconfigured
|
|
29
|
-
* default resolves to whichever tenant happens to own it.
|
|
30
|
-
*
|
|
31
|
-
* Returning `null` makes the caller answer 502. That is the safe failure: the
|
|
32
|
-
* alternative is proxying authenticated requests into another app's process.
|
|
33
|
-
*/
|
|
34
|
-
export declare function resolveApiBase(configuredPort?: number, env?: NodeJS.ProcessEnv): string | null;
|
|
35
|
-
export declare function startProductionServer(options?: { port?: string | number, verbose?: boolean }): Promise<void>;
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.258",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -88,54 +88,54 @@
|
|
|
88
88
|
"prepublishOnly": "bun run build"
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
|
-
"@stacksjs/actions": "^0.70.
|
|
92
|
-
"@stacksjs/ai": "^0.70.
|
|
93
|
-
"@stacksjs/alias": "^0.70.
|
|
94
|
-
"@stacksjs/arrays": "^0.70.
|
|
95
|
-
"@stacksjs/auth": "^0.70.
|
|
96
|
-
"@stacksjs/build": "^0.70.
|
|
97
|
-
"@stacksjs/cache": "^0.70.
|
|
98
|
-
"@stacksjs/cli": "^0.70.
|
|
91
|
+
"@stacksjs/actions": "^0.70.258",
|
|
92
|
+
"@stacksjs/ai": "^0.70.258",
|
|
93
|
+
"@stacksjs/alias": "^0.70.258",
|
|
94
|
+
"@stacksjs/arrays": "^0.70.258",
|
|
95
|
+
"@stacksjs/auth": "^0.70.258",
|
|
96
|
+
"@stacksjs/build": "^0.70.258",
|
|
97
|
+
"@stacksjs/cache": "^0.70.258",
|
|
98
|
+
"@stacksjs/cli": "^0.70.258",
|
|
99
99
|
"@stacksjs/clapp": "^0.2.12",
|
|
100
|
-
"@stacksjs/cloud": "^0.70.
|
|
101
|
-
"@stacksjs/collections": "^0.70.
|
|
102
|
-
"@stacksjs/config": "^0.70.
|
|
103
|
-
"@stacksjs/database": "^0.70.
|
|
104
|
-
"@stacksjs/desktop-build": "^0.70.
|
|
105
|
-
"@stacksjs/dns": "^0.70.
|
|
106
|
-
"@stacksjs/email": "^0.70.
|
|
107
|
-
"@stacksjs/enums": "^0.70.
|
|
108
|
-
"@stacksjs/error-handling": "^0.70.
|
|
109
|
-
"@stacksjs/events": "^0.70.
|
|
110
|
-
"@stacksjs/git": "^0.70.
|
|
100
|
+
"@stacksjs/cloud": "^0.70.258",
|
|
101
|
+
"@stacksjs/collections": "^0.70.258",
|
|
102
|
+
"@stacksjs/config": "^0.70.258",
|
|
103
|
+
"@stacksjs/database": "^0.70.258",
|
|
104
|
+
"@stacksjs/desktop-build": "^0.70.258",
|
|
105
|
+
"@stacksjs/dns": "^0.70.258",
|
|
106
|
+
"@stacksjs/email": "^0.70.258",
|
|
107
|
+
"@stacksjs/enums": "^0.70.258",
|
|
108
|
+
"@stacksjs/error-handling": "^0.70.258",
|
|
109
|
+
"@stacksjs/events": "^0.70.258",
|
|
110
|
+
"@stacksjs/git": "^0.70.258",
|
|
111
111
|
"@stacksjs/gitit": "^0.2.5",
|
|
112
|
-
"@stacksjs/health": "^0.70.
|
|
112
|
+
"@stacksjs/health": "^0.70.258",
|
|
113
113
|
"@stacksjs/dnsx": "^0.2.3",
|
|
114
114
|
"@stacksjs/httx": "^0.1.10",
|
|
115
|
-
"@stacksjs/image": "^0.70.
|
|
116
|
-
"@stacksjs/lint": "^0.70.
|
|
117
|
-
"@stacksjs/logging": "^0.70.
|
|
118
|
-
"@stacksjs/notifications": "^0.70.
|
|
119
|
-
"@stacksjs/objects": "^0.70.
|
|
120
|
-
"@stacksjs/orm": "^0.70.
|
|
121
|
-
"@stacksjs/path": "^0.70.
|
|
122
|
-
"@stacksjs/skills": "^0.70.
|
|
123
|
-
"@stacksjs/payments": "^0.70.
|
|
124
|
-
"@stacksjs/realtime": "^0.70.
|
|
125
|
-
"@stacksjs/router": "^0.70.
|
|
115
|
+
"@stacksjs/image": "^0.70.258",
|
|
116
|
+
"@stacksjs/lint": "^0.70.258",
|
|
117
|
+
"@stacksjs/logging": "^0.70.258",
|
|
118
|
+
"@stacksjs/notifications": "^0.70.258",
|
|
119
|
+
"@stacksjs/objects": "^0.70.258",
|
|
120
|
+
"@stacksjs/orm": "^0.70.258",
|
|
121
|
+
"@stacksjs/path": "^0.70.258",
|
|
122
|
+
"@stacksjs/skills": "^0.70.258",
|
|
123
|
+
"@stacksjs/payments": "^0.70.258",
|
|
124
|
+
"@stacksjs/realtime": "^0.70.258",
|
|
125
|
+
"@stacksjs/router": "^0.70.258",
|
|
126
126
|
"@stacksjs/rpx": "^0.11.42",
|
|
127
|
-
"@stacksjs/search-engine": "^0.70.
|
|
128
|
-
"@stacksjs/security": "^0.70.
|
|
129
|
-
"@stacksjs/server": "^0.70.
|
|
130
|
-
"@stacksjs/storage": "^0.70.
|
|
131
|
-
"@stacksjs/strings": "^0.70.
|
|
132
|
-
"@stacksjs/testing": "^0.70.
|
|
133
|
-
"@stacksjs/tunnel": "^0.70.
|
|
134
|
-
"@stacksjs/types": "^0.70.
|
|
135
|
-
"@stacksjs/ui": "^0.70.
|
|
136
|
-
"@stacksjs/utils": "^0.70.
|
|
137
|
-
"@stacksjs/validation": "^0.70.
|
|
138
|
-
"@stacksjs/ts-cloud": "^0.7.
|
|
127
|
+
"@stacksjs/search-engine": "^0.70.258",
|
|
128
|
+
"@stacksjs/security": "^0.70.258",
|
|
129
|
+
"@stacksjs/server": "^0.70.258",
|
|
130
|
+
"@stacksjs/storage": "^0.70.258",
|
|
131
|
+
"@stacksjs/strings": "^0.70.258",
|
|
132
|
+
"@stacksjs/testing": "^0.70.258",
|
|
133
|
+
"@stacksjs/tunnel": "^0.70.258",
|
|
134
|
+
"@stacksjs/types": "^0.70.258",
|
|
135
|
+
"@stacksjs/ui": "^0.70.258",
|
|
136
|
+
"@stacksjs/utils": "^0.70.258",
|
|
137
|
+
"@stacksjs/validation": "^0.70.258",
|
|
138
|
+
"@stacksjs/ts-cloud": "^0.7.94",
|
|
139
139
|
"ajv": "^8.20.0",
|
|
140
140
|
"ajv-formats": "^3.0.1",
|
|
141
141
|
"ts-pantry": "^0.11.0"
|