@supacloud/cli 0.103.3 → 0.103.6
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 +471 -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.2"
|
|
21451
21578
|
},
|
|
21452
21579
|
app: {
|
|
21453
|
-
version: "0.
|
|
21580
|
+
version: "0.28.2",
|
|
21454
21581
|
dependencies: {
|
|
21455
21582
|
rxjs: "7.8.2"
|
|
21456
21583
|
}
|
|
21457
21584
|
},
|
|
21458
21585
|
elysia: {
|
|
21459
|
-
version: "0.33.
|
|
21586
|
+
version: "0.33.4"
|
|
21460
21587
|
},
|
|
21461
21588
|
commands: {
|
|
21462
|
-
version: "0.9.
|
|
21589
|
+
version: "0.9.6"
|
|
21463
21590
|
},
|
|
21464
21591
|
contracts: {
|
|
21465
|
-
version: "0.11.
|
|
21592
|
+
version: "0.11.4"
|
|
21466
21593
|
},
|
|
21467
21594
|
db: {
|
|
21468
|
-
version: "0.18.
|
|
21595
|
+
version: "0.18.3",
|
|
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.3",
|
|
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,9 +26695,10 @@ 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,
|
|
26701
|
+
owner.featureSpec?.file,
|
|
26454
26702
|
...owner.providers.map((item) => item.file),
|
|
26455
26703
|
...owner.controllers.map((item) => item.file),
|
|
26456
26704
|
...graph.moduleHandlerFiles?.[owner.name] ?? [],
|
|
@@ -26459,7 +26707,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26459
26707
|
...(owner.jobs ?? []).flatMap((item) => (item.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])),
|
|
26460
26708
|
...owner.controllers.flatMap((item) => item.routes.flatMap((route) => (route.aspects ?? []).flatMap((aspect) => aspect.file ? [aspect.file] : [])))
|
|
26461
26709
|
].filter((file) => typeof file === "string");
|
|
26462
|
-
const directories = [...new Set(sources.map((file) =>
|
|
26710
|
+
const directories = [...new Set(sources.map((file) => dirname7(resolve16(sourceRoot, file))))].sort();
|
|
26463
26711
|
const tests = new Set;
|
|
26464
26712
|
const manual = [];
|
|
26465
26713
|
const coverage = [];
|
|
@@ -26484,7 +26732,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26484
26732
|
const local = [];
|
|
26485
26733
|
for (const entry of await readdir4(actual, { withFileTypes: true })) {
|
|
26486
26734
|
if (entry.isFile() && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
26487
|
-
const file = relative5(project,
|
|
26735
|
+
const file = relative5(project, resolve16(actual, entry.name)).replaceAll("\\", "/");
|
|
26488
26736
|
tests.add(file);
|
|
26489
26737
|
local.push(file);
|
|
26490
26738
|
}
|
|
@@ -26504,7 +26752,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26504
26752
|
source: "declaration",
|
|
26505
26753
|
target: owner.name,
|
|
26506
26754
|
ready: manual.length === 0,
|
|
26507
|
-
files: [...new Set(sources.map((file) => relative5(requestedRoot,
|
|
26755
|
+
files: [...new Set(sources.map((file) => relative5(requestedRoot, resolve16(sourceRoot, file)).replaceAll("\\", "/")))].sort(),
|
|
26508
26756
|
tests: selected.slice(0, 32),
|
|
26509
26757
|
commands: selected.length <= 32 ? [
|
|
26510
26758
|
...selected.map((file) => ["bun", "test", `./${file}`]),
|
|
@@ -26525,7 +26773,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
26525
26773
|
// src/shared/tools/app-scaffold-writes.ts
|
|
26526
26774
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
26527
26775
|
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
|
|
26776
|
+
import { dirname as dirname8, isAbsolute as isAbsolute5, join as join12, relative as relative6, resolve as resolve17, sep as sep4, win32 } from "node:path";
|
|
26529
26777
|
|
|
26530
26778
|
class ScaffoldError extends Error {
|
|
26531
26779
|
code;
|
|
@@ -26546,7 +26794,7 @@ function scaffoldPath(root, path) {
|
|
|
26546
26794
|
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
26795
|
throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is reserved or not portable");
|
|
26548
26796
|
}
|
|
26549
|
-
const target =
|
|
26797
|
+
const target = resolve17(root, path);
|
|
26550
26798
|
const local = relative6(root, target);
|
|
26551
26799
|
if (!local || local === ".." || local.startsWith(`..${sep4}`) || isAbsolute5(local)) {
|
|
26552
26800
|
throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is outside the project root");
|
|
@@ -26585,7 +26833,7 @@ function sameFile(a, b) {
|
|
|
26585
26833
|
return a === null ? b === null : b !== null && a.content === b.content && a.dev === b.dev && a.ino === b.ino && a.mode === b.mode;
|
|
26586
26834
|
}
|
|
26587
26835
|
async function planScaffoldWrites(root, writes) {
|
|
26588
|
-
const canonical = await realpath4(
|
|
26836
|
+
const canonical = await realpath4(resolve17(root));
|
|
26589
26837
|
const targets = new Set;
|
|
26590
26838
|
const planned = [];
|
|
26591
26839
|
for (const write of writes) {
|
|
@@ -26628,7 +26876,7 @@ async function applyScaffoldWrites(plan) {
|
|
|
26628
26876
|
for (const write of plan.writes) {
|
|
26629
26877
|
const target = scaffoldPath(plan.root, write.path);
|
|
26630
26878
|
let current = plan.root;
|
|
26631
|
-
for (const part of relative6(plan.root,
|
|
26879
|
+
for (const part of relative6(plan.root, dirname8(target)).split(sep4).filter(Boolean)) {
|
|
26632
26880
|
current = join12(current, part);
|
|
26633
26881
|
try {
|
|
26634
26882
|
await mkdir4(current);
|
|
@@ -26894,7 +27142,7 @@ async function generateScaffold(args) {
|
|
|
26894
27142
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `--${flag.replaceAll("_", "-")} must be boolean`);
|
|
26895
27143
|
}
|
|
26896
27144
|
}
|
|
26897
|
-
const root =
|
|
27145
|
+
const root = resolve18(args.root || process.cwd());
|
|
26898
27146
|
const dir = args.dir ?? "src/features";
|
|
26899
27147
|
if (dir !== ".")
|
|
26900
27148
|
scaffoldPath(root, dir);
|
|
@@ -26948,7 +27196,7 @@ async function generateScaffold(args) {
|
|
|
26948
27196
|
if (new RegExp(`\\b${moduleSymbol}\\b`).test(original)) {
|
|
26949
27197
|
throw new ScaffoldError("SCAFFOLD_REGISTRATION_CONFLICT", `Registration target already references ${moduleSymbol}; review the import manually`);
|
|
26950
27198
|
}
|
|
26951
|
-
let importPath = relative7(
|
|
27199
|
+
let importPath = relative7(dirname9(target), resolve18(root, modulePath)).replaceAll("\\", "/").replace(/\.ts$/, "");
|
|
26952
27200
|
if (!importPath.startsWith("."))
|
|
26953
27201
|
importPath = `./${importPath}`;
|
|
26954
27202
|
const edit = await applyDiagnosticFix({
|
|
@@ -27021,13 +27269,13 @@ function sourceRoot(args, root, configured) {
|
|
|
27021
27269
|
return args.root && !hasConfig ? "." : configured;
|
|
27022
27270
|
}
|
|
27023
27271
|
async function projectCompileConfig(args) {
|
|
27024
|
-
const root =
|
|
27272
|
+
const root = resolve18(args.root || process.cwd());
|
|
27025
27273
|
const configFile = ["ts", "mts", "js", "mjs"].map((extension) => join13(root, `supacloud.config.${extension}`)).find((candidate) => existsSync11(candidate)) ?? null;
|
|
27026
27274
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27027
27275
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27028
27276
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
27029
27277
|
const include = parseInclude(args.include) ?? loadedConfig.include;
|
|
27030
|
-
const outDir = args.out_dir ?
|
|
27278
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir;
|
|
27031
27279
|
const compileOptions = compileOptionsFromConfig({
|
|
27032
27280
|
...loadedConfig,
|
|
27033
27281
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
@@ -27074,6 +27322,7 @@ async function runContext(args) {
|
|
|
27074
27322
|
const subject = args.target?.trim();
|
|
27075
27323
|
if (subject) {
|
|
27076
27324
|
const pack = createContextPack({ ...result.graph, diagnostics: result.diagnostics }, subject);
|
|
27325
|
+
const execution = args.events === undefined ? undefined : createExecutionContextPack({ ...result.graph, diagnostics: result.diagnostics }, subject, await readExecutionMetadata(resolve18(root, args.events)), args.request_id);
|
|
27077
27326
|
const verification = await createVerificationPlan(root, result.graph, pack.subject, sourceDir);
|
|
27078
27327
|
const repairs = createDiagnosticRepairPlan(pack.diagnostics);
|
|
27079
27328
|
const workflow = {
|
|
@@ -27104,7 +27353,7 @@ async function runContext(args) {
|
|
|
27104
27353
|
},
|
|
27105
27354
|
executed: false
|
|
27106
27355
|
};
|
|
27107
|
-
return textResult(args.format === "json" ? JSON.stringify({ ...pack, workflow }, null, 2) : [
|
|
27356
|
+
return textResult(args.format === "json" ? JSON.stringify({ ...pack, ...execution ? { execution } : {}, workflow }, null, 2) : [
|
|
27108
27357
|
formatModuleContextPack(pack),
|
|
27109
27358
|
` edit files (project-relative): ${workflow.editFiles.join(", ") || "-"}`,
|
|
27110
27359
|
` focused tests: ${verification.tests.join(", ") || "-"}`,
|
|
@@ -27256,11 +27505,11 @@ async function runDoctor(args) {
|
|
|
27256
27505
|
`), doctor.errors > 0);
|
|
27257
27506
|
}
|
|
27258
27507
|
async function runFix(args) {
|
|
27259
|
-
const root =
|
|
27508
|
+
const root = resolve18(args.root || process.cwd());
|
|
27260
27509
|
const fixPath = args.fix?.trim();
|
|
27261
27510
|
if (!fixPath)
|
|
27262
27511
|
throw new Error("app fix requires --fix <path-to-diagnostic-fix.json>");
|
|
27263
|
-
const parsed = JSON.parse(await readFile4(
|
|
27512
|
+
const parsed = JSON.parse(await readFile4(resolve18(root, fixPath), "utf8"));
|
|
27264
27513
|
const write = args.write === true;
|
|
27265
27514
|
const applied = await applyDiagnosticFix(parsed, { rootDir: root, dryRun: !write });
|
|
27266
27515
|
return textResult(JSON.stringify({
|
|
@@ -27303,7 +27552,7 @@ function devDatabase(profile, explicit) {
|
|
|
27303
27552
|
}
|
|
27304
27553
|
function readManifestModules(outDir) {
|
|
27305
27554
|
try {
|
|
27306
|
-
const parsed = JSON.parse(
|
|
27555
|
+
const parsed = JSON.parse(readFileSync9(join13(outDir, "app.manifest.json"), "utf8"));
|
|
27307
27556
|
return Array.isArray(parsed.modules) ? parsed.modules.map((module) => module.name) : [];
|
|
27308
27557
|
} catch {
|
|
27309
27558
|
return [];
|
|
@@ -27346,12 +27595,12 @@ async function runAppDev(args, options) {
|
|
|
27346
27595
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "app dev --profile must be fast or integration");
|
|
27347
27596
|
}
|
|
27348
27597
|
const database = devDatabase(profile, args.database_url);
|
|
27349
|
-
const root =
|
|
27598
|
+
const root = resolve18(args.root || process.cwd());
|
|
27350
27599
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27351
27600
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27352
27601
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
27353
27602
|
const include = parseInclude(args.include) ?? loadedConfig.include;
|
|
27354
|
-
const outDir = args.out_dir ?
|
|
27603
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir;
|
|
27355
27604
|
const watch = args.once !== true && args.watch !== false;
|
|
27356
27605
|
const compileOptions = compileOptionsFromConfig({
|
|
27357
27606
|
...loadedConfig,
|
|
@@ -27437,7 +27686,7 @@ async function runAppDev(args, options) {
|
|
|
27437
27686
|
return report(lastEvent, "stopped");
|
|
27438
27687
|
}
|
|
27439
27688
|
async function runCompile(args) {
|
|
27440
|
-
const root =
|
|
27689
|
+
const root = resolve18(args.root || process.cwd());
|
|
27441
27690
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27442
27691
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
27443
27692
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
@@ -27446,7 +27695,7 @@ async function runCompile(args) {
|
|
|
27446
27695
|
...compileOptionsFromConfig({
|
|
27447
27696
|
...loadedConfig,
|
|
27448
27697
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
27449
|
-
outDir: args.out_dir ?
|
|
27698
|
+
outDir: args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir,
|
|
27450
27699
|
...include === undefined ? {} : { include },
|
|
27451
27700
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
27452
27701
|
}, root),
|
|
@@ -27470,7 +27719,7 @@ async function runCompile(args) {
|
|
|
27470
27719
|
return textResult(text, hasError);
|
|
27471
27720
|
}
|
|
27472
27721
|
async function runCheck(args) {
|
|
27473
|
-
const root =
|
|
27722
|
+
const root = resolve18(args.root || process.cwd());
|
|
27474
27723
|
const database = checkAppDatabaseSources(root);
|
|
27475
27724
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
27476
27725
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
@@ -27479,7 +27728,7 @@ async function runCheck(args) {
|
|
|
27479
27728
|
const config = compileOptionsFromConfig({
|
|
27480
27729
|
...loadedConfig,
|
|
27481
27730
|
...configuredRoot === undefined ? {} : { root: configuredRoot },
|
|
27482
|
-
outDir: args.out_dir ?
|
|
27731
|
+
outDir: args.out_dir ? resolve18(root, args.out_dir) : defaults.outDir,
|
|
27483
27732
|
...include === undefined ? {} : { include },
|
|
27484
27733
|
strict: args.strict ?? loadedConfig.strict ?? false
|
|
27485
27734
|
}, root);
|
|
@@ -27509,7 +27758,7 @@ async function runCheck(args) {
|
|
|
27509
27758
|
}
|
|
27510
27759
|
async function readManifest(root, args) {
|
|
27511
27760
|
const config = await loadSupacloudConfig(root);
|
|
27512
|
-
const outDir = args.out_dir ?
|
|
27761
|
+
const outDir = args.out_dir ? resolve18(root, args.out_dir) : resolveSupacloudConfig(config, root).outDir;
|
|
27513
27762
|
const manifestPath = join13(outDir, "app.manifest.json");
|
|
27514
27763
|
if (!existsSync11(manifestPath)) {
|
|
27515
27764
|
throw new Error(`Manifest not found: ${manifestPath}(先运行 app compile 生成)`);
|
|
@@ -27554,7 +27803,7 @@ function formatGraphText(manifest) {
|
|
|
27554
27803
|
`);
|
|
27555
27804
|
}
|
|
27556
27805
|
async function runGraph(args) {
|
|
27557
|
-
const root =
|
|
27806
|
+
const root = resolve18(args.root || process.cwd());
|
|
27558
27807
|
const manifest = await readManifest(root, args);
|
|
27559
27808
|
if (args.format === "json")
|
|
27560
27809
|
return textResult(JSON.stringify(manifest, null, 2));
|
|
@@ -27622,7 +27871,7 @@ async function runExplain(args) {
|
|
|
27622
27871
|
const target = args.target?.trim();
|
|
27623
27872
|
if (!target)
|
|
27624
27873
|
throw new Error("app explain requires --target(provider 类名 / token 名 / command 名 / job 名)");
|
|
27625
|
-
const root =
|
|
27874
|
+
const root = resolve18(args.root || process.cwd());
|
|
27626
27875
|
const manifest = await readManifest(root, args);
|
|
27627
27876
|
for (const module of manifest.modules) {
|
|
27628
27877
|
const job = module.jobs?.find((entry) => entry.name === target || entry.className === target);
|
|
@@ -27697,10 +27946,10 @@ async function runExplain(args) {
|
|
|
27697
27946
|
return textResult(`未找到对象: ${target}`, true);
|
|
27698
27947
|
}
|
|
27699
27948
|
async function runExportTools(args) {
|
|
27700
|
-
const root =
|
|
27949
|
+
const root = resolve18(args.root || process.cwd());
|
|
27701
27950
|
const manifest = await readManifest(root, { ...args, out_dir: undefined });
|
|
27702
27951
|
const definitions = buildToolDefinitions(manifest);
|
|
27703
|
-
const outDir =
|
|
27952
|
+
const outDir = resolve18(args.out_dir || join13(root, "generated"));
|
|
27704
27953
|
if (args.format === "json") {
|
|
27705
27954
|
return textResult(JSON.stringify(definitions, null, 2));
|
|
27706
27955
|
}
|
|
@@ -27721,7 +27970,7 @@ async function runExportTools(args) {
|
|
|
27721
27970
|
return textResult(summary);
|
|
27722
27971
|
}
|
|
27723
27972
|
async function runDelivery(args) {
|
|
27724
|
-
const root =
|
|
27973
|
+
const root = resolve18(args.root || process.cwd());
|
|
27725
27974
|
if (args.action === "build") {
|
|
27726
27975
|
const database = checkAppDatabaseSources(root);
|
|
27727
27976
|
if (database && !database.ok) {
|
|
@@ -27731,7 +27980,7 @@ async function runDelivery(args) {
|
|
|
27731
27980
|
const loaded = await loadSupacloudConfig(root);
|
|
27732
27981
|
const options = compileOptionsFromConfig({
|
|
27733
27982
|
...loaded,
|
|
27734
|
-
...args.out_dir === undefined ? {} : { outDir:
|
|
27983
|
+
...args.out_dir === undefined ? {} : { outDir: resolve18(root, args.out_dir) },
|
|
27735
27984
|
...args.include === undefined ? {} : { include: parseInclude(args.include) },
|
|
27736
27985
|
...args.strict === undefined ? {} : { strict: args.strict }
|
|
27737
27986
|
}, root);
|
|
@@ -27808,7 +28057,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27808
28057
|
if (request.activation_id !== undefined || request.expected_activation_id !== undefined) {
|
|
27809
28058
|
throw new Error("app release generates one activation identity; use app deploy for an exact retry");
|
|
27810
28059
|
}
|
|
27811
|
-
const root =
|
|
28060
|
+
const root = resolve18(request.root || process.cwd());
|
|
27812
28061
|
const completed = {};
|
|
27813
28062
|
let manifestPath;
|
|
27814
28063
|
let buildReceipt;
|
|
@@ -27816,7 +28065,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27816
28065
|
if (request.manifest_path === undefined) {
|
|
27817
28066
|
throw new Error("app release --skip_build requires --manifest_path");
|
|
27818
28067
|
}
|
|
27819
|
-
manifestPath =
|
|
28068
|
+
manifestPath = resolve18(root, request.manifest_path);
|
|
27820
28069
|
completed.build = { skipped: true, manifest_path: manifestPath };
|
|
27821
28070
|
} else {
|
|
27822
28071
|
const built = await runDelivery({ ...request, action: "build", root, format: "json" });
|
|
@@ -27853,7 +28102,7 @@ async function runAppRelease(request, options, delegate) {
|
|
|
27853
28102
|
let configurationWrite;
|
|
27854
28103
|
const activationId = crypto.randomUUID();
|
|
27855
28104
|
if (request.configuration_path !== undefined) {
|
|
27856
|
-
const configurationPath =
|
|
28105
|
+
const configurationPath = resolve18(root, request.configuration_path);
|
|
27857
28106
|
try {
|
|
27858
28107
|
configurationWrite = parseApplicationConfigurationWrite(await Bun.file(configurationPath).json());
|
|
27859
28108
|
configurationId = configurationWrite.configuration_id;
|
|
@@ -28180,15 +28429,21 @@ Execution: skipped`);
|
|
|
28180
28429
|
return format === "json" || json ? result : formatPromotionExecution(result);
|
|
28181
28430
|
}
|
|
28182
28431
|
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) {
|
|
28432
|
+
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
28433
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
28185
28434
|
}
|
|
28186
28435
|
request = {
|
|
28187
28436
|
...request,
|
|
28188
28437
|
...request["dry-run"] === undefined ? {} : { dry_run: request["dry-run"] },
|
|
28189
28438
|
...request["register-in"] === undefined ? {} : { register_in: request["register-in"] },
|
|
28190
|
-
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] }
|
|
28439
|
+
...request["database-url"] === undefined ? {} : { database_url: request["database-url"] },
|
|
28440
|
+
...request["request-id"] === undefined ? {} : { request_id: request["request-id"] }
|
|
28191
28441
|
};
|
|
28442
|
+
if (request.events !== undefined || request.request_id !== undefined) {
|
|
28443
|
+
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) {
|
|
28444
|
+
throw new Error("Execution context requires app context, --target, --events, --request-id and --format json; --write is not supported");
|
|
28445
|
+
}
|
|
28446
|
+
}
|
|
28192
28447
|
if ((request.dry_run !== undefined || request.register_in !== undefined) && request.action !== "generate") {
|
|
28193
28448
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--dry-run and --register-in apply only to app generate");
|
|
28194
28449
|
}
|
|
@@ -28375,6 +28630,9 @@ function registerAppTools(server, options = {}) {
|
|
|
28375
28630
|
database_url: optional(String2(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
|
|
28376
28631
|
"database-url": optional(String2(), "[dev] CLI alias of database_url"),
|
|
28377
28632
|
target: optional(String2(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
|
|
28633
|
+
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
28634
|
+
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
28635
|
+
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
28378
28636
|
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
28379
28637
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)"),
|
|
28380
28638
|
skip_build: optional(Boolean2(), "[release] Reuse --manifest_path instead of running the local delivery build")
|
|
@@ -28400,6 +28658,9 @@ function registerAppAliases(server) {
|
|
|
28400
28658
|
strict: optional(Boolean2(), "[compile/check] Promote warnings to errors"),
|
|
28401
28659
|
format: optional(stringEnum2(["text", "json"]), "Output format (default: text)"),
|
|
28402
28660
|
target: optional(String2(), "[explain/context] Provider class name / token name / command name / job name / module name"),
|
|
28661
|
+
events: optional(String2(), "[context] Project-relative execution metadata JSON or generated JSON-lines file"),
|
|
28662
|
+
request_id: optional(String2(), "[context] Opaque request ID or Workflow run ID selected from --events"),
|
|
28663
|
+
"request-id": optional(String2(), "[context] CLI alias of request_id"),
|
|
28403
28664
|
fix: optional(String2(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
|
|
28404
28665
|
write: optional(Boolean2(), "[fix] Write the fix to disk (default: preview only)")
|
|
28405
28666
|
};
|
|
@@ -28410,7 +28671,7 @@ function registerAppAliases(server) {
|
|
|
28410
28671
|
|
|
28411
28672
|
// src/shared/tools/db-governance-tools.ts
|
|
28412
28673
|
import { existsSync as existsSync13 } from "node:fs";
|
|
28413
|
-
import { join as join16, resolve as
|
|
28674
|
+
import { join as join16, resolve as resolve21 } from "node:path";
|
|
28414
28675
|
import {
|
|
28415
28676
|
buildDatabaseManifest,
|
|
28416
28677
|
explainObject,
|
|
@@ -28420,10 +28681,10 @@ import {
|
|
|
28420
28681
|
} from "@supacloud/db";
|
|
28421
28682
|
|
|
28422
28683
|
// src/shared/tools/db-workflow.ts
|
|
28423
|
-
import { createHash as
|
|
28684
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
28424
28685
|
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
28686
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
28426
|
-
import { dirname as
|
|
28687
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join15, relative as relative8, resolve as resolve20 } from "node:path";
|
|
28427
28688
|
import {
|
|
28428
28689
|
assertApplicationRoleRestricted,
|
|
28429
28690
|
readDatabaseRole,
|
|
@@ -28435,7 +28696,7 @@ import { parseDatabaseSourcesConfig } from "@supacloud/db/source-contracts";
|
|
|
28435
28696
|
|
|
28436
28697
|
// src/shared/tools/supabase-cli-tools.ts
|
|
28437
28698
|
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
28438
|
-
import { dirname as
|
|
28699
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join14, resolve as resolve19 } from "node:path";
|
|
28439
28700
|
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
28701
|
var VALID_VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
28441
28702
|
var VALID_MIGRATION_NAME2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
|
|
@@ -28480,12 +28741,12 @@ function schemaArguments2(schema) {
|
|
|
28480
28741
|
function workdirArguments2(workdir) {
|
|
28481
28742
|
if (!workdir)
|
|
28482
28743
|
throw new Error("A workdir is required");
|
|
28483
|
-
return ["--workdir",
|
|
28744
|
+
return ["--workdir", resolve19(workdir)];
|
|
28484
28745
|
}
|
|
28485
28746
|
function resolveOutputPath2(workdir, file, label) {
|
|
28486
28747
|
if (!file || /[\r\n\0]/.test(file))
|
|
28487
28748
|
throw new Error(`${label} file is required`);
|
|
28488
|
-
return isAbsolute6(file) ?
|
|
28749
|
+
return isAbsolute6(file) ? resolve19(file) : resolve19(workdir, file);
|
|
28489
28750
|
}
|
|
28490
28751
|
function databaseTargetArguments2(databaseUrl) {
|
|
28491
28752
|
return databaseUrl ? ["--db-url", requirePostgresUrl2(databaseUrl)] : ["--local"];
|
|
@@ -28660,7 +28921,7 @@ function actionArguments2(request, workdir) {
|
|
|
28660
28921
|
function buildOfficialSupabaseArgs2(request) {
|
|
28661
28922
|
if (request.action === "version")
|
|
28662
28923
|
return ["--version"];
|
|
28663
|
-
const workdir = request.workdir ?
|
|
28924
|
+
const workdir = request.workdir ? resolve19(request.workdir) : undefined;
|
|
28664
28925
|
if (!workdir)
|
|
28665
28926
|
throw new Error("A workdir is required");
|
|
28666
28927
|
return [...actionArguments2(request, workdir), ...workdirArguments2(workdir)];
|
|
@@ -28702,14 +28963,14 @@ function resolveOfficialSupabaseCommand2(workdir, environment = process.env) {
|
|
|
28702
28963
|
}
|
|
28703
28964
|
return ["npx", "--yes", `supabase@${version}`];
|
|
28704
28965
|
}
|
|
28705
|
-
const localPackageEntry = join14(
|
|
28966
|
+
const localPackageEntry = join14(resolve19(workdir), "node_modules", "supabase", "dist", "supabase.js");
|
|
28706
28967
|
if (existsSync12(localPackageEntry))
|
|
28707
28968
|
return [process.execPath, localPackageEntry];
|
|
28708
28969
|
return ["supabase"];
|
|
28709
28970
|
}
|
|
28710
28971
|
|
|
28711
28972
|
// src/shared/tools/db-workflow.ts
|
|
28712
|
-
var hash4 = (content) =>
|
|
28973
|
+
var hash4 = (content) => createHash8("sha256").update(content).digest("hex");
|
|
28713
28974
|
var result = (value, isError = false) => ({
|
|
28714
28975
|
isError,
|
|
28715
28976
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -28873,8 +29134,8 @@ async function reverse(args, root, options) {
|
|
|
28873
29134
|
if (!selectedSchemas.length || selectedSchemas.some((schema) => !/^[a-z_][a-z0-9_$]*$/i.test(schema))) {
|
|
28874
29135
|
throw new Error("Select valid PostgreSQL schemas");
|
|
28875
29136
|
}
|
|
28876
|
-
await mkdir6(
|
|
28877
|
-
const stage = await mkdtemp(join15(
|
|
29137
|
+
await mkdir6(dirname11(out), { recursive: true });
|
|
29138
|
+
const stage = await mkdtemp(join15(dirname11(out), ".reverse-"));
|
|
28878
29139
|
try {
|
|
28879
29140
|
const snapshot = await withDatabase(args, options, async (executor, url) => {
|
|
28880
29141
|
const snapshot = await reverseDatabase(executor, selectedSchemas);
|
|
@@ -28918,8 +29179,8 @@ async function diff2(args, root, options) {
|
|
|
28918
29179
|
const input = await migrations(root, args.dir ?? (await boundary(root))?.migrations ?? "supabase/migrations");
|
|
28919
29180
|
if (overlaps(input.dir, out))
|
|
28920
29181
|
throw new Error("Candidate output overlaps reviewed migration history");
|
|
28921
|
-
await mkdir6(
|
|
28922
|
-
const stage = await mkdtemp(join15(
|
|
29182
|
+
await mkdir6(dirname11(out), { recursive: true });
|
|
29183
|
+
const stage = await mkdtemp(join15(dirname11(out), ".diff-"));
|
|
28923
29184
|
try {
|
|
28924
29185
|
await initializeCandidate(stage, args.db_major_version);
|
|
28925
29186
|
if (!await copySqlSources(root, sourcePath, join15(stage, "supabase/schemas")))
|
|
@@ -29011,7 +29272,7 @@ async function migrationAction(args, root, options) {
|
|
|
29011
29272
|
}
|
|
29012
29273
|
}
|
|
29013
29274
|
async function runDbWorkflow(args, projectRoot, options = {}) {
|
|
29014
|
-
const root =
|
|
29275
|
+
const root = resolve20(projectRoot);
|
|
29015
29276
|
if ((await lstat4(root)).isSymbolicLink())
|
|
29016
29277
|
throw new Error("Workflow root must not be a symlink");
|
|
29017
29278
|
switch (args.action) {
|
|
@@ -29072,7 +29333,7 @@ function normalizeModule(candidate, sourcePath) {
|
|
|
29072
29333
|
};
|
|
29073
29334
|
}
|
|
29074
29335
|
async function loadDatabaseModules(root, moduleFile) {
|
|
29075
|
-
const filePath = moduleFile ?
|
|
29336
|
+
const filePath = moduleFile ? resolve21(root, moduleFile) : join16(root, "db", "modules.ts");
|
|
29076
29337
|
if (!existsSync13(filePath)) {
|
|
29077
29338
|
throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
|
|
29078
29339
|
}
|
|
@@ -29208,7 +29469,7 @@ async function runLint(args, root) {
|
|
|
29208
29469
|
const issues = [];
|
|
29209
29470
|
for (const module of selected) {
|
|
29210
29471
|
const readSqlFile = async (path) => {
|
|
29211
|
-
const absolute =
|
|
29472
|
+
const absolute = resolve21(root, path);
|
|
29212
29473
|
try {
|
|
29213
29474
|
return await Bun.file(absolute).text();
|
|
29214
29475
|
} catch (error) {
|
|
@@ -29248,14 +29509,14 @@ var defaultLiteSpawn = async (command, cwd) => {
|
|
|
29248
29509
|
return { exitCode, stdout, stderr };
|
|
29249
29510
|
};
|
|
29250
29511
|
async function runModuleCheckLite(args, root, spawn, liteBinary) {
|
|
29251
|
-
const projectDir =
|
|
29512
|
+
const projectDir = resolve21(args.project_dir?.trim() || root);
|
|
29252
29513
|
const binary = liteBinary === undefined ? Bun.which("supacloud-lite") : liteBinary;
|
|
29253
29514
|
if (!binary) {
|
|
29254
29515
|
throw new Error("db module_check --lite 需要本机可用的 supacloud-lite(npm i -g @supacloud/lite,或加入 PATH)");
|
|
29255
29516
|
}
|
|
29256
29517
|
const command = [binary, "db", "check", "--project-dir", projectDir];
|
|
29257
29518
|
if (args.module_file?.trim())
|
|
29258
|
-
command.push("--module-file",
|
|
29519
|
+
command.push("--module-file", resolve21(root, args.module_file.trim()));
|
|
29259
29520
|
const result = await spawn(command, projectDir);
|
|
29260
29521
|
const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
|
|
29261
29522
|
`);
|
|
@@ -29315,7 +29576,7 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
29315
29576
|
migration_role: optional(String2(), "[role_sql] Privileged migration role"),
|
|
29316
29577
|
database: optional(String2(), "[role_sql] PostgreSQL database name")
|
|
29317
29578
|
}, async (request) => {
|
|
29318
|
-
const root =
|
|
29579
|
+
const root = resolve21(request.root || fallbackRoot);
|
|
29319
29580
|
switch (request.action) {
|
|
29320
29581
|
case "context":
|
|
29321
29582
|
return runContext2(request, root);
|
|
@@ -29345,8 +29606,8 @@ function registerDbGovernanceTools(server, options = {}) {
|
|
|
29345
29606
|
|
|
29346
29607
|
// src/shared/tools/scheduled-function-tools.ts
|
|
29347
29608
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
29348
|
-
import { readFileSync as
|
|
29349
|
-
import { resolve as
|
|
29609
|
+
import { readFileSync as readFileSync10, statSync as statSync9 } from "node:fs";
|
|
29610
|
+
import { resolve as resolve22 } from "node:path";
|
|
29350
29611
|
import { isDeepStrictEqual } from "node:util";
|
|
29351
29612
|
var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
|
|
29352
29613
|
var ENVIRONMENT_NAME_PATTERN3 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
|
|
@@ -29430,14 +29691,14 @@ function validScheduledFunctionCron(expression) {
|
|
|
29430
29691
|
function readScheduleBodyFile(bodyPathInput) {
|
|
29431
29692
|
if (!bodyPathInput.trim())
|
|
29432
29693
|
throw new Error("'body_file' must be a path");
|
|
29433
|
-
const bodyPath =
|
|
29694
|
+
const bodyPath = resolve22(bodyPathInput);
|
|
29434
29695
|
const bodyStat = statSync9(bodyPath);
|
|
29435
29696
|
if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
|
|
29436
29697
|
throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
|
|
29437
29698
|
}
|
|
29438
29699
|
let payload;
|
|
29439
29700
|
try {
|
|
29440
|
-
payload = JSON.parse(
|
|
29701
|
+
payload = JSON.parse(readFileSync10(bodyPath, "utf8"));
|
|
29441
29702
|
} catch (error) {
|
|
29442
29703
|
if (!(error instanceof SyntaxError))
|
|
29443
29704
|
throw error;
|
|
@@ -29813,9 +30074,9 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
29813
30074
|
};
|
|
29814
30075
|
|
|
29815
30076
|
// 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
|
|
30077
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
30078
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
30079
|
+
import { dirname as dirname12, join as join17, relative as relative9, resolve as resolve23 } from "node:path";
|
|
29819
30080
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
29820
30081
|
var SAFE_ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
29821
30082
|
var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
|
|
@@ -30056,7 +30317,7 @@ function genericScopedReleaseScopeJson(scope) {
|
|
|
30056
30317
|
return JSON.stringify(canonicalValue(scope));
|
|
30057
30318
|
}
|
|
30058
30319
|
function genericScopedReleaseScopeSha256(scope) {
|
|
30059
|
-
return
|
|
30320
|
+
return createHash9("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
|
|
30060
30321
|
}
|
|
30061
30322
|
function scopeEnvironmentId(scope, action) {
|
|
30062
30323
|
const environmentId = isRecord6(scope) ? scope.targetEnvironmentId : undefined;
|
|
@@ -30137,13 +30398,13 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30137
30398
|
const results = [];
|
|
30138
30399
|
const resolvePathWithFallbacks = (filePath) => {
|
|
30139
30400
|
const trimmed = filePath.trim();
|
|
30140
|
-
const resolved =
|
|
30401
|
+
const resolved = resolve23(cwd, trimmed);
|
|
30141
30402
|
if (existsSync14(resolved))
|
|
30142
30403
|
return resolved;
|
|
30143
30404
|
const candidates = [
|
|
30144
|
-
|
|
30145
|
-
|
|
30146
|
-
|
|
30405
|
+
resolve23(cwd, "supacloud/fa/release-scopes", trimmed),
|
|
30406
|
+
resolve23(cwd, "release-scopes", trimmed),
|
|
30407
|
+
resolve23(cwd, "scopes", trimmed)
|
|
30147
30408
|
];
|
|
30148
30409
|
const found = candidates.find(existsSync14);
|
|
30149
30410
|
if (found)
|
|
@@ -30166,10 +30427,10 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30166
30427
|
if (!taskFilter && (!dir || typeof dir !== "string" || !dir.trim())) {
|
|
30167
30428
|
throw new Error("At least one of --file, --files, or --task is required for scope_rebind");
|
|
30168
30429
|
}
|
|
30169
|
-
const candidates = typeof dir === "string" && dir.trim() ? [
|
|
30170
|
-
|
|
30171
|
-
|
|
30172
|
-
|
|
30430
|
+
const candidates = typeof dir === "string" && dir.trim() ? [resolve23(cwd, dir.trim())] : [
|
|
30431
|
+
resolve23(cwd, "supacloud/fa/release-scopes"),
|
|
30432
|
+
resolve23(cwd, "release-scopes"),
|
|
30433
|
+
resolve23(cwd, "scopes")
|
|
30173
30434
|
];
|
|
30174
30435
|
for (const directory of candidates) {
|
|
30175
30436
|
if (existsSync14(directory)) {
|
|
@@ -30184,7 +30445,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30184
30445
|
results.push(fullPath);
|
|
30185
30446
|
} else {
|
|
30186
30447
|
try {
|
|
30187
|
-
const parsed = JSON.parse(
|
|
30448
|
+
const parsed = JSON.parse(readFileSync11(fullPath, "utf8"));
|
|
30188
30449
|
if (typeof parsed?.taskId === "string" && parsed.taskId.toLowerCase() === taskFilter) {
|
|
30189
30450
|
results.push(fullPath);
|
|
30190
30451
|
}
|
|
@@ -30203,7 +30464,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
30203
30464
|
return [...new Set(results)];
|
|
30204
30465
|
}
|
|
30205
30466
|
function handleScopeRebind(args) {
|
|
30206
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30467
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30207
30468
|
const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
|
|
30208
30469
|
const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_rebind");
|
|
30209
30470
|
const scopeFiles = collectScopeFiles(cwd, args.file, args.files, args.dir, args.task);
|
|
@@ -30211,7 +30472,7 @@ function handleScopeRebind(args) {
|
|
|
30211
30472
|
const parsedScopes = scopeFiles.map((filePath) => {
|
|
30212
30473
|
let content;
|
|
30213
30474
|
try {
|
|
30214
|
-
content =
|
|
30475
|
+
content = readFileSync11(filePath, "utf8");
|
|
30215
30476
|
} catch (cause) {
|
|
30216
30477
|
throw new Error(`Failed to read scope file '${filePath}': ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
30217
30478
|
}
|
|
@@ -30261,18 +30522,18 @@ function handleScopeRebind(args) {
|
|
|
30261
30522
|
});
|
|
30262
30523
|
}
|
|
30263
30524
|
function handleScopeInspect(args) {
|
|
30264
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30525
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30265
30526
|
const requestedEnvironmentId = requestedScopeEnvironment(args, "scope_inspect");
|
|
30266
30527
|
if (typeof args.file !== "string" || !args.file.trim()) {
|
|
30267
30528
|
throw new Error("'file' is required for scope_inspect");
|
|
30268
30529
|
}
|
|
30269
30530
|
const trimmed = args.file.trim();
|
|
30270
|
-
let filePath =
|
|
30531
|
+
let filePath = resolve23(cwd, trimmed);
|
|
30271
30532
|
if (!existsSync14(filePath)) {
|
|
30272
30533
|
const candidates = [
|
|
30273
|
-
|
|
30274
|
-
|
|
30275
|
-
|
|
30534
|
+
resolve23(cwd, "supacloud/fa/release-scopes", trimmed),
|
|
30535
|
+
resolve23(cwd, "release-scopes", trimmed),
|
|
30536
|
+
resolve23(cwd, "scopes", trimmed)
|
|
30276
30537
|
];
|
|
30277
30538
|
const found = candidates.find(existsSync14);
|
|
30278
30539
|
if (found)
|
|
@@ -30282,7 +30543,7 @@ function handleScopeInspect(args) {
|
|
|
30282
30543
|
}
|
|
30283
30544
|
let content;
|
|
30284
30545
|
try {
|
|
30285
|
-
content =
|
|
30546
|
+
content = readFileSync11(filePath, "utf8");
|
|
30286
30547
|
} catch (cause) {
|
|
30287
30548
|
throw new Error(`Failed to read scope file '${args.file}': ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
30288
30549
|
}
|
|
@@ -30337,7 +30598,7 @@ function parseCommaList(value) {
|
|
|
30337
30598
|
return [];
|
|
30338
30599
|
}
|
|
30339
30600
|
function handleScopeCreate(args, fallbackRef) {
|
|
30340
|
-
const cwd = typeof args.cwd === "string" && args.cwd.trim() ?
|
|
30601
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
30341
30602
|
if (typeof args.file !== "string" || !args.file.trim()) {
|
|
30342
30603
|
throw new Error("'file' is required for scope_create");
|
|
30343
30604
|
}
|
|
@@ -30353,8 +30614,8 @@ function handleScopeCreate(args, fallbackRef) {
|
|
|
30353
30614
|
throw new Error("'ref' is invalid for release controls");
|
|
30354
30615
|
}
|
|
30355
30616
|
const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
|
|
30356
|
-
const filePath =
|
|
30357
|
-
mkdirSync5(
|
|
30617
|
+
const filePath = resolve23(cwd, args.file.trim());
|
|
30618
|
+
mkdirSync5(dirname12(filePath), { recursive: true });
|
|
30358
30619
|
const scope = {
|
|
30359
30620
|
baseCommit: targetBaseCommit,
|
|
30360
30621
|
excludedFunctions: parseCommaList(args.excluded_functions),
|
|
@@ -30600,9 +30861,9 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
30600
30861
|
// src/shared/tools/deploy-tools.ts
|
|
30601
30862
|
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile6, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
|
|
30602
30863
|
import { spawn as spawn2 } from "node:child_process";
|
|
30603
|
-
import { createHash as
|
|
30864
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
30604
30865
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
30605
|
-
import { basename as basename8, dirname as
|
|
30866
|
+
import { basename as basename8, dirname as dirname13, join as join18, relative as relative10, resolve as resolve24, sep as sep5 } from "node:path";
|
|
30606
30867
|
|
|
30607
30868
|
// src/shared/tools/project-deployment.ts
|
|
30608
30869
|
async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
|
|
@@ -30728,15 +30989,15 @@ async function readJson(path) {
|
|
|
30728
30989
|
}
|
|
30729
30990
|
}
|
|
30730
30991
|
async function findDeployConfigRoot(startDirectory, environmentName) {
|
|
30731
|
-
let directory =
|
|
30992
|
+
let directory = resolve24(startDirectory);
|
|
30732
30993
|
while (true) {
|
|
30733
30994
|
const environmentFile = environmentName ? `.env.supacloud.${environmentName}` : null;
|
|
30734
30995
|
if (await pathExists(join18(directory, "supacloud.json")) || await pathExists(join18(directory, "supacloud.config.ts")) || environmentFile !== null && await pathExists(join18(directory, environmentFile))) {
|
|
30735
30996
|
return directory;
|
|
30736
30997
|
}
|
|
30737
|
-
const parent =
|
|
30998
|
+
const parent = dirname13(directory);
|
|
30738
30999
|
if (parent === directory)
|
|
30739
|
-
return
|
|
31000
|
+
return resolve24(startDirectory);
|
|
30740
31001
|
directory = parent;
|
|
30741
31002
|
}
|
|
30742
31003
|
}
|
|
@@ -30889,7 +31150,7 @@ async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
|
30889
31150
|
return "yarn build";
|
|
30890
31151
|
if (directory === configRoot)
|
|
30891
31152
|
break;
|
|
30892
|
-
const parent =
|
|
31153
|
+
const parent = dirname13(directory);
|
|
30893
31154
|
if (parent === directory || !directory.startsWith(`${configRoot}${sep5}`))
|
|
30894
31155
|
break;
|
|
30895
31156
|
directory = parent;
|
|
@@ -30957,9 +31218,9 @@ function selectFrontendTarget(configRoot, config) {
|
|
|
30957
31218
|
throw new Error(`Multiple frontend deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Set defaultTarget to the Web target`);
|
|
30958
31219
|
}
|
|
30959
31220
|
function targetRoot(configRoot, target, name) {
|
|
30960
|
-
const root =
|
|
30961
|
-
const prefix = `${
|
|
30962
|
-
if (root !==
|
|
31221
|
+
const root = resolve24(configRoot, target.root || (name === "frontend" ? "." : name));
|
|
31222
|
+
const prefix = `${resolve24(configRoot)}${sep5}`;
|
|
31223
|
+
if (root !== resolve24(configRoot) && !root.startsWith(prefix)) {
|
|
30963
31224
|
throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
|
|
30964
31225
|
}
|
|
30965
31226
|
return root;
|
|
@@ -30977,8 +31238,8 @@ function defaultOutputDirectory(framework) {
|
|
|
30977
31238
|
}
|
|
30978
31239
|
}
|
|
30979
31240
|
function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
|
|
30980
|
-
const resolved =
|
|
30981
|
-
const projectRoot =
|
|
31241
|
+
const resolved = resolve24(projectDirectory, candidate);
|
|
31242
|
+
const projectRoot = resolve24(projectDirectory);
|
|
30982
31243
|
const projectPrefix = `${projectRoot}${sep5}`;
|
|
30983
31244
|
if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
|
|
30984
31245
|
throw new Error("Frontend output directory must stay inside the project directory");
|
|
@@ -31057,9 +31318,9 @@ async function collectArchiveFiles(root) {
|
|
|
31057
31318
|
throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
|
|
31058
31319
|
const archivePath = relative10(root, path).split(sep5).join("/");
|
|
31059
31320
|
files.set(archivePath, data);
|
|
31060
|
-
digests.push({ path: archivePath, size: data.byteLength, sha256:
|
|
31321
|
+
digests.push({ path: archivePath, size: data.byteLength, sha256: createHash10("sha256").update(data).digest("hex") });
|
|
31061
31322
|
}
|
|
31062
|
-
const tree =
|
|
31323
|
+
const tree = createHash10("sha256");
|
|
31063
31324
|
for (const file of digests.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
31064
31325
|
const pathBytes = Buffer.from(file.path, "utf8");
|
|
31065
31326
|
const frame = Buffer.alloc(12);
|
|
@@ -31075,7 +31336,7 @@ async function createCollectedFrontendArchive(outputDirectory, collected) {
|
|
|
31075
31336
|
try {
|
|
31076
31337
|
const archiveBytes = await createFrontendTarZstd(collected.files);
|
|
31077
31338
|
await writeFile6(archivePath, archiveBytes);
|
|
31078
|
-
const sha256 =
|
|
31339
|
+
const sha256 = createHash10("sha256").update(archiveBytes).digest("hex");
|
|
31079
31340
|
return {
|
|
31080
31341
|
archivePath,
|
|
31081
31342
|
sha256,
|
|
@@ -31119,7 +31380,7 @@ async function publishConfiguredFrontend(http, projectRef, configRoot, skipBuild
|
|
|
31119
31380
|
}
|
|
31120
31381
|
function registerDeployTools(server, http, options = {}) {
|
|
31121
31382
|
registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
|
|
31122
|
-
const invocationDirectory =
|
|
31383
|
+
const invocationDirectory = resolve24(String(args.cwd || options.cwd || process.cwd()));
|
|
31123
31384
|
const projectRef = String(args.ref || options.projectRef || "").trim();
|
|
31124
31385
|
if (!projectRef)
|
|
31125
31386
|
throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
|
|
@@ -31197,8 +31458,8 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
31197
31458
|
import { existsSync as existsSync15 } from "node:fs";
|
|
31198
31459
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
31199
31460
|
import { readdir as readdir7 } from "node:fs/promises";
|
|
31200
|
-
import { createHash as
|
|
31201
|
-
import { join as join19, resolve as
|
|
31461
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
31462
|
+
import { join as join19, resolve as resolve25 } from "node:path";
|
|
31202
31463
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
31203
31464
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
31204
31465
|
var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
|
|
@@ -31244,12 +31505,12 @@ function toolFailed(value) {
|
|
|
31244
31505
|
return result.isError === true || content.some((chunk) => chunk !== null && typeof chunk === "object" && ("text" in chunk) && typeof chunk.text === "string" && chunk.text.trimStart().startsWith("❌"));
|
|
31245
31506
|
}
|
|
31246
31507
|
async function migrateDatabase(args, options) {
|
|
31247
|
-
const root =
|
|
31508
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31248
31509
|
const projectConfig = await readProjectConfig(root);
|
|
31249
31510
|
const config = projectConfig.dev || {};
|
|
31250
31511
|
const database = config.database || {};
|
|
31251
31512
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
31252
|
-
const drizzleConfig =
|
|
31513
|
+
const drizzleConfig = resolve25(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
|
|
31253
31514
|
const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
|
|
31254
31515
|
if (!existsSync15(drizzleConfig))
|
|
31255
31516
|
throw new Error(`Drizzle config not found: ${drizzleConfig}`);
|
|
@@ -31296,7 +31557,7 @@ async function sourceFingerprint(root) {
|
|
|
31296
31557
|
}
|
|
31297
31558
|
};
|
|
31298
31559
|
await visit(root);
|
|
31299
|
-
const hash =
|
|
31560
|
+
const hash = createHash11("sha256");
|
|
31300
31561
|
for (const file of files) {
|
|
31301
31562
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
31302
31563
|
hash.update(await readFile7(file));
|
|
@@ -31321,14 +31582,14 @@ function targetDirectory(root, target, functionSlug, config = {}) {
|
|
|
31321
31582
|
const targets = config["targets"];
|
|
31322
31583
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
31323
31584
|
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 ?
|
|
31585
|
+
const base = match?.root ? resolve25(root, match.root) : join19(root, "supabase", "functions");
|
|
31325
31586
|
return match?.root ? base : functionSlug ? join19(base, safeToken(functionSlug, "function")) : base;
|
|
31326
31587
|
}
|
|
31327
31588
|
if (target === "frontend") {
|
|
31328
31589
|
const targets = config["targets"];
|
|
31329
31590
|
const entries = targets && typeof targets === "object" ? Object.values(targets) : [];
|
|
31330
31591
|
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 ?
|
|
31592
|
+
return match?.root ? resolve25(root, match.root) : join19(root, "apps", "web");
|
|
31332
31593
|
}
|
|
31333
31594
|
return root;
|
|
31334
31595
|
}
|
|
@@ -31367,10 +31628,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
|
|
|
31367
31628
|
return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
|
|
31368
31629
|
}
|
|
31369
31630
|
function sshArgs(connection, remoteCommand) {
|
|
31370
|
-
return ["-p", String(connection.port), "-i",
|
|
31631
|
+
return ["-p", String(connection.port), "-i", resolve25(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
|
|
31371
31632
|
}
|
|
31372
31633
|
function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
31373
|
-
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${
|
|
31634
|
+
const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve25(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
|
|
31374
31635
|
if (deleteRemote)
|
|
31375
31636
|
args.push("--delete-delay");
|
|
31376
31637
|
for (const exclude of excludes) {
|
|
@@ -31382,7 +31643,7 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
|
|
|
31382
31643
|
return args;
|
|
31383
31644
|
}
|
|
31384
31645
|
async function syncOnce(args, options) {
|
|
31385
|
-
const root =
|
|
31646
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31386
31647
|
const projectConfig = await readProjectConfig(root);
|
|
31387
31648
|
const config = projectConfig.dev || {};
|
|
31388
31649
|
const target = String(args.target || "project");
|
|
@@ -31391,8 +31652,8 @@ async function syncOnce(args, options) {
|
|
|
31391
31652
|
throw new Error(`Dev source directory not found: ${source}`);
|
|
31392
31653
|
let compiled = false;
|
|
31393
31654
|
if (config.compile === true && target !== "db") {
|
|
31394
|
-
const compileRoot =
|
|
31395
|
-
const compileOutDir =
|
|
31655
|
+
const compileRoot = resolve25(root, config.compileRoot || ".");
|
|
31656
|
+
const compileOutDir = resolve25(root, config.compileOutDir || "generated");
|
|
31396
31657
|
const compilation = await compileProject2({
|
|
31397
31658
|
rootDir: compileRoot,
|
|
31398
31659
|
outDir: compileOutDir,
|
|
@@ -31430,7 +31691,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31430
31691
|
if (["production", "prod"].includes((options.environment || "").toLowerCase()))
|
|
31431
31692
|
throw new Error("Remote dev mode is forbidden for production environments");
|
|
31432
31693
|
if (args.action === "status") {
|
|
31433
|
-
const root =
|
|
31694
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31434
31695
|
const config = await readDevConfig(root);
|
|
31435
31696
|
const connection = connectionArgs(options, config, args);
|
|
31436
31697
|
const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
|
|
@@ -31444,7 +31705,7 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31444
31705
|
return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
|
|
31445
31706
|
}
|
|
31446
31707
|
if (args.action === "watch") {
|
|
31447
|
-
const root =
|
|
31708
|
+
const root = resolve25(String(args.project_dir || options.cwd || process.cwd()));
|
|
31448
31709
|
const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
|
|
31449
31710
|
let fingerprint = "";
|
|
31450
31711
|
let lastSync = null;
|
|
@@ -31466,12 +31727,12 @@ function registerRemoteDevTools(server, options = {}) {
|
|
|
31466
31727
|
// src/shared/tools/developer-experience-tools.ts
|
|
31467
31728
|
import { existsSync as existsSync17 } from "node:fs";
|
|
31468
31729
|
import { mkdir as mkdir7, readdir as readdir8, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
31469
|
-
import { dirname as
|
|
31730
|
+
import { dirname as dirname14, join as join20, resolve as resolve27 } from "node:path";
|
|
31470
31731
|
|
|
31471
31732
|
// src/shared/context.ts
|
|
31472
|
-
import { existsSync as existsSync16, readFileSync as
|
|
31733
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
31473
31734
|
import { homedir as homedir3 } from "node:os";
|
|
31474
|
-
import { resolve as
|
|
31735
|
+
import { resolve as resolve26 } from "node:path";
|
|
31475
31736
|
var CORE_CONTEXT_KEYS2 = [
|
|
31476
31737
|
"SUPABASE_URL",
|
|
31477
31738
|
"SUPABASE_SERVICE_ROLE_KEY",
|
|
@@ -31513,7 +31774,7 @@ function readEnvFile2(path, required) {
|
|
|
31513
31774
|
return {};
|
|
31514
31775
|
}
|
|
31515
31776
|
try {
|
|
31516
|
-
return parseEnvFile2(
|
|
31777
|
+
return parseEnvFile2(readFileSync12(path, "utf8"));
|
|
31517
31778
|
} catch (error) {
|
|
31518
31779
|
const message = error instanceof Error ? error.message : String(error);
|
|
31519
31780
|
throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
|
|
@@ -31587,7 +31848,7 @@ function parseInsecureTls2(values) {
|
|
|
31587
31848
|
}
|
|
31588
31849
|
function namedEnvironmentSource2(cwd, selector) {
|
|
31589
31850
|
const environment = normalizeEnvironmentName(selector);
|
|
31590
|
-
const path =
|
|
31851
|
+
const path = resolve26(cwd, `.env.supacloud.${selector}`);
|
|
31591
31852
|
const values = readEnvFile2(path, true);
|
|
31592
31853
|
if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
|
|
31593
31854
|
throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
|
|
@@ -31595,7 +31856,7 @@ function namedEnvironmentSource2(cwd, selector) {
|
|
|
31595
31856
|
return { values, kind: "named_env_file", path, environment };
|
|
31596
31857
|
}
|
|
31597
31858
|
function explicitEnvironmentSource2(cwd, envFile) {
|
|
31598
|
-
const path =
|
|
31859
|
+
const path = resolve26(cwd, envFile);
|
|
31599
31860
|
const values = readEnvFile2(path, true);
|
|
31600
31861
|
if (!values.SUPACLOUD_ENV)
|
|
31601
31862
|
throw new Error(`SUPACLOUD_ENV is required in ${path}`);
|
|
@@ -31624,7 +31885,7 @@ function contextSource2(env, cwd, selection) {
|
|
|
31624
31885
|
if (hasProcessContext2(env)) {
|
|
31625
31886
|
return { values: processValues2(env), kind: "process_env", path: null, environment: "" };
|
|
31626
31887
|
}
|
|
31627
|
-
const path =
|
|
31888
|
+
const path = resolve26(cwd, ".env");
|
|
31628
31889
|
const values = readEnvFile2(path, false);
|
|
31629
31890
|
const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
|
|
31630
31891
|
return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync16(path) ? path : null, environment };
|
|
@@ -31639,7 +31900,7 @@ function resolveSupaCloudContext2(env = process.env, cwd = process.cwd(), select
|
|
|
31639
31900
|
host,
|
|
31640
31901
|
sshUser: env.SUPACLOUD_SSH_USER ?? "root",
|
|
31641
31902
|
sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
|
|
31642
|
-
sshKey: env.SUPACLOUD_SSH_KEY ??
|
|
31903
|
+
sshKey: env.SUPACLOUD_SSH_KEY ?? resolve26(homedir3(), ".ssh", "id_rsa"),
|
|
31643
31904
|
sshPass: env.SUPACLOUD_SSH_PASS ?? "",
|
|
31644
31905
|
apiUrl: core.apiUrl,
|
|
31645
31906
|
apiToken: core.apiToken,
|
|
@@ -31760,7 +32021,7 @@ async function ensureGitignore(root) {
|
|
|
31760
32021
|
return true;
|
|
31761
32022
|
}
|
|
31762
32023
|
async function initializeProject(request, options) {
|
|
31763
|
-
const root =
|
|
32024
|
+
const root = resolve27(request.root || options.cwd || process.cwd());
|
|
31764
32025
|
await mkdir7(root, { recursive: true });
|
|
31765
32026
|
const configPath = join20(root, "supacloud.json");
|
|
31766
32027
|
if (existsSync17(configPath)) {
|
|
@@ -31873,11 +32134,11 @@ function deployMissing(context, application, hasFrontend = false) {
|
|
|
31873
32134
|
}
|
|
31874
32135
|
async function inspectProfile(profile) {
|
|
31875
32136
|
try {
|
|
31876
|
-
const context = resolveSupaCloudContext2({},
|
|
32137
|
+
const context = resolveSupaCloudContext2({}, dirname14(profile.path), {
|
|
31877
32138
|
environmentName: profile.name
|
|
31878
32139
|
});
|
|
31879
32140
|
const status = statusMissing(context);
|
|
31880
|
-
const configText = await readFile8(join20(
|
|
32141
|
+
const configText = await readFile8(join20(dirname14(profile.path), "supacloud.json"), "utf8").catch((error) => {
|
|
31881
32142
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
31882
32143
|
return "{}";
|
|
31883
32144
|
throw error;
|
|
@@ -31921,7 +32182,7 @@ async function inspectProfile(profile) {
|
|
|
31921
32182
|
}
|
|
31922
32183
|
}
|
|
31923
32184
|
async function inspectEnvironment(request, options) {
|
|
31924
|
-
const root =
|
|
32185
|
+
const root = resolve27(request.root || options.cwd || process.cwd());
|
|
31925
32186
|
const profiles = await environmentProfiles(root);
|
|
31926
32187
|
if (request.action === "list") {
|
|
31927
32188
|
const active = options.activeContext?.environment || null;
|
|
@@ -32103,7 +32364,7 @@ async function runProjectDeployment(http, args) {
|
|
|
32103
32364
|
// package.json
|
|
32104
32365
|
var package_default = {
|
|
32105
32366
|
name: "@supacloud/cli",
|
|
32106
|
-
version: "0.103.
|
|
32367
|
+
version: "0.103.6",
|
|
32107
32368
|
description: "Project-scoped CLI for SupaCloud users",
|
|
32108
32369
|
type: "module",
|
|
32109
32370
|
main: "./dist/index.js",
|
|
@@ -32155,19 +32416,19 @@ var package_default = {
|
|
|
32155
32416
|
directory: "packages/cli"
|
|
32156
32417
|
},
|
|
32157
32418
|
dependencies: {
|
|
32158
|
-
"@supacloud/compiler": "^0.
|
|
32159
|
-
"@supacloud/delivery": "0.19.
|
|
32160
|
-
"@supacloud/db": "0.18.
|
|
32419
|
+
"@supacloud/compiler": "^0.47.2",
|
|
32420
|
+
"@supacloud/delivery": "0.19.3",
|
|
32421
|
+
"@supacloud/db": "0.18.3",
|
|
32161
32422
|
typebox: "1.3.37"
|
|
32162
32423
|
},
|
|
32163
32424
|
overrides: {
|
|
32164
|
-
"@supacloud/contracts": "0.11.
|
|
32165
|
-
"@supacloud/delivery": "0.19.
|
|
32425
|
+
"@supacloud/contracts": "0.11.4",
|
|
32426
|
+
"@supacloud/delivery": "0.19.3",
|
|
32166
32427
|
"@graphql-tools/utils": "^12.0.4"
|
|
32167
32428
|
},
|
|
32168
32429
|
devDependencies: {
|
|
32169
32430
|
"@supabase/supabase-js": "2.117.3",
|
|
32170
|
-
"@supacloud/elysia": "0.33.
|
|
32431
|
+
"@supacloud/elysia": "0.33.4",
|
|
32171
32432
|
"@types/bun": "^1.4.3",
|
|
32172
32433
|
effect: "4.0.5",
|
|
32173
32434
|
elysia: "2.0.0-beta.32",
|
|
@@ -32456,6 +32717,7 @@ EXAMPLES
|
|
|
32456
32717
|
${preferredCommand} app explain --target CaseService
|
|
32457
32718
|
${preferredCommand} app context --root . --format json
|
|
32458
32719
|
${preferredCommand} app context --root . --target CaseModule --format json
|
|
32720
|
+
${preferredCommand} app context --root . --target CaseModule --events execution.ndjson --request-id request-123 --format json
|
|
32459
32721
|
${preferredCommand} app doctor --root .
|
|
32460
32722
|
${preferredCommand} app fix --root . --fix fix.json --write
|
|
32461
32723
|
${preferredCommand} generate --kind module --name billing
|