@supacloud/cli 0.103.2 → 0.103.5
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 +10 -0
- package/dist/index.js +470 -209
- package/dist/shared/tools/app-starter-query.d.ts +1 -1
- package/dist/shared/tools/app-starter-review-job.d.ts +1 -1
- package/dist/shared/tools/app-tools.d.ts +5 -0
- package/dist/shared/tools/edge-function-response.d.ts +2 -0
- package/dist/shared/tools/function-aot.d.ts +9 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -10960,7 +10960,7 @@ function formatDeploymentEvidence(evidence) {
|
|
|
10960
10960
|
return [
|
|
10961
10961
|
`DEPLOYMENT ${evidence.scope.project_ref}/${evidence.scope.application_id}/${evidence.scope.environment_id}`,
|
|
10962
10962
|
` status: ${report.status}`,
|
|
10963
|
-
...report.status !== evidence.status ? [` recorded status: ${evidence.status} (
|
|
10963
|
+
...report.status !== evidence.status ? [` recorded status: ${evidence.status} (customer assessment differs)`] : [],
|
|
10964
10964
|
` manifest: ${evidence.source.manifest_sha256.slice(0, 12)}`,
|
|
10965
10965
|
` database: ${evidence.database.provider} ${evidence.database.version} (${evidence.database.topology})`,
|
|
10966
10966
|
` activation: ${evidence.activation.activation_id}`,
|
|
@@ -10988,7 +10988,7 @@ function summarizeDeploymentEvidence(value) {
|
|
|
10988
10988
|
for (const component of evidence.components) {
|
|
10989
10989
|
observe(component.name, component.status, component.checked_at !== null && component.health_check !== null, "Inspect this component's health and logs for the recorded activation before changing or restarting it.");
|
|
10990
10990
|
}
|
|
10991
|
-
observe("database.migration", evidence.database.migration.status, evidence.database.migration.compatibility === "verified" && evidence.database.migration.inventory_sha256 !== null, "Read the migration inventory and compare its digest; do not reapply an unconfirmed migration.");
|
|
10991
|
+
observe("database.migration", evidence.database.migration.compatibility === "failed" ? "failed" : evidence.database.migration.status, evidence.database.migration.compatibility === "verified" && evidence.database.migration.inventory_sha256 !== null, "Read the migration inventory and compare its digest; do not reapply an unconfirmed migration.");
|
|
10992
10992
|
observe("database.backup", evidence.database.backup.status, evidence.database.backup.latest_success_at !== null && evidence.database.backup.freshness_seconds !== null, "Read backup inventory and freshness for this project; a successful deployment is not a backup.");
|
|
10993
10993
|
observe("database.recovery", evidence.database.recovery.status, evidence.database.recovery.drill_id !== null && evidence.database.recovery.rpo_seconds !== null && evidence.database.recovery.rto_seconds !== null, "Obtain a separate approved restore drill and verify data, permissions and storage; never restore automatically.");
|
|
10994
10994
|
observe("health", evidence.health.status, evidence.health.checked_at !== null, "Read back this activation's readiness; process health alone does not prove a business operation.");
|
|
@@ -10997,9 +10997,10 @@ function summarizeDeploymentEvidence(value) {
|
|
|
10997
10997
|
if (evidence.rollback.result !== null) {
|
|
10998
10998
|
observe("code-rollback.result", evidence.rollback.result, true, "Read back the rollback activation and authenticated smoke before another mutation.");
|
|
10999
10999
|
}
|
|
11000
|
+
const assessment = issues.some((issue) => issue.status === "failed") || evidence.status === "failed" ? "failed" : issues.some((issue) => issue.status === "unknown") || evidence.status === "unknown" ? "unknown" : issues.length > 0 || evidence.status === "incomplete" ? "incomplete" : "confirmed";
|
|
11000
11001
|
return {
|
|
11001
11002
|
version: 1,
|
|
11002
|
-
status:
|
|
11003
|
+
status: assessment,
|
|
11003
11004
|
recordedStatus: evidence.status,
|
|
11004
11005
|
scope: evidence.scope,
|
|
11005
11006
|
recordedAt: evidence.recorded_at,
|
|
@@ -15003,7 +15004,7 @@ Actions: status, list_buckets, get_bucket, create_bucket, update_bucket, delete_
|
|
|
15003
15004
|
}
|
|
15004
15005
|
|
|
15005
15006
|
// src/shared/tools/advanced-tools.ts
|
|
15006
|
-
import { createHash as
|
|
15007
|
+
import { createHash as createHash5, timingSafeEqual } from "node:crypto";
|
|
15007
15008
|
import {
|
|
15008
15009
|
closeSync,
|
|
15009
15010
|
constants as fsConstants,
|
|
@@ -15013,14 +15014,101 @@ import {
|
|
|
15013
15014
|
mkdirSync,
|
|
15014
15015
|
mkdtempSync,
|
|
15015
15016
|
openSync,
|
|
15016
|
-
readFileSync as
|
|
15017
|
+
readFileSync as readFileSync6,
|
|
15017
15018
|
readdirSync as readdirSync3,
|
|
15018
15019
|
rmSync,
|
|
15019
15020
|
statSync as statSync4,
|
|
15020
15021
|
writeFileSync
|
|
15021
15022
|
} from "node:fs";
|
|
15022
15023
|
import { tmpdir } from "node:os";
|
|
15023
|
-
import { basename as basename5, join as join5, relative as relative2, resolve as
|
|
15024
|
+
import { basename as basename5, join as join5, relative as relative2, resolve as resolve7, sep as sep2 } from "node:path";
|
|
15025
|
+
|
|
15026
|
+
// src/shared/tools/function-aot.ts
|
|
15027
|
+
import { execFile } from "node:child_process";
|
|
15028
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
15029
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
15030
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
15031
|
+
import { promisify } from "node:util";
|
|
15032
|
+
var execFileAsync = promisify(execFile);
|
|
15033
|
+
var runner = `
|
|
15034
|
+
import { pathToFileURL } from "node:url";
|
|
15035
|
+
import { basename, dirname } from "node:path";
|
|
15036
|
+
const [entry, appEntry, outfile, minify] = Bun.argv.slice(1);
|
|
15037
|
+
const plugin = await import(import.meta.resolve("elysia/plugin/aot/bun", pathToFileURL(appEntry).href));
|
|
15038
|
+
const result = await Bun.build({
|
|
15039
|
+
entrypoints: [entry], outdir: dirname(outfile), naming: { entry: basename(outfile) },
|
|
15040
|
+
target: "bun", format: "esm", splitting: false, metafile: true,
|
|
15041
|
+
minify: minify === "true", env: "disable",
|
|
15042
|
+
plugins: [plugin.aot(appEntry, {
|
|
15043
|
+
strip: false, production: false,
|
|
15044
|
+
registerFrom: Bun.fileURLToPath(import.meta.resolve("elysia", pathToFileURL(appEntry).href)),
|
|
15045
|
+
reconstructFrom: Bun.fileURLToPath(import.meta.resolve("elysia/reconstruct", pathToFileURL(appEntry).href))
|
|
15046
|
+
}), {
|
|
15047
|
+
name: "resolve-aot-manifest-dependencies",
|
|
15048
|
+
setup(build) {
|
|
15049
|
+
build.onResolve({ filter: /^(elysia|typebox)(\\/.*)?$/ }, ({ path }) => ({
|
|
15050
|
+
path: Bun.fileURLToPath(import.meta.resolve(path, pathToFileURL(appEntry).href))
|
|
15051
|
+
}));
|
|
15052
|
+
}
|
|
15053
|
+
}]
|
|
15054
|
+
});
|
|
15055
|
+
if (!result.success) throw new Error(result.logs.map(log => log.message).join("\\n"));
|
|
15056
|
+
if (!Object.keys(result.metafile.inputs).some(input => input.includes("elysia-aot") && input.endsWith("manifest"))) {
|
|
15057
|
+
throw new Error("AOT app entry is not in the bundle graph; compiled manifest was not injected");
|
|
15058
|
+
}
|
|
15059
|
+
if (result.outputs.length !== 1) throw new Error("AOT requires a single JavaScript bundle without sidecar assets");
|
|
15060
|
+
await Bun.write(outfile, await result.outputs[0].text());
|
|
15061
|
+
process.exit(0);
|
|
15062
|
+
`;
|
|
15063
|
+
function validateFunctionAot(args) {
|
|
15064
|
+
if (args.aot !== undefined && args.action !== "deploy") {
|
|
15065
|
+
throw new Error("'--aot' is only supported for 'deploy'");
|
|
15066
|
+
}
|
|
15067
|
+
if (args["aot-entry"] !== undefined && args.aot !== true) {
|
|
15068
|
+
throw new Error("'--aot-entry' requires '--aot'");
|
|
15069
|
+
}
|
|
15070
|
+
if (args.aot !== true)
|
|
15071
|
+
return;
|
|
15072
|
+
if (args.framework !== "elysia")
|
|
15073
|
+
throw new Error("'--aot' requires 'framework=elysia'");
|
|
15074
|
+
if (typeof args.path !== "string" || args.code !== undefined || args["prebundled-path"] !== undefined) {
|
|
15075
|
+
throw new Error("'--aot' requires '--path' without '--code' or '--prebundled-path'");
|
|
15076
|
+
}
|
|
15077
|
+
}
|
|
15078
|
+
async function buildFunctionAot(entrypoint, outfile, options = {}) {
|
|
15079
|
+
const env = {};
|
|
15080
|
+
for (const key of ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "SystemRoot"]) {
|
|
15081
|
+
if (process.env[key] !== undefined)
|
|
15082
|
+
env[key] = process.env[key];
|
|
15083
|
+
}
|
|
15084
|
+
env.NODE_ENV = "production";
|
|
15085
|
+
try {
|
|
15086
|
+
const { stderr } = await execFileAsync("bun", [
|
|
15087
|
+
"--no-env-file",
|
|
15088
|
+
"--eval",
|
|
15089
|
+
runner,
|
|
15090
|
+
resolve6(entrypoint),
|
|
15091
|
+
resolve6(options.appEntry ?? entrypoint),
|
|
15092
|
+
outfile,
|
|
15093
|
+
String(options.minify === true)
|
|
15094
|
+
], {
|
|
15095
|
+
cwd: dirname3(entrypoint),
|
|
15096
|
+
env,
|
|
15097
|
+
timeout: 120000,
|
|
15098
|
+
killSignal: "SIGKILL",
|
|
15099
|
+
maxBuffer: 1024 * 1024
|
|
15100
|
+
});
|
|
15101
|
+
if (stderr)
|
|
15102
|
+
process.stderr.write(stderr);
|
|
15103
|
+
} catch (error) {
|
|
15104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15105
|
+
throw new Error(`Local Elysia AOT build failed (Bun and the app's Elysia AOT plugin are required): ${message}`);
|
|
15106
|
+
}
|
|
15107
|
+
const code = readFileSync5(outfile, "utf8");
|
|
15108
|
+
if (!code.trim())
|
|
15109
|
+
throw new Error("AOT build produced an empty bundle");
|
|
15110
|
+
return { code, prebundled: true, expectedSha256: createHash4("sha256").update(code).digest("hex") };
|
|
15111
|
+
}
|
|
15024
15112
|
|
|
15025
15113
|
// src/shared/tools/edge-function-response.ts
|
|
15026
15114
|
var CANONICAL_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
@@ -15162,6 +15250,8 @@ function projectedFunctionIdentity(payload, expectedProjectRef, expectedSlug) {
|
|
|
15162
15250
|
const response = objectRecord(payload);
|
|
15163
15251
|
if (!response || response.project_ref !== expectedProjectRef || response.slug !== expectedSlug || response.active_version !== "absent" && !canonicalVersion(response.active_version) || !validObservedFunctionActivationId(response.activation_id) || typeof response.verify_jwt !== "boolean" || !stringRoutes(response.background_routes))
|
|
15164
15252
|
return null;
|
|
15253
|
+
if (response.bytecode !== undefined && !["off", "auto", "required"].includes(response.bytecode))
|
|
15254
|
+
return null;
|
|
15165
15255
|
const optionalFields = optionalConfigFields(response);
|
|
15166
15256
|
if (!optionalFields || !coherentFunctionVersion(response.active_version, optionalFields.version))
|
|
15167
15257
|
return null;
|
|
@@ -15177,6 +15267,7 @@ function projectedFunctionIdentity(payload, expectedProjectRef, expectedSlug) {
|
|
|
15177
15267
|
slug: expectedSlug,
|
|
15178
15268
|
active_version: response.active_version,
|
|
15179
15269
|
verify_jwt: response.verify_jwt,
|
|
15270
|
+
...response.bytecode === undefined ? {} : { bytecode: response.bytecode },
|
|
15180
15271
|
background_routes: response.background_routes,
|
|
15181
15272
|
...framework === undefined ? {} : { framework },
|
|
15182
15273
|
...optionalFields,
|
|
@@ -15194,6 +15285,10 @@ function configMatchesExpectation(response, expected) {
|
|
|
15194
15285
|
}
|
|
15195
15286
|
if (response.framework !== undefined && !FUNCTION_FRAMEWORKS.includes(response.framework))
|
|
15196
15287
|
return false;
|
|
15288
|
+
if (response.bytecode !== undefined && !["off", "auto", "required"].includes(response.bytecode))
|
|
15289
|
+
return false;
|
|
15290
|
+
if (expected.bytecode !== undefined && response.bytecode !== expected.bytecode)
|
|
15291
|
+
return false;
|
|
15197
15292
|
if (expected.verify_jwt !== undefined && response.verify_jwt !== expected.verify_jwt)
|
|
15198
15293
|
return false;
|
|
15199
15294
|
if (expected.framework !== undefined && response.framework !== expected.framework)
|
|
@@ -15223,6 +15318,7 @@ function confirmedFunctionConfigMutation(payload, expectation) {
|
|
|
15223
15318
|
verify_jwt: response.verify_jwt,
|
|
15224
15319
|
background_routes: response.background_routes,
|
|
15225
15320
|
...response.framework === undefined ? {} : { framework: response.framework },
|
|
15321
|
+
...response.bytecode === undefined ? {} : { bytecode: response.bytecode },
|
|
15226
15322
|
...optionalFields,
|
|
15227
15323
|
...capabilities === undefined ? {} : { capabilities },
|
|
15228
15324
|
...limits === undefined ? {} : { limits }
|
|
@@ -15320,13 +15416,13 @@ function scaffoldFunction(pathArg, slug, framework) {
|
|
|
15320
15416
|
if (typeof framework !== "string" || !FUNCTION_FRAMEWORKS2.includes(framework)) {
|
|
15321
15417
|
throw new Error("'framework' required for 'scaffold'");
|
|
15322
15418
|
}
|
|
15323
|
-
const target =
|
|
15419
|
+
const target = resolve7(typeof pathArg === "string" ? pathArg : join5("supabase", "functions", slug));
|
|
15324
15420
|
if (existsSync5(target))
|
|
15325
15421
|
throw new Error(`Scaffold target already exists: ${target}`);
|
|
15326
15422
|
const files = scaffoldFiles(framework, slug);
|
|
15327
15423
|
for (const [relativePath, contents] of Object.entries(files)) {
|
|
15328
15424
|
const destination = join5(target, relativePath);
|
|
15329
|
-
mkdirSync(
|
|
15425
|
+
mkdirSync(resolve7(destination, ".."), { recursive: true });
|
|
15330
15426
|
writeFileSync(destination, contents, { flag: "wx" });
|
|
15331
15427
|
}
|
|
15332
15428
|
return JSON.stringify({ success: true, framework, path: target, files: Object.keys(files) }, null, 2);
|
|
@@ -15364,17 +15460,17 @@ function assertExpectedSha256(bytes, expectedSha256) {
|
|
|
15364
15460
|
if (!SHA256_HEX_PATTERN.test(expectedSha256)) {
|
|
15365
15461
|
throw new Error("'--expected-sha256' must be a lowercase 64-character SHA-256");
|
|
15366
15462
|
}
|
|
15367
|
-
const actualDigest =
|
|
15463
|
+
const actualDigest = createHash5("sha256").update(bytes).digest();
|
|
15368
15464
|
const expectedDigest = Buffer.from(expectedSha256, "hex");
|
|
15369
15465
|
if (!timingSafeEqual(actualDigest, expectedDigest)) {
|
|
15370
15466
|
throw new Error("Prebundled file SHA-256 does not match --expected-sha256");
|
|
15371
15467
|
}
|
|
15372
15468
|
}
|
|
15373
15469
|
function readVerifiedPrebundledCode(pathArg, expectedSha256) {
|
|
15374
|
-
const descriptor = openSync(
|
|
15470
|
+
const descriptor = openSync(resolve7(pathArg), fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
|
|
15375
15471
|
try {
|
|
15376
15472
|
const identityBeforeRead = openFileIdentity(descriptor);
|
|
15377
|
-
const bytes =
|
|
15473
|
+
const bytes = readFileSync6(descriptor);
|
|
15378
15474
|
const identityAfterRead = openFileIdentity(descriptor);
|
|
15379
15475
|
if (BigInt(bytes.byteLength) !== identityBeforeRead.size || !sameOpenFile(identityBeforeRead, identityAfterRead)) {
|
|
15380
15476
|
throw new Error("Prebundled file changed while it was being read");
|
|
@@ -15408,7 +15504,7 @@ function readBundleDirectory(root, directory = root) {
|
|
|
15408
15504
|
}
|
|
15409
15505
|
if (!entryState.isFile())
|
|
15410
15506
|
throw new Error(`Bundle directory must contain only regular files: ${bundlePath}`);
|
|
15411
|
-
files[bundlePath] = verifiedUtf8Code(
|
|
15507
|
+
files[bundlePath] = verifiedUtf8Code(readFileSync6(entryPath));
|
|
15412
15508
|
}
|
|
15413
15509
|
return files;
|
|
15414
15510
|
}
|
|
@@ -15420,7 +15516,7 @@ function preparedBundleFiles(args) {
|
|
|
15420
15516
|
}
|
|
15421
15517
|
if (typeof bundleDirectory !== "string")
|
|
15422
15518
|
return files;
|
|
15423
|
-
const resolvedDirectory =
|
|
15519
|
+
const resolvedDirectory = resolve7(bundleDirectory);
|
|
15424
15520
|
const rootState = lstatSync(resolvedDirectory);
|
|
15425
15521
|
if (rootState.isSymbolicLink() || !rootState.isDirectory()) {
|
|
15426
15522
|
throw new Error("'--bundle-dir' must be a regular directory and must not be a symlink");
|
|
@@ -15501,6 +15597,17 @@ async function preparedDeployCode(args) {
|
|
|
15501
15597
|
return { code: codeArg, prebundled: false };
|
|
15502
15598
|
if (typeof pathArg !== "string")
|
|
15503
15599
|
throw new Error("Function deploy source is invalid");
|
|
15600
|
+
if (args.aot === true) {
|
|
15601
|
+
const directory = mkdtempSync(join5(tmpdir(), "supacloud-function-aot-"));
|
|
15602
|
+
try {
|
|
15603
|
+
return await buildFunctionAot(resolveEntrypoint(pathArg), join5(directory, "index.js"), {
|
|
15604
|
+
...typeof args["aot-entry"] === "string" ? { appEntry: args["aot-entry"] } : {},
|
|
15605
|
+
minify: args.minify === true
|
|
15606
|
+
});
|
|
15607
|
+
} finally {
|
|
15608
|
+
rmSync(directory, { recursive: true, force: true });
|
|
15609
|
+
}
|
|
15610
|
+
}
|
|
15504
15611
|
return bundledDeployCode(pathArg);
|
|
15505
15612
|
}
|
|
15506
15613
|
async function bundledDeployCode(pathArg) {
|
|
@@ -15522,7 +15629,7 @@ function rejectActionSpecificFlags(action, args) {
|
|
|
15522
15629
|
}
|
|
15523
15630
|
}
|
|
15524
15631
|
function resolveEntrypoint(pathArg) {
|
|
15525
|
-
const resolved =
|
|
15632
|
+
const resolved = resolve7(pathArg);
|
|
15526
15633
|
const stat = statSync4(resolved);
|
|
15527
15634
|
if (!stat.isDirectory())
|
|
15528
15635
|
return resolved;
|
|
@@ -15800,6 +15907,10 @@ function confirmedFunctionConfig(payload, expected) {
|
|
|
15800
15907
|
return false;
|
|
15801
15908
|
if (expected.framework !== undefined && response.framework !== expected.framework)
|
|
15802
15909
|
return false;
|
|
15910
|
+
if (response.bytecode !== undefined && response.bytecode !== "off" && response.bytecode !== "auto" && response.bytecode !== "required")
|
|
15911
|
+
return false;
|
|
15912
|
+
if (expected.bytecode !== undefined && response.bytecode !== expected.bytecode)
|
|
15913
|
+
return false;
|
|
15803
15914
|
if (expected.background_routes !== undefined) {
|
|
15804
15915
|
if (!Array.isArray(response.background_routes))
|
|
15805
15916
|
return false;
|
|
@@ -15830,7 +15941,7 @@ function functionSourceOutput(slug, sourceCode, output) {
|
|
|
15830
15941
|
if (!output) {
|
|
15831
15942
|
return { content: [{ type: "text", text: JSON.stringify({ code: sourceCode }, null, 2) }] };
|
|
15832
15943
|
}
|
|
15833
|
-
const outputPath =
|
|
15944
|
+
const outputPath = resolve7(output);
|
|
15834
15945
|
writeFileSync(outputPath, sourceCode, { flag: "wx" });
|
|
15835
15946
|
return {
|
|
15836
15947
|
content: [{
|
|
@@ -15914,6 +16025,7 @@ function confirmedFunctionMutation(expectation, payload) {
|
|
|
15914
16025
|
activeVersion,
|
|
15915
16026
|
activationId,
|
|
15916
16027
|
verifyJwt: config.verify_jwt,
|
|
16028
|
+
...config.bytecode === undefined ? {} : { bytecode: config.bytecode },
|
|
15917
16029
|
...framework === undefined ? {} : { framework },
|
|
15918
16030
|
...config.capabilities === undefined ? {} : { capabilities: config.capabilities },
|
|
15919
16031
|
...config.limits === undefined ? {} : { limits: config.limits }
|
|
@@ -15935,6 +16047,7 @@ function functionMutationResponse(expectation, response) {
|
|
|
15935
16047
|
active_version: confirmed.activeVersion,
|
|
15936
16048
|
version: confirmed.activeVersion,
|
|
15937
16049
|
verify_jwt: confirmed.verifyJwt,
|
|
16050
|
+
...confirmed.bytecode === undefined ? {} : { bytecode: confirmed.bytecode },
|
|
15938
16051
|
...confirmed.framework === undefined ? {} : { framework: confirmed.framework },
|
|
15939
16052
|
...confirmed.capabilities === undefined ? {} : { capabilities: confirmed.capabilities },
|
|
15940
16053
|
...confirmed.limits === undefined ? {} : { limits: confirmed.limits }
|
|
@@ -16013,7 +16126,10 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
|
|
|
16013
16126
|
"bundle-dir": optional(String2(), "[deploy_bundle] Local self-contained UTF-8 bundle directory; excludes node_modules, .git, AppleDouble, symlinks, and special files"),
|
|
16014
16127
|
entrypoint: optional(String2(), "[deploy_bundle] Entrypoint file (default: index.ts)"),
|
|
16015
16128
|
minify: optional(Boolean2(), "[deploy/deploy_bundle] Minify bundle"),
|
|
16129
|
+
aot: optional(Boolean2(), "[deploy] Experimental: execute trusted local Elysia initialization to precompile before uploading; off by default"),
|
|
16130
|
+
"aot-entry": optional(String2(), "[deploy --aot] Module exporting the Elysia app (defaults to path)"),
|
|
16016
16131
|
verify_jwt: optional(Boolean2(), "[deploy/deploy_bundle/config] Set JWT verification for this function"),
|
|
16132
|
+
bytecode: optional(withDescription(stringEnum2(["off", "auto", "required"]), "[deploy/deploy_bundle/config] Generate Bun bytecode during Edge Runtime preheat")),
|
|
16017
16133
|
background_routes: withDescription(backgroundRoutesSchema, "[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI"),
|
|
16018
16134
|
framework: optional(withDescription(stringEnum2(["fetch", "elysia", "hono", "sveltekit-function"]), "[deploy/deploy_bundle/config/scaffold] Fetch framework adapter profile")),
|
|
16019
16135
|
capabilities: optional(functionCapabilitiesSchema, "[deploy/deploy_bundle/config] Host capabilities: secrets, outbound_hosts, bindings, background"),
|
|
@@ -16023,6 +16139,13 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
|
|
|
16023
16139
|
"expected-active-version": withDescription(expectedActiveVersionSchema, "[deploy/deploy_bundle/activate] Required current active version, or 'absent' when none exists"),
|
|
16024
16140
|
"expected-activation-id": withDescription(expectedActivationIdSchema, "[deploy/deploy_bundle/config/activate/delete] Required activation ID from list, or 'legacy' for a new or legacy function")
|
|
16025
16141
|
}, async (args) => {
|
|
16142
|
+
if (args.aot === true && options.readOnly) {
|
|
16143
|
+
return {
|
|
16144
|
+
isError: true,
|
|
16145
|
+
content: [{ type: "text", text: "⚠️ Edge Function AOT deployment blocked in read-only mode." }]
|
|
16146
|
+
};
|
|
16147
|
+
}
|
|
16148
|
+
validateFunctionAot(args);
|
|
16026
16149
|
if (args.action === "activate")
|
|
16027
16150
|
return activateFunctionVersion(http, args, options.readOnly);
|
|
16028
16151
|
if (args.action === "scaffold") {
|
|
@@ -16037,6 +16160,7 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
|
|
|
16037
16160
|
entrypoint,
|
|
16038
16161
|
minify,
|
|
16039
16162
|
verify_jwt,
|
|
16163
|
+
bytecode,
|
|
16040
16164
|
background_routes,
|
|
16041
16165
|
framework,
|
|
16042
16166
|
capabilities,
|
|
@@ -16060,6 +16184,7 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
|
|
|
16060
16184
|
} : undefined;
|
|
16061
16185
|
const functionConfig = () => ({
|
|
16062
16186
|
...typeof verify_jwt === "boolean" ? { verify_jwt } : {},
|
|
16187
|
+
...bytecode === undefined ? {} : { bytecode },
|
|
16063
16188
|
...Array.isArray(background_routes) ? { background_routes } : {},
|
|
16064
16189
|
...framework === undefined ? {} : { framework },
|
|
16065
16190
|
...capabilities === undefined ? {} : { capabilities },
|
|
@@ -16368,13 +16493,13 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
|
|
|
16368
16493
|
}
|
|
16369
16494
|
|
|
16370
16495
|
// src/shared/tools/frontend-release-control.ts
|
|
16371
|
-
import { createHash as
|
|
16496
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
16372
16497
|
import { constants as fsConstants2 } from "node:fs";
|
|
16373
16498
|
import { open as open2 } from "node:fs/promises";
|
|
16374
|
-
import { resolve as
|
|
16499
|
+
import { resolve as resolve8 } from "node:path";
|
|
16375
16500
|
|
|
16376
16501
|
// src/shared/mutation-protocol.ts
|
|
16377
|
-
import { createHash as
|
|
16502
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
16378
16503
|
var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
16379
16504
|
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
16380
16505
|
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
@@ -16453,7 +16578,7 @@ function stableStringifyNode(jsonValue) {
|
|
|
16453
16578
|
}
|
|
16454
16579
|
function mutationRequestFingerprint(normalizedRequest) {
|
|
16455
16580
|
assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
|
|
16456
|
-
return
|
|
16581
|
+
return createHash6("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
|
|
16457
16582
|
}
|
|
16458
16583
|
function objectRecord3(candidate) {
|
|
16459
16584
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
@@ -16742,7 +16867,7 @@ function sameArchiveIdentity(left, right) {
|
|
|
16742
16867
|
return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
16743
16868
|
}
|
|
16744
16869
|
async function archiveSha256(handle, sizeBytes) {
|
|
16745
|
-
const hash =
|
|
16870
|
+
const hash = createHash7("sha256");
|
|
16746
16871
|
const chunk = new Uint8Array(Math.min(sizeBytes, ARCHIVE_CHUNK_BYTES));
|
|
16747
16872
|
for (let offset = 0;offset < sizeBytes; ) {
|
|
16748
16873
|
const requested = Math.min(chunk.byteLength, sizeBytes - offset);
|
|
@@ -16755,7 +16880,7 @@ async function archiveSha256(handle, sizeBytes) {
|
|
|
16755
16880
|
return hash.digest("hex");
|
|
16756
16881
|
}
|
|
16757
16882
|
async function verifiedArchive(path) {
|
|
16758
|
-
const archivePath =
|
|
16883
|
+
const archivePath = resolve8(path);
|
|
16759
16884
|
const handle = await open2(archivePath, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
|
|
16760
16885
|
try {
|
|
16761
16886
|
const before = await handle.stat({ bigint: true });
|
|
@@ -16877,7 +17002,7 @@ function stableJson(candidate) {
|
|
|
16877
17002
|
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
16878
17003
|
}
|
|
16879
17004
|
function activationFingerprint(identity) {
|
|
16880
|
-
return
|
|
17005
|
+
return createHash7("sha256").update(stableJson({
|
|
16881
17006
|
project_ref: identity.projectRef,
|
|
16882
17007
|
deployment_id: identity.deploymentId,
|
|
16883
17008
|
release_id: identity.releaseId,
|
|
@@ -20365,7 +20490,7 @@ function registerBranchTools(server, http, options = {}) {
|
|
|
20365
20490
|
|
|
20366
20491
|
// src/shared/tools/supabase-cli-tools.ts
|
|
20367
20492
|
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20368
|
-
import { dirname as
|
|
20493
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join6, resolve as resolve9 } from "node:path";
|
|
20369
20494
|
var SENSITIVE_ENV_KEY = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIALS?|AUTHORIZATION|AUTH|SESSION|COOKIE|BEARER|DB_URI|DB_URL|DSN|DATABASE_URL|DATABASE_URI|CONNECTION_STRING|CONNECTION_URI)(?:_|$)/i;
|
|
20370
20495
|
var VALID_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
20371
20496
|
var VALID_MIGRATION_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
|
|
@@ -20410,12 +20535,12 @@ function schemaArguments(schema) {
|
|
|
20410
20535
|
function workdirArguments(workdir) {
|
|
20411
20536
|
if (!workdir)
|
|
20412
20537
|
throw new Error("A workdir is required");
|
|
20413
|
-
return ["--workdir",
|
|
20538
|
+
return ["--workdir", resolve9(workdir)];
|
|
20414
20539
|
}
|
|
20415
20540
|
function resolveOutputPath(workdir, file, label) {
|
|
20416
20541
|
if (!file || /[\r\n\0]/.test(file))
|
|
20417
20542
|
throw new Error(`${label} file is required`);
|
|
20418
|
-
return isAbsolute2(file) ?
|
|
20543
|
+
return isAbsolute2(file) ? resolve9(file) : resolve9(workdir, file);
|
|
20419
20544
|
}
|
|
20420
20545
|
function databaseTargetArguments(databaseUrl) {
|
|
20421
20546
|
return databaseUrl ? ["--db-url", requirePostgresUrl(databaseUrl)] : ["--local"];
|
|
@@ -20590,7 +20715,7 @@ function actionArguments(request, workdir) {
|
|
|
20590
20715
|
function buildOfficialSupabaseArgs(request) {
|
|
20591
20716
|
if (request.action === "version")
|
|
20592
20717
|
return ["--version"];
|
|
20593
|
-
const workdir = request.workdir ?
|
|
20718
|
+
const workdir = request.workdir ? resolve9(request.workdir) : undefined;
|
|
20594
20719
|
if (!workdir)
|
|
20595
20720
|
throw new Error("A workdir is required");
|
|
20596
20721
|
return [...actionArguments(request, workdir), ...workdirArguments(workdir)];
|
|
@@ -20667,13 +20792,13 @@ function resolveOfficialSupabaseCommand(workdir, environment = process.env) {
|
|
|
20667
20792
|
}
|
|
20668
20793
|
return ["npx", "--yes", `supabase@${version}`];
|
|
20669
20794
|
}
|
|
20670
|
-
const localPackageEntry = join6(
|
|
20795
|
+
const localPackageEntry = join6(resolve9(workdir), "node_modules", "supabase", "dist", "supabase.js");
|
|
20671
20796
|
if (existsSync6(localPackageEntry))
|
|
20672
20797
|
return [process.execPath, localPackageEntry];
|
|
20673
20798
|
return ["supabase"];
|
|
20674
20799
|
}
|
|
20675
20800
|
function resolveExistingWorkdir(workdirInput, fallback) {
|
|
20676
|
-
const workdir =
|
|
20801
|
+
const workdir = resolve9(workdirInput || fallback);
|
|
20677
20802
|
if (!existsSync6(workdir) || !statSync5(workdir).isDirectory()) {
|
|
20678
20803
|
throw new Error(`Supabase workdir not found: ${workdir}`);
|
|
20679
20804
|
}
|
|
@@ -20774,7 +20899,7 @@ async function executeMigrationPush(request, runtime) {
|
|
|
20774
20899
|
if (!projectRef)
|
|
20775
20900
|
return missingProjectRefResult();
|
|
20776
20901
|
const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
|
|
20777
|
-
const migrationDirectory =
|
|
20902
|
+
const migrationDirectory = resolve9(workdir, request.dir || "supabase/migrations");
|
|
20778
20903
|
const migrationResponse = await pushMigrations({
|
|
20779
20904
|
action: "push_migrations",
|
|
20780
20905
|
ref: projectRef,
|
|
@@ -20811,7 +20936,7 @@ async function executeOfficialAction(request, runtime) {
|
|
|
20811
20936
|
buildOfficialSupabaseArgs(normalizedRequest);
|
|
20812
20937
|
const outputPath = actionOutputPath(normalizedRequest, workdir);
|
|
20813
20938
|
if (outputPath)
|
|
20814
|
-
mkdirSync2(
|
|
20939
|
+
mkdirSync2(dirname4(outputPath), { recursive: true });
|
|
20815
20940
|
const secrets = sensitiveValues(runtime.environment, request.db_url);
|
|
20816
20941
|
const execution = await runtime.executeOfficialCli(normalizedRequest);
|
|
20817
20942
|
if (execution.exitCode === 0 && request.action === "gen_types" && outputPath) {
|
|
@@ -20887,11 +21012,11 @@ function registerSupabaseCliTools(server, options = {}) {
|
|
|
20887
21012
|
|
|
20888
21013
|
// src/shared/tools/lite-cli-tools.ts
|
|
20889
21014
|
import { existsSync as existsSync8, statSync as statSync7 } from "node:fs";
|
|
20890
|
-
import { resolve as
|
|
21015
|
+
import { resolve as resolve11 } from "node:path";
|
|
20891
21016
|
|
|
20892
21017
|
// src/shared/tools/lite-cli-command.ts
|
|
20893
|
-
import { existsSync as existsSync7, readFileSync as
|
|
20894
|
-
import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as
|
|
21018
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync6 } from "node:fs";
|
|
21019
|
+
import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve10 } from "node:path";
|
|
20895
21020
|
function isRecord5(value) {
|
|
20896
21021
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20897
21022
|
}
|
|
@@ -20902,11 +21027,11 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
20902
21027
|
throw new Error("Invalid SUPACLOUD_LITE_CLI_BIN");
|
|
20903
21028
|
return [explicitBinary];
|
|
20904
21029
|
}
|
|
20905
|
-
const packageRoot = join7(
|
|
21030
|
+
const packageRoot = join7(resolve10(workdir), "node_modules", "@supacloud", "lite");
|
|
20906
21031
|
const manifestPath = join7(packageRoot, "package.json");
|
|
20907
21032
|
if (!existsSync7(manifestPath))
|
|
20908
21033
|
return ["supacloud-lite"];
|
|
20909
|
-
const manifest = JSON.parse(
|
|
21034
|
+
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
20910
21035
|
if (!isRecord5(manifest) || manifest.name !== "@supacloud/lite" || !isRecord5(manifest.bin)) {
|
|
20911
21036
|
throw new Error("Invalid installed @supacloud/lite package manifest");
|
|
20912
21037
|
}
|
|
@@ -20914,7 +21039,7 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
20914
21039
|
if (typeof entry !== "string" || !entry.trim() || entry.includes("\x00") || isAbsolute3(entry)) {
|
|
20915
21040
|
throw new Error("Invalid installed @supacloud/lite bin");
|
|
20916
21041
|
}
|
|
20917
|
-
const executable =
|
|
21042
|
+
const executable = resolve10(packageRoot, entry);
|
|
20918
21043
|
const packageRelative = relative3(packageRoot, executable);
|
|
20919
21044
|
if (!packageRelative || packageRelative === ".." || packageRelative.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute3(packageRelative)) {
|
|
20920
21045
|
throw new Error("Installed @supacloud/lite bin must stay inside its package");
|
|
@@ -20926,7 +21051,7 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
20926
21051
|
}
|
|
20927
21052
|
// src/shared/tools/lite-cli-tools.ts
|
|
20928
21053
|
function requireWorkdir(workdir, fallback) {
|
|
20929
|
-
const resolved =
|
|
21054
|
+
const resolved = resolve11(workdir || fallback);
|
|
20930
21055
|
if (!existsSync8(resolved) || !statSync7(resolved).isDirectory()) {
|
|
20931
21056
|
throw new Error(`Lite workdir not found: ${resolved}`);
|
|
20932
21057
|
}
|
|
@@ -21135,12 +21260,12 @@ import {
|
|
|
21135
21260
|
mkdirSync as mkdirSync3,
|
|
21136
21261
|
mkdtempSync as mkdtempSync2,
|
|
21137
21262
|
readdirSync as readdirSync4,
|
|
21138
|
-
readFileSync as
|
|
21263
|
+
readFileSync as readFileSync8,
|
|
21139
21264
|
renameSync,
|
|
21140
21265
|
rmSync as rmSync2
|
|
21141
21266
|
} from "node:fs";
|
|
21142
21267
|
import { homedir as homedir2 } from "node:os";
|
|
21143
|
-
import { dirname as
|
|
21268
|
+
import { dirname as dirname5, join as join8, relative as relative4, resolve as resolve12, sep as sep3 } from "node:path";
|
|
21144
21269
|
import { fileURLToPath } from "node:url";
|
|
21145
21270
|
var SKILL_NAME = "supacloud";
|
|
21146
21271
|
function regularFiles(rootDirectory, currentDirectory = rootDirectory) {
|
|
@@ -21163,7 +21288,7 @@ function directoriesMatch(sourceDirectory, destinationDirectory) {
|
|
|
21163
21288
|
const destinationFiles = regularFiles(destinationDirectory);
|
|
21164
21289
|
if (sourceFiles.join("\x00") !== destinationFiles.join("\x00"))
|
|
21165
21290
|
return false;
|
|
21166
|
-
return sourceFiles.every((file) =>
|
|
21291
|
+
return sourceFiles.every((file) => readFileSync8(join8(sourceDirectory, file)).equals(readFileSync8(join8(destinationDirectory, file))));
|
|
21167
21292
|
}
|
|
21168
21293
|
function backupTimestamp(now) {
|
|
21169
21294
|
return now.toISOString().replace(/[-:.]/g, "");
|
|
@@ -21213,8 +21338,8 @@ function replaceSkill(sourceDirectory, targetRoot, destinationDirectory, backupD
|
|
|
21213
21338
|
}
|
|
21214
21339
|
}
|
|
21215
21340
|
function skillSummary(request, action, files, backupDirectory) {
|
|
21216
|
-
const sourceDirectory =
|
|
21217
|
-
const targetRoot =
|
|
21341
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
21342
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
21218
21343
|
return {
|
|
21219
21344
|
name: SKILL_NAME,
|
|
21220
21345
|
sourceDirectory,
|
|
@@ -21228,8 +21353,8 @@ function skillSummary(request, action, files, backupDirectory) {
|
|
|
21228
21353
|
};
|
|
21229
21354
|
}
|
|
21230
21355
|
function installSkill(request) {
|
|
21231
|
-
const sourceDirectory =
|
|
21232
|
-
const targetRoot =
|
|
21356
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
21357
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
21233
21358
|
const destinationDirectory = join8(targetRoot, SKILL_NAME);
|
|
21234
21359
|
if (!existsSync9(join8(sourceDirectory, "SKILL.md"))) {
|
|
21235
21360
|
throw new Error(`Bundled SupaCloud CLI skill not found: ${sourceDirectory}`);
|
|
@@ -21246,16 +21371,16 @@ function installSkill(request) {
|
|
|
21246
21371
|
return installReplacementSkill(request, files);
|
|
21247
21372
|
}
|
|
21248
21373
|
function installNewSkill(request, files) {
|
|
21249
|
-
const sourceDirectory =
|
|
21250
|
-
const targetRoot =
|
|
21374
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
21375
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
21251
21376
|
if (request.mode === "write") {
|
|
21252
21377
|
createSkill(sourceDirectory, targetRoot, join8(targetRoot, SKILL_NAME));
|
|
21253
21378
|
}
|
|
21254
21379
|
return skillSummary(request, "create", files, null);
|
|
21255
21380
|
}
|
|
21256
21381
|
function installReplacementSkill(request, files) {
|
|
21257
|
-
const sourceDirectory =
|
|
21258
|
-
const targetRoot =
|
|
21382
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
21383
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
21259
21384
|
const destinationDirectory = join8(targetRoot, SKILL_NAME);
|
|
21260
21385
|
const backupDirectory = availableBackupDirectory(destinationDirectory, request.now);
|
|
21261
21386
|
if (request.mode === "write") {
|
|
@@ -21265,13 +21390,13 @@ function installReplacementSkill(request, files) {
|
|
|
21265
21390
|
}
|
|
21266
21391
|
function resolveDefaultCodexSkillRoot(environment = process.env, homeDirectory = homedir2()) {
|
|
21267
21392
|
const codexHome = environment.CODEX_HOME?.trim();
|
|
21268
|
-
return join8(
|
|
21393
|
+
return join8(resolve12(codexHome || join8(homeDirectory, ".codex")), "skills");
|
|
21269
21394
|
}
|
|
21270
21395
|
function resolveBundledSkillDirectory(moduleUrl = import.meta.url) {
|
|
21271
|
-
const moduleDirectory =
|
|
21396
|
+
const moduleDirectory = dirname5(fileURLToPath(moduleUrl));
|
|
21272
21397
|
const candidates = [
|
|
21273
|
-
|
|
21274
|
-
|
|
21398
|
+
resolve12(moduleDirectory, "../../../skills", SKILL_NAME),
|
|
21399
|
+
resolve12(moduleDirectory, "../skills", SKILL_NAME)
|
|
21275
21400
|
];
|
|
21276
21401
|
const skillDirectory = candidates.find((candidate) => existsSync9(join8(candidate, "SKILL.md")));
|
|
21277
21402
|
if (!skillDirectory)
|
|
@@ -21309,9 +21434,9 @@ function registerAiTools(server) {
|
|
|
21309
21434
|
}
|
|
21310
21435
|
|
|
21311
21436
|
// src/shared/tools/app-tools.ts
|
|
21312
|
-
import { existsSync as existsSync11, readFileSync as
|
|
21437
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
21313
21438
|
import { mkdir as mkdir5, readFile as readFile4, readdir as readdir5, writeFile as writeFile4 } from "node:fs/promises";
|
|
21314
|
-
import { basename as basename7, dirname as
|
|
21439
|
+
import { basename as basename7, dirname as dirname9, join as join13, relative as relative7, resolve as resolve18 } from "node:path";
|
|
21315
21440
|
import {
|
|
21316
21441
|
applyDiagnosticFix,
|
|
21317
21442
|
buildDeliveryProject,
|
|
@@ -21319,12 +21444,14 @@ import {
|
|
|
21319
21444
|
compileProject,
|
|
21320
21445
|
compileOptionsFromConfig,
|
|
21321
21446
|
createContextPack,
|
|
21447
|
+
createExecutionContextPack,
|
|
21322
21448
|
createDiagnosticRepairPlan,
|
|
21323
21449
|
doctorProject,
|
|
21324
21450
|
loadSupacloudConfig,
|
|
21325
21451
|
formatDeliveryPlan,
|
|
21326
21452
|
planDeliveryProject,
|
|
21327
21453
|
resolveSupacloudConfig,
|
|
21454
|
+
readExecutionMetadata,
|
|
21328
21455
|
watchProject
|
|
21329
21456
|
} from "@supacloud/compiler";
|
|
21330
21457
|
|
|
@@ -21447,25 +21574,25 @@ var starter_metadata_default = {
|
|
|
21447
21574
|
generatedBy: "supacloud/starter-metadata-v1",
|
|
21448
21575
|
packages: {
|
|
21449
21576
|
compiler: {
|
|
21450
|
-
version: "0.
|
|
21577
|
+
version: "0.47.1"
|
|
21451
21578
|
},
|
|
21452
21579
|
app: {
|
|
21453
|
-
version: "0.
|
|
21580
|
+
version: "0.28.1",
|
|
21454
21581
|
dependencies: {
|
|
21455
21582
|
rxjs: "7.8.2"
|
|
21456
21583
|
}
|
|
21457
21584
|
},
|
|
21458
21585
|
elysia: {
|
|
21459
|
-
version: "0.33.
|
|
21586
|
+
version: "0.33.3"
|
|
21460
21587
|
},
|
|
21461
21588
|
commands: {
|
|
21462
|
-
version: "0.9.
|
|
21589
|
+
version: "0.9.5"
|
|
21463
21590
|
},
|
|
21464
21591
|
contracts: {
|
|
21465
|
-
version: "0.11.
|
|
21592
|
+
version: "0.11.3"
|
|
21466
21593
|
},
|
|
21467
21594
|
db: {
|
|
21468
|
-
version: "0.18.
|
|
21595
|
+
version: "0.18.2",
|
|
21469
21596
|
peerDependencies: {
|
|
21470
21597
|
"drizzle-orm": "1.0.0-rc.5-5935859"
|
|
21471
21598
|
},
|
|
@@ -21474,7 +21601,7 @@ var starter_metadata_default = {
|
|
|
21474
21601
|
}
|
|
21475
21602
|
},
|
|
21476
21603
|
sdk: {
|
|
21477
|
-
version: "0.43.
|
|
21604
|
+
version: "0.43.2",
|
|
21478
21605
|
peerDependencies: {
|
|
21479
21606
|
"@supabase/supabase-js": "^2.117.3"
|
|
21480
21607
|
}
|
|
@@ -21579,7 +21706,7 @@ test("cancelled stream consumers release their subscription", async () => {
|
|
|
21579
21706
|
|
|
21580
21707
|
// src/shared/tools/app-starter.ts
|
|
21581
21708
|
import { lstat as lstat2, mkdir as mkdir3, readdir as readdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
21582
|
-
import { basename as basename6, dirname as
|
|
21709
|
+
import { basename as basename6, dirname as dirname6, join as join9, resolve as resolve13 } from "node:path";
|
|
21583
21710
|
|
|
21584
21711
|
// src/shared/tools/app-starter-environment.ts
|
|
21585
21712
|
var STARTER_ENVIRONMENT = String.raw`import { existsSync, readFileSync } from "node:fs";
|
|
@@ -22264,10 +22391,11 @@ var STARTER_REVIEW_JOB = `import { DB_CLIENT, Inject, Injectable, Job } from "@s
|
|
|
22264
22391
|
import { ApplicationError } from "@supacloud/elysia";
|
|
22265
22392
|
import { t } from "elysia";
|
|
22266
22393
|
import type { Static } from "elysia/type";
|
|
22394
|
+
import { REVIEW_VERSION_MAX } from "./limits";
|
|
22267
22395
|
|
|
22268
22396
|
export const AttachmentInput = t.Object({
|
|
22269
22397
|
reviewId: t.String({ minLength: 1, maxLength: 100 }),
|
|
22270
|
-
version: t.Integer({ minimum: 2 }),
|
|
22398
|
+
version: t.Integer({ minimum: 2, maximum: REVIEW_VERSION_MAX }),
|
|
22271
22399
|
artifactId: t.String({ format: "uuid" }),
|
|
22272
22400
|
}, { additionalProperties: false });
|
|
22273
22401
|
|
|
@@ -23701,11 +23829,19 @@ export function createReviewClient(transport: ReviewTransport, pending?: ReviewP
|
|
|
23701
23829
|
try { listener(); } catch {}
|
|
23702
23830
|
}
|
|
23703
23831
|
};
|
|
23704
|
-
function
|
|
23832
|
+
function outcome(operation: ReviewOperation, result: unknown): ReviewWriteResult["status"] {
|
|
23833
|
+
try {
|
|
23834
|
+
if (result === null || typeof result !== "object" || Array.isArray(result) || !("status" in result)) return "unknown";
|
|
23835
|
+
const status = result.status;
|
|
23836
|
+
if (status === "denied" || status === "conflict") return status;
|
|
23837
|
+
if (status === "confirmed" && "operationId" in result && "reviewId" in result
|
|
23838
|
+
&& result.operationId === operation.operationId && result.reviewId === operation.reviewId) return "confirmed";
|
|
23839
|
+
} catch { /* Invalid adapters must not discard recovery evidence. */ }
|
|
23840
|
+
return "unknown";
|
|
23841
|
+
}
|
|
23842
|
+
function finish(operation: ReviewOperation, result: unknown) {
|
|
23705
23843
|
if (closed) return;
|
|
23706
|
-
let status = result
|
|
23707
|
-
&& (result.operationId !== operation.operationId || result.reviewId !== operation.reviewId)
|
|
23708
|
-
? "unknown" : result.status;
|
|
23844
|
+
let status = outcome(operation, result);
|
|
23709
23845
|
if (status !== "unknown") {
|
|
23710
23846
|
try {
|
|
23711
23847
|
if (pending) {
|
|
@@ -23722,6 +23858,7 @@ export function createReviewClient(transport: ReviewTransport, pending?: ReviewP
|
|
|
23722
23858
|
const request = ++generation;
|
|
23723
23859
|
state = { ...state, list: { status: "loading", rows: [] } };
|
|
23724
23860
|
notify();
|
|
23861
|
+
if (closed || request !== generation) return;
|
|
23725
23862
|
try {
|
|
23726
23863
|
const rows = await transport.list();
|
|
23727
23864
|
if (closed || request !== generation) return;
|
|
@@ -23786,7 +23923,10 @@ export function createReviewClient(transport: ReviewTransport, pending?: ReviewP
|
|
|
23786
23923
|
try {
|
|
23787
23924
|
const result = await transport.lookup(structuredClone(operation));
|
|
23788
23925
|
// Denial or conflict during lookup does not prove the original write did not commit.
|
|
23789
|
-
|
|
23926
|
+
// Normalize once: custom adapter accessors may change between reads.
|
|
23927
|
+
finish(operation, outcome(operation, result) === "confirmed"
|
|
23928
|
+
? { status: "confirmed", operationId: operation.operationId, reviewId: operation.reviewId }
|
|
23929
|
+
: { status: "unknown" });
|
|
23790
23930
|
if (snapshot().mutation.status === "confirmed") await load();
|
|
23791
23931
|
} catch {
|
|
23792
23932
|
finish(operation, { status: "unknown" });
|
|
@@ -23959,6 +24099,34 @@ test("list has loading, empty, permission and failure states; old results cannot
|
|
|
23959
24099
|
expect(JSON.stringify(failed.snapshot())).not.toContain("private");
|
|
23960
24100
|
}
|
|
23961
24101
|
});
|
|
24102
|
+
test("disposing from a loading subscriber prevents read dispatch", async () => {
|
|
24103
|
+
let reads = 0;
|
|
24104
|
+
const client = createReviewClient(transport({ list: async () => { reads++; return [draft]; } }));
|
|
24105
|
+
client.subscribe(() => {
|
|
24106
|
+
if (client.snapshot().list.status === "loading") client.dispose();
|
|
24107
|
+
});
|
|
24108
|
+
await client.load();
|
|
24109
|
+
expect(reads).toBe(0);
|
|
24110
|
+
await client.load();
|
|
24111
|
+
expect(reads).toBe(0);
|
|
24112
|
+
});
|
|
24113
|
+
|
|
24114
|
+
test("a reentrant newer load supersedes the outer read before dispatch", async () => {
|
|
24115
|
+
let reads = 0, replaced = false;
|
|
24116
|
+
let replacement: Promise<void> | undefined;
|
|
24117
|
+
const client = createReviewClient(transport({ list: async () => { reads++; return [draft]; } }));
|
|
24118
|
+
client.subscribe(() => {
|
|
24119
|
+
if (!replaced && client.snapshot().list.status === "loading") {
|
|
24120
|
+
replaced = true;
|
|
24121
|
+
replacement = client.load();
|
|
24122
|
+
}
|
|
24123
|
+
});
|
|
24124
|
+
await client.load();
|
|
24125
|
+
await replacement;
|
|
24126
|
+
expect(reads).toBe(1);
|
|
24127
|
+
expect(client.snapshot().list).toEqual({ status: "ready", rows: [draft] });
|
|
24128
|
+
});
|
|
24129
|
+
|
|
23962
24130
|
test("double submit sends one write; unknown retains its identity and only a lookup can confirm it", async () => {
|
|
23963
24131
|
let sends = 0, lookups = 0, resolve: (result: ReviewWriteResult) => void = () => {};
|
|
23964
24132
|
const client = createReviewClient(transport({
|
|
@@ -23991,6 +24159,72 @@ test("conflict refreshes but does not retry the write; wrong receipt remains unk
|
|
|
23991
24159
|
await wrong.approve(draft);
|
|
23992
24160
|
expect(wrong.snapshot().mutation.status).toBe("unknown");
|
|
23993
24161
|
});
|
|
24162
|
+
|
|
24163
|
+
test("malformed adapter outcomes preserve pending evidence across writes and recovery", async () => {
|
|
24164
|
+
const malformed: unknown[] = [
|
|
24165
|
+
null, undefined, false, [], "confirmed", {}, { status: "complete" }, { status: 200 },
|
|
24166
|
+
{ status: "confirmed" }, { status: "confirmed", operationId: "foreign", reviewId: draft.id },
|
|
24167
|
+
{ get status() { throw new Error("private adapter detail"); } },
|
|
24168
|
+
];
|
|
24169
|
+
for (const kind of ["approve", "upload"] as const) {
|
|
24170
|
+
for (const value of malformed) {
|
|
24171
|
+
let saved: ReviewOperation | null = null, sends = 0, clears = 0, reads = 0, recovered = false;
|
|
24172
|
+
const readSaved = (): ReviewOperation | null => saved;
|
|
24173
|
+
// Simulate an untyped/custom adapter crossing the public TypeScript boundary.
|
|
24174
|
+
const invalid = value as ReviewWriteResult;
|
|
24175
|
+
const send = async () => { sends++; return invalid; };
|
|
24176
|
+
const client = createReviewClient(transport({
|
|
24177
|
+
approve: send, upload: send,
|
|
24178
|
+
list: async () => { reads++; return []; },
|
|
24179
|
+
lookup: async op => recovered
|
|
24180
|
+
? { status: "confirmed", operationId: op.operationId, reviewId: op.reviewId } : invalid,
|
|
24181
|
+
}), {
|
|
24182
|
+
load: () => saved, save: op => { saved = op; },
|
|
24183
|
+
clear: () => { clears++; saved = null; },
|
|
24184
|
+
});
|
|
24185
|
+
const submit = () => kind === "approve" ? client.approve(draft)
|
|
24186
|
+
: client.upload(uploadDraft, new Blob(["hello"], { type: "text/plain" }));
|
|
24187
|
+
await submit();
|
|
24188
|
+
const original = client.snapshot().mutation.operation;
|
|
24189
|
+
expect(original).not.toBeNull();
|
|
24190
|
+
expect(readSaved()).toEqual(original);
|
|
24191
|
+
expect(client.snapshot().mutation).toEqual({ status: "unknown", operation: original });
|
|
24192
|
+
await client.reconcile();
|
|
24193
|
+
await submit();
|
|
24194
|
+
expect(client.snapshot().mutation).toEqual({ status: "unknown", operation: original });
|
|
24195
|
+
expect(readSaved()).toEqual(original);
|
|
24196
|
+
expect(sends).toBe(1); expect(clears).toBe(0); expect(reads).toBe(0);
|
|
24197
|
+
expect(JSON.stringify(client.snapshot())).not.toContain("private adapter detail");
|
|
24198
|
+
recovered = true;
|
|
24199
|
+
await client.reconcile();
|
|
24200
|
+
expect(client.snapshot().mutation.status).toBe("confirmed");
|
|
24201
|
+
expect(readSaved()).toBeNull();
|
|
24202
|
+
expect(sends).toBe(1); expect(clears).toBe(1); expect(reads).toBe(1);
|
|
24203
|
+
}
|
|
24204
|
+
}
|
|
24205
|
+
});
|
|
24206
|
+
test("recovery validates adapter accessors once before clearing pending evidence", async () => {
|
|
24207
|
+
let saved: ReviewOperation | null = null, reads = 0, sends = 0, clears = 0;
|
|
24208
|
+
const client = createReviewClient(transport({
|
|
24209
|
+
approve: async () => { sends++; return { status: "unknown" }; },
|
|
24210
|
+
lookup: async operation => ({
|
|
24211
|
+
get status() { return ++reads === 1 ? "confirmed" as const : "denied" as const; },
|
|
24212
|
+
operationId: operation.operationId, reviewId: operation.reviewId,
|
|
24213
|
+
}),
|
|
24214
|
+
}), {
|
|
24215
|
+
load: () => saved, save: operation => { saved = operation; },
|
|
24216
|
+
clear: () => { clears++; saved = null; },
|
|
24217
|
+
});
|
|
24218
|
+
await client.approve(draft);
|
|
24219
|
+
expect(client.snapshot().mutation.status).toBe("unknown");
|
|
24220
|
+
await client.reconcile();
|
|
24221
|
+
expect(reads).toBe(1);
|
|
24222
|
+
expect(client.snapshot().mutation.status).toBe("confirmed");
|
|
24223
|
+
expect(saved).toBeNull();
|
|
24224
|
+
expect(clears).toBe(1);
|
|
24225
|
+
expect(sends).toBe(1);
|
|
24226
|
+
});
|
|
24227
|
+
|
|
23994
24228
|
test("upload validates size/type, retains failure identity, and disposal drops late results", async () => {
|
|
23995
24229
|
let uploads = 0;
|
|
23996
24230
|
const client = createReviewClient(transport({ upload: async () => { uploads++; throw new Error("private"); } }));
|
|
@@ -24483,6 +24717,12 @@ export async function listReviews(
|
|
|
24483
24717
|
const { data, error, count } = await (signal ? query.abortSignal(signal) : query);
|
|
24484
24718
|
if (error) throw error;
|
|
24485
24719
|
if (data === null || count === null) throw new Error("Incomplete review page response");
|
|
24720
|
+
if (!Array.isArray(data)
|
|
24721
|
+
|| !Number.isSafeInteger(count) || count < 0
|
|
24722
|
+
|| data.length > pageSize
|
|
24723
|
+
|| (data.length > 0 && start + data.length > count)) {
|
|
24724
|
+
throw new Error("Invalid review page response");
|
|
24725
|
+
}
|
|
24486
24726
|
return { data, total: count, page, pageSize, hasNextPage: start + data.length < count };
|
|
24487
24727
|
}
|
|
24488
24728
|
`;
|
|
@@ -25706,6 +25946,7 @@ handler contract, not production Storage, queue delivery or external identity.
|
|
|
25706
25946
|
## Inspection And Repair
|
|
25707
25947
|
|
|
25708
25948
|
\`\`\`sh
|
|
25949
|
+
supacloud app context --root . --target review --events execution.ndjson --request-id request-123 --format json
|
|
25709
25950
|
bunx supacloud-compiler context review --root src --json
|
|
25710
25951
|
bunx supacloud-compiler context review --root src --events execution-events.json --request-id request-123 --json
|
|
25711
25952
|
bunx supacloud-compiler explain review --root src
|
|
@@ -25715,6 +25956,11 @@ bunx supacloud-compiler fix ./fix.json --dry-run
|
|
|
25715
25956
|
|
|
25716
25957
|
Context packs include directional dependencies, relevant aspect files, diagnostics
|
|
25717
25958
|
and static execution plans, without provider values or source expressions.
|
|
25959
|
+
The app context entry point retains its module workflow and adds a separate
|
|
25960
|
+
execution evidence block when --events and --request-id are provided together.
|
|
25961
|
+
It requires an explicit --target and --format json, rejects --write, and
|
|
25962
|
+
resolves relative event paths from --root. The top-level context alias supports
|
|
25963
|
+
the same flags. Omit both execution flags for the unchanged authoring context.
|
|
25718
25964
|
The optional --events example expects a trusted host
|
|
25719
25965
|
to capture approved onExecution metadata in { "version": 1, "events": [...] }.
|
|
25720
25966
|
It also accepts a bounded JSON-lines export containing only the generated
|
|
@@ -25746,7 +25992,7 @@ and governance; TypeScript checks both application and generated source.
|
|
|
25746
25992
|
};
|
|
25747
25993
|
}
|
|
25748
25994
|
async function initializeAppProject(options) {
|
|
25749
|
-
const root =
|
|
25995
|
+
const root = resolve13(options.root ?? process.cwd());
|
|
25750
25996
|
const name = options.name ?? basename6(root);
|
|
25751
25997
|
if (!/^[a-z][a-z0-9-]*$/.test(name) || name.length > 100) {
|
|
25752
25998
|
throw new Error("Project name must be lowercase kebab-case (1-100 characters)");
|
|
@@ -25765,7 +26011,7 @@ async function initializeAppProject(options) {
|
|
|
25765
26011
|
});
|
|
25766
26012
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
25767
26013
|
const path = join9(root, relativePath);
|
|
25768
|
-
await mkdir3(
|
|
26014
|
+
await mkdir3(dirname6(path), { recursive: true });
|
|
25769
26015
|
await writeFile3(path, content, { encoding: "utf8", flag: "wx" });
|
|
25770
26016
|
}
|
|
25771
26017
|
return { root, name, files: Object.keys(files) };
|
|
@@ -25773,7 +26019,7 @@ async function initializeAppProject(options) {
|
|
|
25773
26019
|
|
|
25774
26020
|
// src/shared/tools/app-database-check.ts
|
|
25775
26021
|
import { existsSync as existsSync10 } from "node:fs";
|
|
25776
|
-
import { join as join10, resolve as
|
|
26022
|
+
import { join as join10, resolve as resolve14 } from "node:path";
|
|
25777
26023
|
var CHECK_ENTRY = `
|
|
25778
26024
|
import { createRequire } from "node:module";
|
|
25779
26025
|
import { join } from "node:path";
|
|
@@ -25784,7 +26030,7 @@ process.argv = [process.execPath, cli, "check", "--root", root];
|
|
|
25784
26030
|
await import(pathToFileURL(cli).href);
|
|
25785
26031
|
`;
|
|
25786
26032
|
function checkAppDatabaseSources(projectRoot) {
|
|
25787
|
-
const root =
|
|
26033
|
+
const root = resolve14(projectRoot);
|
|
25788
26034
|
if (!existsSync10(join10(root, "database.sources.json")))
|
|
25789
26035
|
return;
|
|
25790
26036
|
const name = "database-source-contracts";
|
|
@@ -26024,6 +26270,7 @@ import type { StaticDecode } from "typebox";
|
|
|
26024
26270
|
|
|
26025
26271
|
export type ${className}ListInput = StaticDecode<typeof ${className}ListQuery>;
|
|
26026
26272
|
export type ${className}ApproveInput = StaticDecode<typeof ${className}ApproveBody>;
|
|
26273
|
+
export type ${className}ApproveCommand = ${className}ApproveInput & { id: string };
|
|
26027
26274
|
export type ${className}Result = StaticDecode<typeof ${className}Row>;
|
|
26028
26275
|
|
|
26029
26276
|
export interface ${className}ReadPort {
|
|
@@ -26032,11 +26279,7 @@ export interface ${className}ReadPort {
|
|
|
26032
26279
|
}
|
|
26033
26280
|
|
|
26034
26281
|
export interface ${className}WritePort {
|
|
26035
|
-
approve(input: {
|
|
26036
|
-
id: string;
|
|
26037
|
-
expectedVersion: number;
|
|
26038
|
-
reason?: string;
|
|
26039
|
-
}): Promise<${className}Result>;
|
|
26282
|
+
approve(input: ${className}ApproveCommand): Promise<${className}Result>;
|
|
26040
26283
|
}
|
|
26041
26284
|
|
|
26042
26285
|
export const ${className}Reader = new InjectionToken<${className}ReadPort>(
|
|
@@ -26244,6 +26487,10 @@ The scaffold does not create migrations, infer permissions, or provide an in-mem
|
|
|
26244
26487
|
production fallback. Database migrations remain the database source of truth.
|
|
26245
26488
|
The HTTP schemas describe the boundary, not a second database model. Bind their
|
|
26246
26489
|
fields to the existing database-source contract checker before delivery.
|
|
26490
|
+
The Writer input is derived from the approval body contract plus the route ID.
|
|
26491
|
+
Add request fields in the contract, not a second handwritten Writer interface.
|
|
26492
|
+
Typecheck application adapters after changing a field type; the derived Port
|
|
26493
|
+
must expose incompatible assumptions in business code.
|
|
26247
26494
|
|
|
26248
26495
|
The named RPC adapter must invoke the continuation in the same database transaction
|
|
26249
26496
|
used by the request-scoped Writer, or own the complete command instead; never execute
|
|
@@ -26289,7 +26536,7 @@ Scenario: Delivery
|
|
|
26289
26536
|
// src/shared/tools/app-local-dev.ts
|
|
26290
26537
|
import { spawn } from "node:child_process";
|
|
26291
26538
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
26292
|
-
import { join as join11, resolve as
|
|
26539
|
+
import { join as join11, resolve as resolve15 } from "node:path";
|
|
26293
26540
|
function localDatabaseUrl(value) {
|
|
26294
26541
|
try {
|
|
26295
26542
|
if (!value || /[\x00-\x20\x7f]/.test(value))
|
|
@@ -26336,7 +26583,7 @@ async function probeDatabase(url, signal) {
|
|
|
26336
26583
|
}
|
|
26337
26584
|
}
|
|
26338
26585
|
async function runLocalDevelopment(options) {
|
|
26339
|
-
const root =
|
|
26586
|
+
const root = resolve15(options.root);
|
|
26340
26587
|
const profile = options.profile ?? "fast";
|
|
26341
26588
|
if (profile !== "fast" && profile !== "integration")
|
|
26342
26589
|
throw new Error("Unknown development profile");
|
|
@@ -26437,7 +26684,7 @@ async function runLocalDevelopment(options) {
|
|
|
26437
26684
|
|
|
26438
26685
|
// src/shared/tools/app-verification-plan.ts
|
|
26439
26686
|
import { readdir as readdir4, realpath as realpath3 } from "node:fs/promises";
|
|
26440
|
-
import { dirname as
|
|
26687
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, relative as relative5, resolve as resolve16 } from "node:path";
|
|
26441
26688
|
function inside2(root, file) {
|
|
26442
26689
|
const path = relative5(root, file).replaceAll("\\", "/");
|
|
26443
26690
|
return path !== ".." && !path.startsWith("../") && !isAbsolute4(path);
|
|
@@ -26448,7 +26695,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26448
26695
|
throw new Error("Select one unambiguous module or owned symbol for verification");
|
|
26449
26696
|
const owner = owners[0];
|
|
26450
26697
|
const project = await realpath3(root);
|
|
26451
|
-
const requestedRoot =
|
|
26698
|
+
const requestedRoot = resolve16(root);
|
|
26452
26699
|
const sources = [
|
|
26453
26700
|
owner.file,
|
|
26454
26701
|
...owner.providers.map((item) => item.file),
|
|
@@ -26459,7 +26706,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26459
26706
|
...(owner.jobs ?? []).flatMap((item) => (item.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])),
|
|
26460
26707
|
...owner.controllers.flatMap((item) => item.routes.flatMap((route) => (route.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])))
|
|
26461
26708
|
].filter((file) => typeof file === "string");
|
|
26462
|
-
const directories = [...new Set(sources.map((file) =>
|
|
26709
|
+
const directories = [...new Set(sources.map((file) => dirname7(resolve16(sourceRoot, file))))].sort();
|
|
26463
26710
|
const tests = new Set;
|
|
26464
26711
|
const manual = [];
|
|
26465
26712
|
const coverage = [];
|
|
@@ -26484,7 +26731,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26484
26731
|
const local = [];
|
|
26485
26732
|
for (const entry of await readdir4(actual, { withFileTypes: true })) {
|
|
26486
26733
|
if (entry.isFile() && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
26487
|
-
const file = relative5(project,
|
|
26734
|
+
const file = relative5(project, resolve16(actual, entry.name)).replaceAll("\\", "/");
|
|
26488
26735
|
tests.add(file);
|
|
26489
26736
|
local.push(file);
|
|
26490
26737
|
}
|
|
@@ -26504,7 +26751,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26504
26751
|
source: "declaration",
|
|
26505
26752
|
target: owner.name,
|
|
26506
26753
|
ready: manual.length === 0,
|
|
26507
|
-
files: [...new Set(sources.map((file) => relative5(requestedRoot,
|
|
26754
|
+
files: [...new Set(sources.map((file) => relative5(requestedRoot, resolve16(sourceRoot, file)).replaceAll("\\", "/")))].sort(),
|
|
26508
26755
|
tests: selected.slice(0, 32),
|
|
26509
26756
|
commands: selected.length <= 32 ? [
|
|
26510
26757
|
...selected.map((file) => ["bun", "test", `./${file}`]),
|
|
@@ -26525,7 +26772,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26525
26772
|
// src/shared/tools/app-scaffold-writes.ts
|
|
26526
26773
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
26527
26774
|
import { link, lstat as lstat3, mkdir as mkdir4, open as open3, readFile as readFile3, realpath as realpath4, rename as rename2, rmdir, unlink } from "node:fs/promises";
|
|
26528
|
-
import { dirname as
|
|
26775
|
+
import { dirname as dirname8, isAbsolute as isAbsolute5, join as join12, relative as relative6, resolve as resolve17, sep as sep4, win32 } from "node:path";
|
|
26529
26776
|
|
|
26530
26777
|
class ScaffoldError extends Error {
|
|
26531
26778
|
code;
|
|
@@ -26546,7 +26793,7 @@ function scaffoldPath(root, path) {
|
|
|
26546
26793
|
if (parts.some((part) => /[<>:"|?*\u0000-\u001f]/.test(part) || /[ .]$/.test(part) && part !== "." && part !== ".." || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part) || part.toLowerCase() === ".git" || part.toLowerCase() === ".supacloud-generate.lock")) {
|
|
26547
26794
|
throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is reserved or not portable");
|
|
26548
26795
|
}
|
|
26549
|
-
const target =
|
|
26796
|
+
const target = resolve17(root, path);
|
|
26550
26797
|
const local = relative6(root, target);
|
|
26551
26798
|
if (!local || local === ".." || local.startsWith(`..${sep4}`) || isAbsolute5(local)) {
|
|
26552
26799
|
throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is outside the project root");
|
|
@@ -26585,7 +26832,7 @@ function sameFile(a, b) {
|
|
|
26585
26832
|
return a === null ? b === null : b !== null && a.content === b.content && a.dev === b.dev && a.ino === b.ino && a.mode === b.mode;
|
|
26586
26833
|
}
|
|
26587
26834
|
async function planScaffoldWrites(root, writes) {
|
|
26588
|
-
const canonical = await realpath4(
|
|
26835
|
+
const canonical = await realpath4(resolve17(root));
|
|
26589
26836
|
const targets = new Set;
|
|
26590
26837
|
const planned = [];
|
|
26591
26838
|
for (const write of writes) {
|
|
@@ -26628,7 +26875,7 @@ async function applyScaffoldWrites(plan) {
|
|
|
26628
26875
|
for (const write of plan.writes) {
|
|
26629
26876
|
const target = scaffoldPath(plan.root, write.path);
|
|
26630
26877
|
let current = plan.root;
|
|
26631
|
-
for (const part of relative6(plan.root,
|
|
26878
|
+
for (const part of relative6(plan.root, dirname8(target)).split(sep4).filter(Boolean)) {
|
|
26632
26879
|
current = join12(current, part);
|
|
26633
26880
|
try {
|
|
26634
26881
|
await mkdir4(current);
|
|
@@ -26894,7 +27141,7 @@ async function generateScaffold(args) {
|
|
|
26894
27141
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `--${flag.replaceAll("_", "-")} must be boolean`);
|
|
26895
27142
|
}
|
|
26896
27143
|
}
|
|
26897
|
-
const root =
|
|
27144
|
+
const root = resolve18(args.root || process.cwd());
|
|
26898
27145
|
const dir = args.dir ?? "src/features";
|
|
26899
27146
|
if (dir !== ".")
|
|
26900
27147
|
scaffoldPath(root, dir);
|
|
@@ -26948,7 +27195,7 @@ async function generateScaffold(args) {
|
|
|
26948
27195
|
if (new RegExp(`\\b${moduleSymbol}\\b`).test(original)) {
|
|
26949
27196
|
throw new ScaffoldError("SCAFFOLD_REGISTRATION_CONFLICT", `Registration target already references ${moduleSymbol}; review the import manually`);
|
|
26950
27197
|
}
|
|
26951
|
-
let importPath = relative7(
|
|
27198
|
+
let importPath = relative7(dirname9(target), resolve18(root, modulePath)).replaceAll("\\", "/").replace(/\.ts$/, "");
|
|
26952
27199
|
if (!importPath.startsWith("."))
|
|
26953
27200
|
importPath = `./${importPath}`;
|
|
26954
27201
|
const edit = await applyDiagnosticFix({
|
|
@@ -27021,13 +27268,13 @@ function sourceRoot(args, root, configured) {
|
|
|
27021
27268
|
return args.root && !hasConfig ? "." : configured;
|
|
27022
27269
|
}
|
|
27023
27270
|
async function projectCompileConfig(args) {
|
|
27024
|
-
const root =
|
|
27271
|
+
const root = resolve18(args.root || process.cwd());
|
|
27025
27272
|
const configFile = ["ts", "mts", "js", "mjs"].map((extension) => join13(root, `supacloud.config.${extension}`)).find((candidate) => existsSync11(candidate)) ?? null;
|
|
27026
27273
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27027
27274
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27028
27275
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
27029
27276
|
const include = parseInclude(args.include) ?? loadedConfig.include;
|
|
27030
|
-
const outDir = args.out_dir ?
|
|
27277
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir;
|
|
27031
27278
|
const compileOptions = compileOptionsFromConfig({
|
|
27032
27279
|
...loadedConfig,
|
|
27033
27280
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
@@ -27074,6 +27321,7 @@ async function runContext(args) {
|
|
|
27074
27321
|
const subject = args.target?.trim();
|
|
27075
27322
|
if (subject) {
|
|
27076
27323
|
const pack = createContextPack({ ...result.graph, diagnostics: result.diagnostics }, subject);
|
|
27324
|
+
const execution = args.events === undefined ? undefined : createExecutionContextPack({ ...result.graph, diagnostics: result.diagnostics }, subject, await readExecutionMetadata(resolve18(root, args.events)), args.request_id);
|
|
27077
27325
|
const verification = await createVerificationPlan(root, result.graph, pack.subject, sourceDir);
|
|
27078
27326
|
const repairs = createDiagnosticRepairPlan(pack.diagnostics);
|
|
27079
27327
|
const workflow = {
|
|
@@ -27104,7 +27352,7 @@ async function runContext(args) {
|
|
|
27104
27352
|
},
|
|
27105
27353
|
executed: false
|
|
27106
27354
|
};
|
|
27107
|
-
return textResult(args.format === "json" ? JSON.stringify({ ...pack, workflow }, null, 2) : [
|
|
27355
|
+
return textResult(args.format === "json" ? JSON.stringify({ ...pack, ...execution ? { execution } : {}, workflow }, null, 2) : [
|
|
27108
27356
|
formatModuleContextPack(pack),
|
|
27109
27357
|
` edit files (project-relative): ${workflow.editFiles.join(", ") || "-"}`,
|
|
27110
27358
|
` focused tests: ${verification.tests.join(", ") || "-"}`,
|
|
@@ -27256,11 +27504,11 @@ async function runDoctor(args) {
|
|
|
27256
27504
|
`), doctor.errors > 0);
|
|
27257
27505
|
}
|
|
27258
27506
|
async function runFix(args) {
|
|
27259
|
-
const root =
|
|
27507
|
+
const root = resolve18(args.root || process.cwd());
|
|
27260
27508
|
const fixPath = args.fix?.trim();
|
|
27261
27509
|
if (!fixPath)
|
|
27262
27510
|
throw new Error("app fix requires --fix <path-to-diagnostic-fix.json>");
|
|
27263
|
-
const parsed = JSON.parse(await readFile4(
|
|
27511
|
+
const parsed = JSON.parse(await readFile4(resolve18(root, fixPath), "utf8"));
|
|
27264
27512
|
const write = args.write === true;
|
|
27265
27513
|
const applied = await applyDiagnosticFix(parsed, { rootDir: root, dryRun: !write });
|
|
27266
27514
|
return textResult(JSON.stringify({
|
|
@@ -27303,7 +27551,7 @@ function devDatabase(profile, explicit) {
|
|
|
27303
27551
|
}
|
|
27304
27552
|
function readManifestModules(outDir) {
|
|
27305
27553
|
try {
|
|
27306
|
-
const parsed = JSON.parse(
|
|
27554
|
+
const parsed = JSON.parse(readFileSync9(join13(outDir, "app.manifest.json"), "utf8"));
|
|
27307
27555
|
return Array.isArray(parsed.modules) ? parsed.modules.map((module) => module.name) : [];
|
|
27308
27556
|
} catch {
|
|
27309
27557
|
return [];
|
|
@@ -27346,12 +27594,12 @@ async function runAppDev(args, options) {
|
|
|
27346
27594
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "app dev --profile must be fast or integration");
|
|
27347
27595
|
}
|
|
27348
27596
|
const database = devDatabase(profile, args.database_url);
|
|
27349
|
-
const root =
|
|
27597
|
+
const root = resolve18(args.root || process.cwd());
|
|
27350
27598
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27351
27599
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27352
27600
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
27353
27601
|
const include = parseInclude(args.include) ?? loadedConfig.include;
|
|
27354
|
-
const outDir = args.out_dir ?
|
|
27602
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir;
|
|
27355
27603
|
const watch = args.once !== true && args.watch !== false;
|
|
27356
27604
|
const compileOptions = compileOptionsFromConfig({
|
|
27357
27605
|
...loadedConfig,
|
|
@@ -27437,7 +27685,7 @@ async function runAppDev(args, options) {
|
|
|
27437
27685
|
return report(lastEvent, "stopped");
|
|
27438
27686
|
}
|
|
27439
27687
|
async function runCompile(args) {
|
|
27440
|
-
const root =
|
|
27688
|
+
const root = resolve18(args.root || process.cwd());
|
|
27441
27689
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27442
27690
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27443
27691
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
@@ -27446,7 +27694,7 @@ async function runCompile(args) {
|
|
|
27446
27694
|
...compileOptionsFromConfig({
|
|
27447
27695
|
...loadedConfig,
|
|
27448
27696
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
27449
|
-
outDir: args.out_dir ?
|
|
27697
|
+
outDir: args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir,
|
|
27450
27698
|
...include === undefined ? {} : { include },
|
|
27451
27699
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
27452
27700
|
}, root),
|
|
@@ -27470,7 +27718,7 @@ async function runCompile(args) {
|
|
|
27470
27718
|
return textResult(text, hasError);
|
|
27471
27719
|
}
|
|
27472
27720
|
async function runCheck(args) {
|
|
27473
|
-
const root =
|
|
27721
|
+
const root = resolve18(args.root || process.cwd());
|
|
27474
27722
|
const database = checkAppDatabaseSources(root);
|
|
27475
27723
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27476
27724
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
@@ -27479,7 +27727,7 @@ async function runCheck(args) {
|
|
|
27479
27727
|
const config = compileOptionsFromConfig({
|
|
27480
27728
|
...loadedConfig,
|
|
27481
27729
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
27482
|
-
outDir: args.out_dir ?
|
|
27730
|
+
outDir: args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir,
|
|
27483
27731
|
...include === undefined ? {} : { include },
|
|
27484
27732
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
27485
27733
|
}, root);
|
|
@@ -27509,7 +27757,7 @@ async function runCheck(args) {
|
|
|
27509
27757
|
}
|
|
27510
27758
|
async function readManifest(root, args) {
|
|
27511
27759
|
const config = await loadSupacloudConfig(root);
|
|
27512
|
-
const outDir = args.out_dir ?
|
|
27760
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : resolveSupacloudConfig(config, root).outDir;
|
|
27513
27761
|
const manifestPath = join13(outDir, "app.manifest.json");
|
|
27514
27762
|
if (!existsSync11(manifestPath)) {
|
|
27515
27763
|
throw new Error(`Manifest not found: ${manifestPath}(先运行 app compile 生成)`);
|
|
@@ -27554,7 +27802,7 @@ function formatGraphText(manifest) {
|
|
|
27554
27802
|
`);
|
|
27555
27803
|
}
|
|
27556
27804
|
async function runGraph(args) {
|
|
27557
|
-
const root =
|
|
27805
|
+
const root = resolve18(args.root || process.cwd());
|
|
27558
27806
|
const manifest = await readManifest(root, args);
|
|
27559
27807
|
if (args.format === "json")
|
|
27560
27808
|
return textResult(JSON.stringify(manifest, null, 2));
|
|
@@ -27622,7 +27870,7 @@ async function runExplain(args) {
|
|
|
27622
27870
|
const target = args.target?.trim();
|
|
27623
27871
|
if (!target)
|
|
27624
27872
|
throw new Error("app explain requires --target(provider 类名 / token 名 / command 名 / job 名)");
|
|
27625
|
-
const root =
|
|
27873
|
+
const root = resolve18(args.root || process.cwd());
|
|
27626
27874
|
const manifest = await readManifest(root, args);
|
|
27627
27875
|
for (const module of manifest.modules) {
|
|
27628
27876
|
const job = module.jobs?.find((entry) => entry.name === target || entry.className === target);
|
|
@@ -27697,10 +27945,10 @@ async function runExplain(args) {
|
|
|
27697
27945
|
return textResult(`未找到对象: ${target}`, true);
|
|
27698
27946
|
}
|
|
27699
27947
|
async function runExportTools(args) {
|
|
27700
|
-
const root =
|
|
27948
|
+
const root = resolve18(args.root || process.cwd());
|
|
27701
27949
|
const manifest = await readManifest(root, { ...args, out_dir: undefined });
|
|
27702
27950
|
const definitions = buildToolDefinitions(manifest);
|
|
27703
|
-
const outDir =
|
|
27951
|
+
const outDir = resolve18(args.out_dir || join13(root, "generated"));
|
|
27704
27952
|
if (args.format === "json") {
|
|
27705
27953
|
return textResult(JSON.stringify(definitions, null, 2));
|
|
27706
27954
|
}
|
|
@@ -27721,7 +27969,7 @@ async function runExportTools(args) {
|
|
|
27721
27969
|
return textResult(summary);
|
|
27722
27970
|
}
|
|
27723
27971
|
async function runDelivery(args) {
|
|
27724
|
-
const root =
|
|
27972
|
+
const root = resolve18(args.root || process.cwd());
|
|
27725
27973
|
if (args.action === "build") {
|
|
27726
27974
|
const database = checkAppDatabaseSources(root);
|
|
27727
27975
|
if (database && !database.ok) {
|
|
@@ -27731,7 +27979,7 @@ async function runDelivery(args) {
|
|
|
27731
27979
|
const loaded = await loadSupacloudConfig(root);
|
|
27732
27980
|
const options = compileOptionsFromConfig({
|
|
27733
27981
|
...loaded,
|
|
27734
|
-
...args.out_dir === undefined ? {} : { outDir:
|
|
27982
|
+
...args.out_dir === undefined ? {} : { outDir: resolve18(root, args.out_dir) },
|
|
27735
27983
|
...args.include === undefined ? {} : { include: parseInclude(args.include) },
|
|
27736
27984
|
...args.strict === undefined ? {} : { strict: args.strict }
|
|
27737
27985
|
}, root);
|
|
@@ -27808,7 +28056,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27808
28056
|
if (request.activation_id !== undefined || request.expected_activation_id !== undefined) {
|
|
27809
28057
|
throw new Error("app release generates one activation identity; use app deploy for an exact retry");
|
|
27810
28058
|
}
|
|
27811
|
-
const root =
|
|
28059
|
+
const root = resolve18(request.root || process.cwd());
|
|
27812
28060
|
const completed = {};
|
|
27813
28061
|
let manifestPath;
|
|
27814
28062
|
let buildReceipt;
|
|
@@ -27816,7 +28064,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27816
28064
|
if (request.manifest_path === undefined) {
|
|
27817
28065
|
throw new Error("app release --skip_build requires --manifest_path");
|
|
27818
28066
|
}
|
|
27819
|
-
manifestPath =
|
|
28067
|
+
manifestPath = resolve18(root, request.manifest_path);
|
|
27820
28068
|
completed.build = { skipped: true, manifest_path: manifestPath };
|
|
27821
28069
|
} else {
|
|
27822
28070
|
const built = await runDelivery({ ...request, action: "build", root, format: "json" });
|
|
@@ -27853,7 +28101,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27853
28101
|
let configurationWrite;
|
|
27854
28102
|
const activationId = crypto.randomUUID();
|
|
27855
28103
|
if (request.configuration_path !== undefined) {
|
|
27856
|
-
const configurationPath =
|
|
28104
|
+
const configurationPath = resolve18(root, request.configuration_path);
|
|
27857
28105
|
try {
|
|
27858
28106
|
configurationWrite = parseApplicationConfigurationWrite(await Bun.file(configurationPath).json());
|
|
27859
28107
|
configurationId = configurationWrite.configuration_id;
|
|
@@ -28180,15 +28428,21 @@ Execution: skipped`);
|
|
|
28180
28428
|
return format === "json" || json ? result : formatPromotionExecution(result);
|
|
28181
28429
|
}
|
|
28182
28430
|
async function runAppTool(request, options = {}) {
|
|
28183
|
-
if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined) {
|
|
28431
|
+
if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined || request.request_id !== undefined && request["request-id"] !== undefined) {
|
|
28184
28432
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
28185
28433
|
}
|
|
28186
28434
|
request = {
|
|
28187
28435
|
...request,
|
|
28188
28436
|
...request["dry-run"] === undefined ? {} : { dry_run: request["dry-run"] },
|
|
28189
28437
|
...request["register-in"] === undefined ? {} : { register_in: request["register-in"] },
|
|
28190
|
-
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] }
|
|
28438
|
+
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] },
|
|
28439
|
+
...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
|
|
28191
28440
|
};
|
|
28441
|
+
if (request.events !== undefined || request.request_id !== undefined) {
|
|
28442
|
+
if (request.action !== "context" || !request.events?.trim() || !request.target?.trim() || !request.request_id || !/^[A-Za-z0-9._:-]{1,256}$/.test(request.request_id) || request.format !== "json" || request.write !== undefined) {
|
|
28443
|
+
throw new Error("Execution context requires app context, --target, --events, --request-id and --format json; --write is not supported");
|
|
28444
|
+
}
|
|
28445
|
+
}
|
|
28192
28446
|
if ((request.dry_run !== undefined || request.register_in !== undefined) && request.action !== "generate") {
|
|
28193
28447
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--dry-run and --register-in apply only to app generate");
|
|
28194
28448
|
}
|
|
@@ -28375,6 +28629,9 @@ function registerAppTools(server, options = {}) {
|
|
|
28375
28629
|
database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
|
|
28376
28630
|
"database-url": optional(String2(), "[dev] CLI alias of database_url"),
|
|
28377
28631
|
target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
|
|
28632
|
+
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
28633
|
+
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
28634
|
+
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
28378
28635
|
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
28379
28636
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
|
|
28380
28637
|
skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
|
|
@@ -28400,6 +28657,9 @@ function registerAppAliases(server) {
|
|
|
28400
28657
|
strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
|
|
28401
28658
|
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
|
|
28402
28659
|
target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
|
|
28660
|
+
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
28661
|
+
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
28662
|
+
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
28403
28663
|
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
28404
28664
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
|
|
28405
28665
|
};
|
|
@@ -28410,7 +28670,7 @@ function registerAppAliases(server) {
|
|
|
28410
28670
|
|
|
28411
28671
|
// src/shared/tools/db-governance-tools.ts
|
|
28412
28672
|
import { existsSync as existsSync13 } from "node:fs";
|
|
28413
|
-
import { join as join16, resolve as
|
|
28673
|
+
import { join as join16, resolve as resolve21 } from "node:path";
|
|
28414
28674
|
import {
|
|
28415
28675
|
buildDatabaseManifest,
|
|
28416
28676
|
explainObject,
|
|
@@ -28420,10 +28680,10 @@ import {
|
|
|
28420
28680
|
} from "@supacloud/db";
|
|
28421
28681
|
|
|
28422
28682
|
// src/shared/tools/db-workflow.ts
|
|
28423
|
-
import { createHash as
|
|
28683
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
28424
28684
|
import { lstat as lstat4, mkdir as mkdir6, mkdtemp, readFile as readFile5, readdir as readdir6, rename as rename3, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
28425
28685
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
28426
|
-
import { dirname as
|
|
28686
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join15, relative as relative8, resolve as resolve20 } from "node:path";
|
|
28427
28687
|
import {
|
|
28428
28688
|
assertApplicationRoleRestricted,
|
|
28429
28689
|
readDatabaseRole,
|
|
@@ -28435,7 +28695,7 @@ import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
|
|
|
28435
28695
|
|
|
28436
28696
|
// src/shared/tools/supabase-cli-tools.ts
|
|
28437
28697
|
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
28438
|
-
import { dirname as
|
|
28698
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join14, resolve as resolve19 } from "node:path";
|
|
28439
28699
|
var SENSITIVE_ENV_KEY2 = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIALS?|AUTHORIZATION|AUTH|SESSION|COOKIE|BEARER|DB_URI|DB_URL|DSN|DATABASE_URL|DATABASE_URI|CONNECTION_STRING|CONNECTION_URI)(?:_|$)/i;
|
|
28440
28700
|
var VALID_VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
28441
28701
|
var VALID_MIGRATION_NAME2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
|
|
@@ -28480,12 +28740,12 @@ function schemaArguments2(schema) {
|
|
|
28480
28740
|
function workdirArguments2(workdir) {
|
|
28481
28741
|
if (!workdir)
|
|
28482
28742
|
throw new Error("A workdir is required");
|
|
28483
|
-
return ["--workdir",
|
|
28743
|
+
return ["--workdir", resolve19(workdir)];
|
|
28484
28744
|
}
|
|
28485
28745
|
function resolveOutputPath2(workdir, file, label) {
|
|
28486
28746
|
if (!file || /[\r\n\0]/.test(file))
|
|
28487
28747
|
throw new Error(`${label} file is required`);
|
|
28488
|
-
return isAbsolute6(file) ?
|
|
28748
|
+
return isAbsolute6(file) ? resolve19(file) : resolve19(workdir, file);
|
|
28489
28749
|
}
|
|
28490
28750
|
function databaseTargetArguments2(databaseUrl) {
|
|
28491
28751
|
return databaseUrl ? ["--db-url", requirePostgresUrl2(databaseUrl)] : ["--local"];
|
|
@@ -28660,7 +28920,7 @@ function actionArguments2(request, workdir) {
|
|
|
28660
28920
|
function buildOfficialSupabaseArgs2(request) {
|
|
28661
28921
|
if (request.action === "version")
|
|
28662
28922
|
return ["--version"];
|
|
28663
|
-
const workdir = request.workdir ?
|
|
28923
|
+
const workdir = request.workdir ? resolve19(request.workdir) : undefined;
|
|
28664
28924
|
if (!workdir)
|
|
28665
28925
|
throw new Error("A workdir is required");
|
|
28666
28926
|
return [...actionArguments2(request, workdir), ...workdirArguments2(workdir)];
|
|
@@ -28702,14 +28962,14 @@ function resolveOfficialSupabaseCommand2(workdir, environment = process.env) {
|
|
|
28702
28962
|
}
|
|
28703
28963
|
return ["npx", "--yes", `supabase@${version}`];
|
|
28704
28964
|
}
|
|
28705
|
-
const localPackageEntry = join14(
|
|
28965
|
+
const localPackageEntry = join14(resolve19(workdir), "node_modules", "supabase", "dist", "supabase.js");
|
|
28706
28966
|
if (existsSync12(localPackageEntry))
|
|
28707
28967
|
return [process.execPath, localPackageEntry];
|
|
28708
28968
|
return ["supabase"];
|
|
28709
28969
|
}
|
|
28710
28970
|
|
|
28711
28971
|
// src/shared/tools/db-workflow.ts
|
|
28712
|
-
var hash4 = (content) =>
|
|
28972
|
+
var hash4 = (content) => createHash8("sha256").update(content).digest("hex");
|
|
28713
28973
|
var result = (value, isError = false) => ({
|
|
28714
28974
|
isError,
|
|
28715
28975
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -28873,8 +29133,8 @@ async function reverse(args, root, options) {
|
|
|
28873
29133
|
if (!selectedSchemas.length || selectedSchemas.some((schema) => !/^[a-z_][a-z0-9_$]*$/i.test(schema))) {
|
|
28874
29134
|
throw new Error("Select valid PostgreSQL schemas");
|
|
28875
29135
|
}
|
|
28876
|
-
await mkdir6(
|
|
28877
|
-
const stage = await mkdtemp(join15(
|
|
29136
|
+
await mkdir6(dirname11(out), { recursive: true });
|
|
29137
|
+
const stage = await mkdtemp(join15(dirname11(out), ".reverse-"));
|
|
28878
29138
|
try {
|
|
28879
29139
|
const snapshot = await withDatabase(args, options, async (executor, url) => {
|
|
28880
29140
|
const snapshot = await reverseDatabase(executor, selectedSchemas);
|
|
@@ -28918,8 +29178,8 @@ async function diff2(args, root, options) {
|
|
|
28918
29178
|
const input = await migrations(root, args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations");
|
|
28919
29179
|
if (overlaps(input.dir, out))
|
|
28920
29180
|
throw new Error("Candidate output overlaps reviewed migration history");
|
|
28921
|
-
await mkdir6(
|
|
28922
|
-
const stage = await mkdtemp(join15(
|
|
29181
|
+
await mkdir6(dirname11(out), { recursive: true });
|
|
29182
|
+
const stage = await mkdtemp(join15(dirname11(out), ".diff-"));
|
|
28923
29183
|
try {
|
|
28924
29184
|
await initializeCandidate(stage, args.db_major_version);
|
|
28925
29185
|
if (!await copySqlSources(root, sourcePath, join15(stage, "supabase/schemas")))
|
|
@@ -29011,7 +29271,7 @@ async function migrationAction(args, root, options) {
|
|
|
29011
29271
|
}
|
|
29012
29272
|
}
|
|
29013
29273
|
async function runDbWorkflow(args, projectRoot, options = {}) {
|
|
29014
|
-
const root =
|
|
29274
|
+
const root = resolve20(projectRoot);
|
|
29015
29275
|
if ((await lstat4(root)).isSymbolicLink())
|
|
29016
29276
|
throw new Error("Workflow root must not be a symlink");
|
|
29017
29277
|
switch (args.action) {
|
|
@@ -29072,7 +29332,7 @@ function normalizeModule(candidate, sourcePath) {
|
|
|
29072
29332
|
};
|
|
29073
29333
|
}
|
|
29074
29334
|
async function loadDatabaseModules(root, moduleFile) {
|
|
29075
|
-
const filePath = moduleFile ?
|
|
29335
|
+
const filePath = moduleFile ? resolve21(root, moduleFile) : join16(root, "db", "modules.ts");
|
|
29076
29336
|
if (!existsSync13(filePath)) {
|
|
29077
29337
|
throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
|
|
29078
29338
|
}
|
|
@@ -29208,7 +29468,7 @@ async function runLint(args, root) {
|
|
|
29208
29468
|
const issues = [];
|
|
29209
29469
|
for (const module of selected) {
|
|
29210
29470
|
const readSqlFile = async (path) => {
|
|
29211
|
-
const absolute =
|
|
29471
|
+
const absolute = resolve21(root, path);
|
|
29212
29472
|
try {
|
|
29213
29473
|
return await Bun.file(absolute).text();
|
|
29214
29474
|
} catch (error) {
|
|
@@ -29248,14 +29508,14 @@ var defaultLiteSpawn = async (command, cwd) => {
|
|
|
29248
29508
|
return { exitCode, stdout, stderr };
|
|
29249
29509
|
};
|
|
29250
29510
|
async function runModuleCheckLite(args, root, spawn, liteBinary) {
|
|
29251
|
-
const projectDir =
|
|
29511
|
+
const projectDir = resolve21(args.project_dir?.trim() || root);
|
|
29252
29512
|
const binary = liteBinary === undefined ? Bun.which("supacloud-lite") : liteBinary;
|
|
29253
29513
|
if (!binary) {
|
|
29254
29514
|
throw new Error("db module_check --lite 需要本机可用的 supacloud-lite(npm i -g @supacloud/lite,或加入 PATH)");
|
|
29255
29515
|
}
|
|
29256
29516
|
const command = [binary, "db", "check", "--project-dir", projectDir];
|
|
29257
29517
|
if (args.module_file?.trim())
|
|
29258
|
-
command.push("--module-file",
|
|
29518
|
+
command.push("--module-file", resolve21(root, args.module_file.trim()));
|
|
29259
29519
|
const result = await spawn(command, projectDir);
|
|
29260
29520
|
const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
|
|
29261
29521
|
`);
|
|
@@ -29315,7 +29575,7 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
29315
29575
|
migration_role: optional(String2(), "[role_sql] Privileged migration role"),
|
|
29316
29576
|
database: optional(String2(), "[role_sql] PostgreSQL database name")
|
|
29317
29577
|
}, async (request) => {
|
|
29318
|
-
const root =
|
|
29578
|
+
const root = resolve21(request.root || fallbackRoot);
|
|
29319
29579
|
switch (request.action) {
|
|
29320
29580
|
case "context":
|
|
29321
29581
|
return runContext2(request, root);
|
|
@@ -29345,8 +29605,8 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
29345
29605
|
|
|
29346
29606
|
// src/shared/tools/scheduled-function-tools.ts
|
|
29347
29607
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
29348
|
-
import { readFileSync as
|
|
29349
|
-
import { resolve as
|
|
29608
|
+
import { readFileSync as readFileSync10, statSync as statSync9 } from "node:fs";
|
|
29609
|
+
import { resolve as resolve22 } from "node:path";
|
|
29350
29610
|
import { isDeepStrictEqual } from "node:util";
|
|
29351
29611
|
var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
|
|
29352
29612
|
var ENVIRONMENT_NAME_PATTERN3 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
|
|
@@ -29430,14 +29690,14 @@ function validScheduledFunctionCron(expression) {
|
|
|
29430
29690
|
function readScheduleBodyFile(bodyPathInput) {
|
|
29431
29691
|
if (!bodyPathInput.trim())
|
|
29432
29692
|
throw new Error("'body_file' must be a path");
|
|
29433
|
-
const bodyPath =
|
|
29693
|
+
const bodyPath = resolve22(bodyPathInput);
|
|
29434
29694
|
const bodyStat = statSync9(bodyPath);
|
|
29435
29695
|
if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
|
|
29436
29696
|
throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
|
|
29437
29697
|
}
|
|
29438
29698
|
let payload;
|
|
29439
29699
|
try {
|
|
29440
|
-
payload = JSON.parse(
|
|
29700
|
+
payload = JSON.parse(readFileSync10(bodyPath, "utf8"));
|
|
29441
29701
|
} catch (error) {
|
|
29442
29702
|
if (!(error instanceof SyntaxError))
|
|
29443
29703
|
throw error;
|
|
@@ -29813,9 +30073,9 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
29813
30073
|
};
|
|
29814
30074
|
|
|
29815
30075
|
// src/shared/tools/release-tools.ts
|
|
29816
|
-
import { createHash as
|
|
29817
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as
|
|
29818
|
-
import { dirname as
|
|
30076
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
30077
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
30078
|
+
import { dirname as dirname12, join as join17, relative as relative9, resolve as resolve23 } from "node:path";
|
|
29819
30079
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
29820
30080
|
var SAFE_ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
29821
30081
|
var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
|
|
@@ -30056,7 +30316,7 @@ function genericScopedReleaseScopeJson(scope) {
|
|
|
30056
30316
|
return JSON.stringify(canonicalValue(scope));
|
|
30057
30317
|
}
|
|
30058
30318
|
function genericScopedReleaseScopeSha256(scope) {
|
|
30059
|
-
return
|
|
30319
|
+
return createHash9("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
|
|
30060
30320
|
}
|
|
30061
30321
|
function scopeEnvironmentId(scope, action) {
|
|
30062
30322
|
const environmentId = isRecord6(scope) ? scope.targetEnvironmentId : undefined;
|
|
@@ -30137,13 +30397,13 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30137
30397
|
const results = [];
|
|
30138
30398
|
const resolvePathWithFallbacks = (filePath) => {
|
|
30139
30399
|
const trimmed = filePath.trim();
|
|
30140
|
-
const resolved =
|
|
30400
|
+
const resolved = resolve23(cwd, trimmed);
|
|
30141
30401
|
if (existsSync14(resolved))
|
|
30142
30402
|
return resolved;
|
|
30143
30403
|
const candidates = [
|
|
30144
|
-
|
|
30145
|
-
|
|
30146
|
-
|
|
30404
|
+
resolve23(cwd, "supacloud/fa/release-scopes", trimmed),
|
|
30405
|
+
resolve23(cwd, "release-scopes", trimmed),
|
|
30406
|
+
resolve23(cwd, "scopes", trimmed)
|
|
30147
30407
|
];
|
|
30148
30408
|
const found = candidates.find(existsSync14);
|
|
30149
30409
|
if (found)
|
|
@@ -30166,10 +30426,10 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30166
30426
|
if (!taskFilter && (!dir || typeof dir !== "string" || !dir.trim())) {
|
|
30167
30427
|
throw new Error("At least one of --file, --files, or --task is required for scope_rebind");
|
|
30168
30428
|
}
|
|
30169
|
-
const candidates = typeof dir === "string" && dir.trim() ? [
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30429
|
+
const candidates = typeof dir === "string" && dir.trim() ? [resolve23(cwd, dir.trim())] : [
|
|
30430
|
+
resolve23(cwd, "supacloud/fa/release-scopes"),
|
|
30431
|
+
resolve23(cwd, "release-scopes"),
|
|
30432
|
+
resolve23(cwd, "scopes")
|
|
30173
30433
|
];
|
|
30174
30434
|
for (const directory of candidates) {
|
|
30175
30435
|
if (existsSync14(directory)) {
|
|
@@ -30184,7 +30444,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30184
30444
|
results.push(fullPath);
|
|
30185
30445
|
} else {
|
|
30186
30446
|
try {
|
|
30187
|
-
const parsed = JSON.parse(
|
|
30447
|
+
const parsed = JSON.parse(readFileSync11(fullPath, "utf8"));
|
|
30188
30448
|
if (typeof parsed?.taskId === "string" && parsed.taskId.toLowerCase() === taskFilter) {
|
|
30189
30449
|
results.push(fullPath);
|
|
30190
30450
|
}
|
|
@@ -30203,7 +30463,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30203
30463
|
return [...new Set(results)];
|
|
30204
30464
|
}
|
|
30205
30465
|
function handleScopeRebind(args) {
|
|
30206
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30466
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30207
30467
|
const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
|
|
30208
30468
|
const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_rebind");
|
|
30209
30469
|
const scopeFiles = collectScopeFiles(cwd, args.file, args.files, args.dir, args.task);
|
|
@@ -30211,7 +30471,7 @@ function handleScopeRebind(args) {
|
|
|
30211
30471
|
const parsedScopes = scopeFiles.map((filePath) => {
|
|
30212
30472
|
let content;
|
|
30213
30473
|
try {
|
|
30214
|
-
content =
|
|
30474
|
+
content = readFileSync11(filePath, "utf8");
|
|
30215
30475
|
} catch (cause) {
|
|
30216
30476
|
throw new Error(`Failed to read scope file '${filePath}': ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
30217
30477
|
}
|
|
@@ -30261,18 +30521,18 @@ function handleScopeRebind(args) {
|
|
|
30261
30521
|
});
|
|
30262
30522
|
}
|
|
30263
30523
|
function handleScopeInspect(args) {
|
|
30264
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30524
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30265
30525
|
const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_inspect");
|
|
30266
30526
|
if (typeof args.file !== "string" || !args.file.trim()) {
|
|
30267
30527
|
throw new Error("'file' is required for scope_inspect");
|
|
30268
30528
|
}
|
|
30269
30529
|
const trimmed = args.file.trim();
|
|
30270
|
-
let filePath =
|
|
30530
|
+
let filePath = resolve23(cwd, trimmed);
|
|
30271
30531
|
if (!existsSync14(filePath)) {
|
|
30272
30532
|
const candidates = [
|
|
30273
|
-
|
|
30274
|
-
|
|
30275
|
-
|
|
30533
|
+
resolve23(cwd, "supacloud/fa/release-scopes", trimmed),
|
|
30534
|
+
resolve23(cwd, "release-scopes", trimmed),
|
|
30535
|
+
resolve23(cwd, "scopes", trimmed)
|
|
30276
30536
|
];
|
|
30277
30537
|
const found = candidates.find(existsSync14);
|
|
30278
30538
|
if (found)
|
|
@@ -30282,7 +30542,7 @@ function handleScopeInspect(args) {
|
|
|
30282
30542
|
}
|
|
30283
30543
|
let content;
|
|
30284
30544
|
try {
|
|
30285
|
-
content =
|
|
30545
|
+
content = readFileSync11(filePath, "utf8");
|
|
30286
30546
|
} catch (cause) {
|
|
30287
30547
|
throw new Error(`Failed to read scope file '${args.file}': ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
30288
30548
|
}
|
|
@@ -30337,7 +30597,7 @@ function parseCommaList(value) {
|
|
|
30337
30597
|
return [];
|
|
30338
30598
|
}
|
|
30339
30599
|
function handleScopeCreate(args, fallbackRef) {
|
|
30340
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30600
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30341
30601
|
if (typeof args.file !== "string" || !args.file.trim()) {
|
|
30342
30602
|
throw new Error("'file' is required for scope_create");
|
|
30343
30603
|
}
|
|
@@ -30353,8 +30613,8 @@ function handleScopeCreate(args, fallbackRef) {
|
|
|
30353
30613
|
throw new Error("'ref' is invalid for release controls");
|
|
30354
30614
|
}
|
|
30355
30615
|
const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
|
|
30356
|
-
const filePath =
|
|
30357
|
-
mkdirSync5(
|
|
30616
|
+
const filePath = resolve23(cwd, args.file.trim());
|
|
30617
|
+
mkdirSync5(dirname12(filePath), { recursive: true });
|
|
30358
30618
|
const scope = {
|
|
30359
30619
|
baseCommit: targetBaseCommit,
|
|
30360
30620
|
excludedFunctions: parseCommaList(args.excluded_functions),
|
|
@@ -30600,9 +30860,9 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
30600
30860
|
// src/shared/tools/deploy-tools.ts
|
|
30601
30861
|
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile6, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
|
|
30602
30862
|
import { spawn as spawn2 } from "node:child_process";
|
|
30603
|
-
import { createHash as
|
|
30863
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
30604
30864
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
30605
|
-
import { basename as basename8, dirname as
|
|
30865
|
+
import { basename as basename8, dirname as dirname13, join as join18, relative as relative10, resolve as resolve24, sep as sep5 } from "node:path";
|
|
30606
30866
|
|
|
30607
30867
|
// src/shared/tools/project-deployment.ts
|
|
30608
30868
|
async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
|
|
@@ -30728,15 +30988,15 @@ async function readJson(path) {
|
|
|
30728
30988
|
}
|
|
30729
30989
|
}
|
|
30730
30990
|
async function findDeployConfigRoot(startDirectory, environmentName) {
|
|
30731
|
-
let directory =
|
|
30991
|
+
let directory = resolve24(startDirectory);
|
|
30732
30992
|
while (true) {
|
|
30733
30993
|
const environmentFile = environmentName ? `.env.supacloud.${environmentName}` : null;
|
|
30734
30994
|
if (await pathExists(join18(directory, "supacloud.json")) || await pathExists(join18(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join18(directory, environmentFile))) {
|
|
30735
30995
|
return directory;
|
|
30736
30996
|
}
|
|
30737
|
-
const parent =
|
|
30997
|
+
const parent = dirname13(directory);
|
|
30738
30998
|
if (parent === directory)
|
|
30739
|
-
return
|
|
30999
|
+
return resolve24(startDirectory);
|
|
30740
31000
|
directory = parent;
|
|
30741
31001
|
}
|
|
30742
31002
|
}
|
|
@@ -30889,7 +31149,7 @@ async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
|
30889
31149
|
return "yarn build";
|
|
30890
31150
|
if (directory === configRoot)
|
|
30891
31151
|
break;
|
|
30892
|
-
const parent =
|
|
31152
|
+
const parent = dirname13(directory);
|
|
30893
31153
|
if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
|
|
30894
31154
|
break;
|
|
30895
31155
|
directory = parent;
|
|
@@ -30957,9 +31217,9 @@ function selectFrontendTarget(configRoot, config) {
|
|
|
30957
31217
|
throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
|
|
30958
31218
|
}
|
|
30959
31219
|
function targetRoot(configRoot, target, name) {
|
|
30960
|
-
const root =
|
|
30961
|
-
const prefix = `${
|
|
30962
|
-
if (root !==
|
|
31220
|
+
const root = resolve24(configRoot, target.root || (name === "frontend" ? "." : name));
|
|
31221
|
+
const prefix = `${resolve24(configRoot)}${sep5}`;
|
|
31222
|
+
if (root !== resolve24(configRoot) && !root.startsWith(prefix)) {
|
|
30963
31223
|
throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
|
|
30964
31224
|
}
|
|
30965
31225
|
return root;
|
|
@@ -30977,8 +31237,8 @@ function defaultOutputDirectory(framework) {
|
|
|
30977
31237
|
}
|
|
30978
31238
|
}
|
|
30979
31239
|
function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
|
|
30980
|
-
const resolved =
|
|
30981
|
-
const projectRoot =
|
|
31240
|
+
const resolved = resolve24(projectDirectory, candidate);
|
|
31241
|
+
const projectRoot = resolve24(projectDirectory);
|
|
30982
31242
|
const projectPrefix = `${projectRoot}${sep5}`;
|
|
30983
31243
|
if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
|
|
30984
31244
|
throw new Error("Frontend output directory must stay inside the project directory");
|
|
@@ -31057,9 +31317,9 @@ async function collectArchiveFiles(root) {
|
|
|
31057
31317
|
throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
|
|
31058
31318
|
const archivePath = relative10(root, path).split(sep5).join("/");
|
|
31059
31319
|
files.set(archivePath, data);
|
|
31060
|
-
digests.push({ path: archivePath, size: data.byteLength, sha256:
|
|
31320
|
+
digests.push({ path: archivePath, size: data.byteLength, sha256: createHash10("sha256").update(data).digest("hex") });
|
|
31061
31321
|
}
|
|
31062
|
-
const tree =
|
|
31322
|
+
const tree = createHash10("sha256");
|
|
31063
31323
|
for (const file of digests.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
31064
31324
|
const pathBytes = Buffer.from(file.path, "utf8");
|
|
31065
31325
|
const frame = Buffer.alloc(12);
|
|
@@ -31075,7 +31335,7 @@ async function createCollectedFrontendArchive(outputDirectory, collected) {
|
|
|
31075
31335
|
try {
|
|
31076
31336
|
const archiveBytes = await createFrontendTarZstd(collected.files);
|
|
31077
31337
|
await writeFile6(archivePath, archiveBytes);
|
|
31078
|
-
const sha256 =
|
|
31338
|
+
const sha256 = createHash10("sha256").update(archiveBytes).digest("hex");
|
|
31079
31339
|
return {
|
|
31080
31340
|
archivePath,
|
|
31081
31341
|
sha256,
|
|
@@ -31119,7 +31379,7 @@ async function publishConfiguredFrontend(http, projectRef, configRoot, skipBuild
|
|
|
31119
31379
|
}
|
|
31120
31380
|
function registerDeployTools(server, http, options = {}) {
|
|
31121
31381
|
registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
|
|
31122
|
-
const invocationDirectory =
|
|
31382
|
+
const invocationDirectory = resolve24(String(args.cwd || options.cwd || process.cwd()));
|
|
31123
31383
|
const projectRef = String(args.ref || options.projectRef || "").trim();
|
|
31124
31384
|
if (!projectRef)
|
|
31125
31385
|
throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
|
|
@@ -31197,8 +31457,8 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
31197
31457
|
import { existsSync as existsSync15 } from "node:fs";
|
|
31198
31458
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
31199
31459
|
import { readdir as readdir7 } from "node:fs/promises";
|
|
31200
|
-
import { createHash as
|
|
31201
|
-
import { join as join19, resolve as
|
|
31460
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
31461
|
+
import { join as join19, resolve as resolve25 } from "node:path";
|
|
31202
31462
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
31203
31463
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
31204
31464
|
var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
|
|
@@ -31244,12 +31504,12 @@ function toolFailed(value) {
|
|
|
31244
31504
|
return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
|
|
31245
31505
|
}
|
|
31246
31506
|
async function migrateDatabase(args, options) {
|
|
31247
|
-
const root =
|
|
31507
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31248
31508
|
const projectConfig = await readProjectConfig(root);
|
|
31249
31509
|
const config = projectConfig.dev || {};
|
|
31250
31510
|
const database = config.database || {};
|
|
31251
31511
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
31252
|
-
const drizzleConfig =
|
|
31512
|
+
const drizzleConfig = resolve25(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
|
|
31253
31513
|
const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
|
|
31254
31514
|
if (!existsSync15(drizzleConfig))
|
|
31255
31515
|
throw new Error(`Drizzle config not found: ${drizzleConfig}`);
|
|
@@ -31296,7 +31556,7 @@ async function sourceFingerprint(root) {
|
|
|
31296
31556
|
}
|
|
31297
31557
|
};
|
|
31298
31558
|
await visit(root);
|
|
31299
|
-
const hash =
|
|
31559
|
+
const hash = createHash11("sha256");
|
|
31300
31560
|
for (const file of files) {
|
|
31301
31561
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
31302
31562
|
hash.update(await readFile7(file));
|
|
@@ -31321,14 +31581,14 @@ function targetDirectory(root, target, functionSlug, config = {}) {
|
|
|
31321
31581
|
const targets = config["targets"];
|
|
31322
31582
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
31323
31583
|
const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "edge_function" && (!("root" in entry) || typeof entry.root === "string") && (!("slug" in entry) || typeof entry.slug === "string") && (!functionSlug || ("slug" in entry) && entry.slug === functionSlug));
|
|
31324
|
-
const base = match?.root ?
|
|
31584
|
+
const base = match?.root ? resolve25(root, match.root) : join19(root, "supabase", "functions");
|
|
31325
31585
|
return match?.root ? base : functionSlug ? join19(base, safeToken(functionSlug, "function")) : base;
|
|
31326
31586
|
}
|
|
31327
31587
|
if (target === "frontend") {
|
|
31328
31588
|
const targets = config["targets"];
|
|
31329
31589
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
31330
31590
|
const match = entries.find((entry) => entry !== null && typeof entry === "object" && ("type" in entry) && entry.type === "frontend" && (!("root" in entry) || typeof entry.root === "string"));
|
|
31331
|
-
return match?.root ?
|
|
31591
|
+
return match?.root ? resolve25(root, match.root) : join19(root, "apps", "web");
|
|
31332
31592
|
}
|
|
31333
31593
|
return root;
|
|
31334
31594
|
}
|
|
@@ -31367,10 +31627,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
|
|
|
31367
31627
|
return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
|
|
31368
31628
|
}
|
|
31369
31629
|
function sshArgs(connection, remoteCommand) {
|
|
31370
|
-
return ["-p", String(connection.port), "-i",
|
|
31630
|
+
return ["-p", String(connection.port), "-i", resolve25(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
|
|
31371
31631
|
}
|
|
31372
31632
|
function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
31373
|
-
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${
|
|
31633
|
+
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve25(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
|
|
31374
31634
|
if (deleteRemote)
|
|
31375
31635
|
args.push("--delete-delay");
|
|
31376
31636
|
for (const exclude of excludes) {
|
|
@@ -31382,7 +31642,7 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
|
31382
31642
|
return args;
|
|
31383
31643
|
}
|
|
31384
31644
|
async function syncOnce(args, options) {
|
|
31385
|
-
const root =
|
|
31645
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31386
31646
|
const projectConfig = await readProjectConfig(root);
|
|
31387
31647
|
const config = projectConfig.dev || {};
|
|
31388
31648
|
const target = String(args.target || "project");
|
|
@@ -31391,8 +31651,8 @@ async function syncOnce(args, options) {
|
|
|
31391
31651
|
throw new Error(`Dev source directory not found: ${source}`);
|
|
31392
31652
|
let compiled = false;
|
|
31393
31653
|
if (config.compile === true && target !== "db") {
|
|
31394
|
-
const compileRoot =
|
|
31395
|
-
const compileOutDir =
|
|
31654
|
+
const compileRoot = resolve25(root, config.compileRoot || ".");
|
|
31655
|
+
const compileOutDir = resolve25(root, config.compileOutDir || "generated");
|
|
31396
31656
|
const compilation = await compileProject2({
|
|
31397
31657
|
rootDir: compileRoot,
|
|
31398
31658
|
outDir: compileOutDir,
|
|
@@ -31430,7 +31690,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31430
31690
|
if (["production", "prod"].includes((options.environment || "").toLowerCase()))
|
|
31431
31691
|
throw new Error("Remote dev mode is forbidden for production environments");
|
|
31432
31692
|
if (args.action === "status") {
|
|
31433
|
-
const root =
|
|
31693
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31434
31694
|
const config = await readDevConfig(root);
|
|
31435
31695
|
const connection = connectionArgs(options, config, args);
|
|
31436
31696
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
@@ -31444,7 +31704,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31444
31704
|
return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
|
|
31445
31705
|
}
|
|
31446
31706
|
if (args.action === "watch") {
|
|
31447
|
-
const root =
|
|
31707
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31448
31708
|
const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
|
|
31449
31709
|
let fingerprint = "";
|
|
31450
31710
|
let lastSync = null;
|
|
@@ -31466,12 +31726,12 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31466
31726
|
// src/shared/tools/developer-experience-tools.ts
|
|
31467
31727
|
import { existsSync as existsSync17 } from "node:fs";
|
|
31468
31728
|
import { mkdir as mkdir7, readdir as readdir8, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
31469
|
-
import { dirname as
|
|
31729
|
+
import { dirname as dirname14, join as join20, resolve as resolve27 } from "node:path";
|
|
31470
31730
|
|
|
31471
31731
|
// src/shared/context.ts
|
|
31472
|
-
import { existsSync as existsSync16, readFileSync as
|
|
31732
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
31473
31733
|
import { homedir as homedir3 } from "node:os";
|
|
31474
|
-
import { resolve as
|
|
31734
|
+
import { resolve as resolve26 } from "node:path";
|
|
31475
31735
|
var CORE_CONTEXT_KEYS2 = [
|
|
31476
31736
|
"SUPABASE_URL",
|
|
31477
31737
|
"SUPABASE_SERVICE_ROLE_KEY",
|
|
@@ -31513,7 +31773,7 @@ function readEnvFile2(path, required) {
|
|
|
31513
31773
|
return {};
|
|
31514
31774
|
}
|
|
31515
31775
|
try {
|
|
31516
|
-
return parseEnvFile2(
|
|
31776
|
+
return parseEnvFile2(readFileSync12(path, "utf8"));
|
|
31517
31777
|
} catch (error) {
|
|
31518
31778
|
const message = error instanceof Error ? error.message : String(error);
|
|
31519
31779
|
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
@@ -31587,7 +31847,7 @@ function parseInsecureTls2(values) {
|
|
|
31587
31847
|
}
|
|
31588
31848
|
function namedEnvironmentSource2(cwd, selector) {
|
|
31589
31849
|
const environment = normalizeEnvironmentName(selector);
|
|
31590
|
-
const path =
|
|
31850
|
+
const path = resolve26(cwd, `.env.supacloud.${selector}`);
|
|
31591
31851
|
const values = readEnvFile2(path, true);
|
|
31592
31852
|
if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
|
|
31593
31853
|
throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
|
|
@@ -31595,7 +31855,7 @@ function namedEnvironmentSource2(cwd, selector) {
|
|
|
31595
31855
|
return { values, kind: "named_env_file", path, environment };
|
|
31596
31856
|
}
|
|
31597
31857
|
function explicitEnvironmentSource2(cwd, envFile) {
|
|
31598
|
-
const path =
|
|
31858
|
+
const path = resolve26(cwd, envFile);
|
|
31599
31859
|
const values = readEnvFile2(path, true);
|
|
31600
31860
|
if (!values.SUPACLOUD_ENV)
|
|
31601
31861
|
throw new Error(`SUPACLOUD_ENV is required in ${path}`);
|
|
@@ -31624,7 +31884,7 @@ function contextSource2(env, cwd, selection) {
|
|
|
31624
31884
|
if (hasProcessContext2(env)) {
|
|
31625
31885
|
return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
|
|
31626
31886
|
}
|
|
31627
|
-
const path =
|
|
31887
|
+
const path = resolve26(cwd, ".env");
|
|
31628
31888
|
const values = readEnvFile2(path, false);
|
|
31629
31889
|
const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
|
|
31630
31890
|
return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
|
|
@@ -31639,7 +31899,7 @@ function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), select
|
|
|
31639
31899
|
host,
|
|
31640
31900
|
sshUser: env.SUPACLOUD_SSH_USER ?? "root",
|
|
31641
31901
|
sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
|
|
31642
|
-
sshKey: env.SUPACLOUD_SSH_KEY ??
|
|
31902
|
+
sshKey: env.SUPACLOUD_SSH_KEY ?? resolve26(homedir3(), ".ssh", "id_rsa"),
|
|
31643
31903
|
sshPass: env.SUPACLOUD_SSH_PASS ?? "",
|
|
31644
31904
|
apiUrl: core.apiUrl,
|
|
31645
31905
|
apiToken: core.apiToken,
|
|
@@ -31760,7 +32020,7 @@ async function ensureGitignore(root) {
|
|
|
31760
32020
|
return true;
|
|
31761
32021
|
}
|
|
31762
32022
|
async function initializeProject(request, options) {
|
|
31763
|
-
const root =
|
|
32023
|
+
const root = resolve27(request.root || options.cwd || process.cwd());
|
|
31764
32024
|
await mkdir7(root, { recursive: true });
|
|
31765
32025
|
const configPath = join20(root, "supacloud.json");
|
|
31766
32026
|
if (existsSync17(configPath)) {
|
|
@@ -31873,11 +32133,11 @@ function deployMissing(context, application, hasFrontend = false) {
|
|
|
31873
32133
|
}
|
|
31874
32134
|
async function inspectProfile(profile) {
|
|
31875
32135
|
try {
|
|
31876
|
-
const context = resolveSupaCloudContext2({},
|
|
32136
|
+
const context = resolveSupaCloudContext2({}, dirname14(profile.path), {
|
|
31877
32137
|
environmentName: profile.name
|
|
31878
32138
|
});
|
|
31879
32139
|
const status = statusMissing(context);
|
|
31880
|
-
const configText = await readFile8(join20(
|
|
32140
|
+
const configText = await readFile8(join20(dirname14(profile.path), "supacloud.json"), "utf8").catch((error) => {
|
|
31881
32141
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
31882
32142
|
return "{}";
|
|
31883
32143
|
throw error;
|
|
@@ -31921,7 +32181,7 @@ async function inspectProfile(profile) {
|
|
|
31921
32181
|
}
|
|
31922
32182
|
}
|
|
31923
32183
|
async function inspectEnvironment(request, options) {
|
|
31924
|
-
const root =
|
|
32184
|
+
const root = resolve27(request.root || options.cwd || process.cwd());
|
|
31925
32185
|
const profiles = await environmentProfiles(root);
|
|
31926
32186
|
if (request.action === "list") {
|
|
31927
32187
|
const active = options.activeContext?.environment || null;
|
|
@@ -32103,7 +32363,7 @@ async function runProjectDeployment(http, args) {
|
|
|
32103
32363
|
// package.json
|
|
32104
32364
|
var package_default = {
|
|
32105
32365
|
name: "@supacloud/cli",
|
|
32106
|
-
version: "0.103.
|
|
32366
|
+
version: "0.103.5",
|
|
32107
32367
|
description: "Project-scoped CLI for SupaCloud users",
|
|
32108
32368
|
type: "module",
|
|
32109
32369
|
main: "./dist/index.js",
|
|
@@ -32155,19 +32415,19 @@ var package_default = {
|
|
|
32155
32415
|
directory: "packages/cli"
|
|
32156
32416
|
},
|
|
32157
32417
|
dependencies: {
|
|
32158
|
-
"@supacloud/compiler": "^0.
|
|
32159
|
-
"@supacloud/delivery": "0.19.
|
|
32160
|
-
"@supacloud/db": "0.18.
|
|
32418
|
+
"@supacloud/compiler": "^0.47.1",
|
|
32419
|
+
"@supacloud/delivery": "0.19.2",
|
|
32420
|
+
"@supacloud/db": "0.18.2",
|
|
32161
32421
|
typebox: "1.3.37"
|
|
32162
32422
|
},
|
|
32163
32423
|
overrides: {
|
|
32164
|
-
"@supacloud/contracts": "0.11.
|
|
32165
|
-
"@supacloud/delivery": "0.19.
|
|
32424
|
+
"@supacloud/contracts": "0.11.3",
|
|
32425
|
+
"@supacloud/delivery": "0.19.2",
|
|
32166
32426
|
"@graphql-tools/utils": "^12.0.4"
|
|
32167
32427
|
},
|
|
32168
32428
|
devDependencies: {
|
|
32169
32429
|
"@supabase/supabase-js": "2.117.3",
|
|
32170
|
-
"@supacloud/elysia": "0.33.
|
|
32430
|
+
"@supacloud/elysia": "0.33.3",
|
|
32171
32431
|
"@types/bun": "^1.4.3",
|
|
32172
32432
|
effect: "4.0.5",
|
|
32173
32433
|
elysia: "2.0.0-beta.32",
|
|
@@ -32456,6 +32716,7 @@ EXAMPLES
|
|
|
32456
32716
|
${preferredCommand} app explain --target CaseService
|
|
32457
32717
|
${preferredCommand} app context --root . --format json
|
|
32458
32718
|
${preferredCommand} app context --root . --target CaseModule --format json
|
|
32719
|
+
${preferredCommand} app context --root . --target CaseModule --events execution.ndjson --request-id request-123 --format json
|
|
32459
32720
|
${preferredCommand} app doctor --root .
|
|
32460
32721
|
${preferredCommand} app fix --root . --fix fix.json --write
|
|
32461
32722
|
${preferredCommand} generate --kind module --name billing
|