@supacloud/cli 0.70.2 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1041 -699
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -185129,7 +185129,7 @@ function printComment(comment) {
|
|
|
185129
185129
|
# ` + comment.replace(/\n/g, `
|
|
185130
185130
|
# `);
|
|
185131
185131
|
}
|
|
185132
|
-
function
|
|
185132
|
+
function join10(maybeArray, separator) {
|
|
185133
185133
|
return maybeArray ? maybeArray.filter((x) => x).join(separator || "") : "";
|
|
185134
185134
|
}
|
|
185135
185135
|
function hasMultilineItems(maybeArray) {
|
|
@@ -185150,7 +185150,7 @@ function addDescription(cb) {
|
|
|
185150
185150
|
if (node.kind.includes("Definition") && commentsRegistry[key]) {
|
|
185151
185151
|
items.push(...commentsRegistry[key]);
|
|
185152
185152
|
}
|
|
185153
|
-
return
|
|
185153
|
+
return join10([...items.map(printComment), node.description, cb(node, _key, _parent, path, ancestors)], `
|
|
185154
185154
|
`);
|
|
185155
185155
|
};
|
|
185156
185156
|
}
|
|
@@ -185160,7 +185160,7 @@ function indent(maybeString) {
|
|
|
185160
185160
|
}
|
|
185161
185161
|
function block(array) {
|
|
185162
185162
|
return array && array.length !== 0 ? `{
|
|
185163
|
-
${indent(
|
|
185163
|
+
${indent(join10(array, `
|
|
185164
185164
|
`))}
|
|
185165
185165
|
}` : "";
|
|
185166
185166
|
}
|
|
@@ -185255,43 +185255,43 @@ var init_comments = __esm(() => {
|
|
|
185255
185255
|
Name: { leave: (node) => node.value },
|
|
185256
185256
|
Variable: { leave: (node) => "$" + node.name },
|
|
185257
185257
|
Document: {
|
|
185258
|
-
leave: (node) =>
|
|
185258
|
+
leave: (node) => join10(node.definitions, `
|
|
185259
185259
|
|
|
185260
185260
|
`)
|
|
185261
185261
|
},
|
|
185262
185262
|
OperationDefinition: {
|
|
185263
185263
|
leave: (node) => {
|
|
185264
|
-
const varDefs = wrap("(",
|
|
185265
|
-
const prefix =
|
|
185264
|
+
const varDefs = wrap("(", join10(node.variableDefinitions, ", "), ")");
|
|
185265
|
+
const prefix = join10([node.operation, join10([node.name, varDefs]), join10(node.directives, " ")], " ");
|
|
185266
185266
|
return prefix + " " + node.selectionSet;
|
|
185267
185267
|
}
|
|
185268
185268
|
},
|
|
185269
185269
|
VariableDefinition: {
|
|
185270
|
-
leave: ({ variable, type, defaultValue, directives }) => variable + ": " + type + wrap(" = ", defaultValue) + wrap(" ",
|
|
185270
|
+
leave: ({ variable, type, defaultValue, directives }) => variable + ": " + type + wrap(" = ", defaultValue) + wrap(" ", join10(directives, " "))
|
|
185271
185271
|
},
|
|
185272
185272
|
SelectionSet: { leave: ({ selections }) => block(selections) },
|
|
185273
185273
|
Field: {
|
|
185274
185274
|
leave({ alias, name, arguments: args, directives, selectionSet }) {
|
|
185275
185275
|
const prefix = wrap("", alias, ": ") + name;
|
|
185276
|
-
let argsLine = prefix + wrap("(",
|
|
185276
|
+
let argsLine = prefix + wrap("(", join10(args, ", "), ")");
|
|
185277
185277
|
if (argsLine.length > MAX_LINE_LENGTH) {
|
|
185278
185278
|
argsLine = prefix + wrap(`(
|
|
185279
|
-
`, indent(
|
|
185279
|
+
`, indent(join10(args, `
|
|
185280
185280
|
`)), `
|
|
185281
185281
|
)`);
|
|
185282
185282
|
}
|
|
185283
|
-
return
|
|
185283
|
+
return join10([argsLine, join10(directives, " "), selectionSet], " ");
|
|
185284
185284
|
}
|
|
185285
185285
|
},
|
|
185286
185286
|
Argument: { leave: ({ name, value }) => name + ": " + value },
|
|
185287
185287
|
FragmentSpread: {
|
|
185288
|
-
leave: ({ name, directives }) => "..." + name + wrap(" ",
|
|
185288
|
+
leave: ({ name, directives }) => "..." + name + wrap(" ", join10(directives, " "))
|
|
185289
185289
|
},
|
|
185290
185290
|
InlineFragment: {
|
|
185291
|
-
leave: ({ typeCondition, directives, selectionSet }) =>
|
|
185291
|
+
leave: ({ typeCondition, directives, selectionSet }) => join10(["...", wrap("on ", typeCondition), join10(directives, " "), selectionSet], " ")
|
|
185292
185292
|
},
|
|
185293
185293
|
FragmentDefinition: {
|
|
185294
|
-
leave: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) => `fragment ${name}${wrap("(",
|
|
185294
|
+
leave: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) => `fragment ${name}${wrap("(", join10(variableDefinitions, ", "), ")")} ` + `on ${typeCondition} ${wrap("", join10(directives, " "), " ")}` + selectionSet
|
|
185295
185295
|
},
|
|
185296
185296
|
IntValue: { leave: ({ value }) => value },
|
|
185297
185297
|
FloatValue: { leave: ({ value }) => value },
|
|
@@ -185306,101 +185306,101 @@ var init_comments = __esm(() => {
|
|
|
185306
185306
|
BooleanValue: { leave: ({ value }) => value ? "true" : "false" },
|
|
185307
185307
|
NullValue: { leave: () => "null" },
|
|
185308
185308
|
EnumValue: { leave: ({ value }) => value },
|
|
185309
|
-
ListValue: { leave: ({ values }) => "[" +
|
|
185310
|
-
ObjectValue: { leave: ({ fields }) => "{" +
|
|
185309
|
+
ListValue: { leave: ({ values }) => "[" + join10(values, ", ") + "]" },
|
|
185310
|
+
ObjectValue: { leave: ({ fields }) => "{" + join10(fields, ", ") + "}" },
|
|
185311
185311
|
ObjectField: { leave: ({ name, value }) => name + ": " + value },
|
|
185312
185312
|
Directive: {
|
|
185313
|
-
leave: ({ name, arguments: args }) => "@" + name + wrap("(",
|
|
185313
|
+
leave: ({ name, arguments: args }) => "@" + name + wrap("(", join10(args, ", "), ")")
|
|
185314
185314
|
},
|
|
185315
185315
|
NamedType: { leave: ({ name }) => name },
|
|
185316
185316
|
ListType: { leave: ({ type }) => "[" + type + "]" },
|
|
185317
185317
|
NonNullType: { leave: ({ type }) => type + "!" },
|
|
185318
185318
|
SchemaDefinition: {
|
|
185319
|
-
leave: ({ directives, operationTypes }) =>
|
|
185319
|
+
leave: ({ directives, operationTypes }) => join10(["schema", join10(directives, " "), block(operationTypes)], " ")
|
|
185320
185320
|
},
|
|
185321
185321
|
OperationTypeDefinition: {
|
|
185322
185322
|
leave: ({ operation, type }) => operation + ": " + type
|
|
185323
185323
|
},
|
|
185324
185324
|
ScalarTypeDefinition: {
|
|
185325
|
-
leave: ({ name, directives }) =>
|
|
185325
|
+
leave: ({ name, directives }) => join10(["scalar", name, join10(directives, " ")], " ")
|
|
185326
185326
|
},
|
|
185327
185327
|
ObjectTypeDefinition: {
|
|
185328
|
-
leave: ({ name, interfaces, directives, fields }) =>
|
|
185328
|
+
leave: ({ name, interfaces, directives, fields }) => join10([
|
|
185329
185329
|
"type",
|
|
185330
185330
|
name,
|
|
185331
|
-
wrap("implements ",
|
|
185332
|
-
|
|
185331
|
+
wrap("implements ", join10(interfaces, " & ")),
|
|
185332
|
+
join10(directives, " "),
|
|
185333
185333
|
block(fields)
|
|
185334
185334
|
], " ")
|
|
185335
185335
|
},
|
|
185336
185336
|
FieldDefinition: {
|
|
185337
185337
|
leave: ({ name, arguments: args, type, directives }) => name + (hasMultilineItems(args) ? wrap(`(
|
|
185338
|
-
`, indent(
|
|
185338
|
+
`, indent(join10(args, `
|
|
185339
185339
|
`)), `
|
|
185340
|
-
)`) : wrap("(",
|
|
185340
|
+
)`) : wrap("(", join10(args, ", "), ")")) + ": " + type + wrap(" ", join10(directives, " "))
|
|
185341
185341
|
},
|
|
185342
185342
|
InputValueDefinition: {
|
|
185343
|
-
leave: ({ name, type, defaultValue, directives }) =>
|
|
185343
|
+
leave: ({ name, type, defaultValue, directives }) => join10([name + ": " + type, wrap("= ", defaultValue), join10(directives, " ")], " ")
|
|
185344
185344
|
},
|
|
185345
185345
|
InterfaceTypeDefinition: {
|
|
185346
|
-
leave: ({ name, interfaces, directives, fields }) =>
|
|
185346
|
+
leave: ({ name, interfaces, directives, fields }) => join10([
|
|
185347
185347
|
"interface",
|
|
185348
185348
|
name,
|
|
185349
|
-
wrap("implements ",
|
|
185350
|
-
|
|
185349
|
+
wrap("implements ", join10(interfaces, " & ")),
|
|
185350
|
+
join10(directives, " "),
|
|
185351
185351
|
block(fields)
|
|
185352
185352
|
], " ")
|
|
185353
185353
|
},
|
|
185354
185354
|
UnionTypeDefinition: {
|
|
185355
|
-
leave: ({ name, directives, types }) =>
|
|
185355
|
+
leave: ({ name, directives, types }) => join10(["union", name, join10(directives, " "), wrap("= ", join10(types, " | "))], " ")
|
|
185356
185356
|
},
|
|
185357
185357
|
EnumTypeDefinition: {
|
|
185358
|
-
leave: ({ name, directives, values }) =>
|
|
185358
|
+
leave: ({ name, directives, values }) => join10(["enum", name, join10(directives, " "), block(values)], " ")
|
|
185359
185359
|
},
|
|
185360
185360
|
EnumValueDefinition: {
|
|
185361
|
-
leave: ({ name, directives }) =>
|
|
185361
|
+
leave: ({ name, directives }) => join10([name, join10(directives, " ")], " ")
|
|
185362
185362
|
},
|
|
185363
185363
|
InputObjectTypeDefinition: {
|
|
185364
|
-
leave: ({ name, directives, fields }) =>
|
|
185364
|
+
leave: ({ name, directives, fields }) => join10(["input", name, join10(directives, " "), block(fields)], " ")
|
|
185365
185365
|
},
|
|
185366
185366
|
DirectiveDefinition: {
|
|
185367
185367
|
leave: ({ name, arguments: args, repeatable, locations }) => "directive @" + name + (hasMultilineItems(args) ? wrap(`(
|
|
185368
|
-
`, indent(
|
|
185368
|
+
`, indent(join10(args, `
|
|
185369
185369
|
`)), `
|
|
185370
|
-
)`) : wrap("(",
|
|
185370
|
+
)`) : wrap("(", join10(args, ", "), ")")) + (repeatable ? " repeatable" : "") + " on " + join10(locations, " | ")
|
|
185371
185371
|
},
|
|
185372
185372
|
SchemaExtension: {
|
|
185373
|
-
leave: ({ directives, operationTypes }) =>
|
|
185373
|
+
leave: ({ directives, operationTypes }) => join10(["extend schema", join10(directives, " "), block(operationTypes)], " ")
|
|
185374
185374
|
},
|
|
185375
185375
|
ScalarTypeExtension: {
|
|
185376
|
-
leave: ({ name, directives }) =>
|
|
185376
|
+
leave: ({ name, directives }) => join10(["extend scalar", name, join10(directives, " ")], " ")
|
|
185377
185377
|
},
|
|
185378
185378
|
ObjectTypeExtension: {
|
|
185379
|
-
leave: ({ name, interfaces, directives, fields }) =>
|
|
185379
|
+
leave: ({ name, interfaces, directives, fields }) => join10([
|
|
185380
185380
|
"extend type",
|
|
185381
185381
|
name,
|
|
185382
|
-
wrap("implements ",
|
|
185383
|
-
|
|
185382
|
+
wrap("implements ", join10(interfaces, " & ")),
|
|
185383
|
+
join10(directives, " "),
|
|
185384
185384
|
block(fields)
|
|
185385
185385
|
], " ")
|
|
185386
185386
|
},
|
|
185387
185387
|
InterfaceTypeExtension: {
|
|
185388
|
-
leave: ({ name, interfaces, directives, fields }) =>
|
|
185388
|
+
leave: ({ name, interfaces, directives, fields }) => join10([
|
|
185389
185389
|
"extend interface",
|
|
185390
185390
|
name,
|
|
185391
|
-
wrap("implements ",
|
|
185392
|
-
|
|
185391
|
+
wrap("implements ", join10(interfaces, " & ")),
|
|
185392
|
+
join10(directives, " "),
|
|
185393
185393
|
block(fields)
|
|
185394
185394
|
], " ")
|
|
185395
185395
|
},
|
|
185396
185396
|
UnionTypeExtension: {
|
|
185397
|
-
leave: ({ name, directives, types }) =>
|
|
185397
|
+
leave: ({ name, directives, types }) => join10(["extend union", name, join10(directives, " "), wrap("= ", join10(types, " | "))], " ")
|
|
185398
185398
|
},
|
|
185399
185399
|
EnumTypeExtension: {
|
|
185400
|
-
leave: ({ name, directives, values }) =>
|
|
185400
|
+
leave: ({ name, directives, values }) => join10(["extend enum", name, join10(directives, " "), block(values)], " ")
|
|
185401
185401
|
},
|
|
185402
185402
|
InputObjectTypeExtension: {
|
|
185403
|
-
leave: ({ name, directives, fields }) =>
|
|
185403
|
+
leave: ({ name, directives, fields }) => join10(["extend input", name, join10(directives, " "), block(fields)], " ")
|
|
185404
185404
|
}
|
|
185405
185405
|
};
|
|
185406
185406
|
printDocASTReducerWithComments = Object.keys(printDocASTReducer).reduce((prev, key) => ({
|
|
@@ -188960,7 +188960,7 @@ var require_parse_filepath = __commonJS(function(exports, module) {
|
|
|
188960
188960
|
});
|
|
188961
188961
|
|
|
188962
188962
|
// node_modules/@graphql-codegen/visitor-plugin-common/esm/imports.js
|
|
188963
|
-
import { dirname as dirname5, isAbsolute as isAbsolute4, join as
|
|
188963
|
+
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative5, resolve as resolve13 } from "path";
|
|
188964
188964
|
function generateFragmentImportStatement(statement, kind) {
|
|
188965
188965
|
const { importSource: fragmentImportSource, ...rest } = statement;
|
|
188966
188966
|
const { identifiers, path, namespace } = fragmentImportSource;
|
|
@@ -188994,8 +188994,8 @@ function generateImportStatement(statement) {
|
|
|
188994
188994
|
function resolveImportPath(baseDir, outputPath, sourcePath) {
|
|
188995
188995
|
const shouldAbsolute = !sourcePath.startsWith("~");
|
|
188996
188996
|
if (shouldAbsolute) {
|
|
188997
|
-
const absGeneratedFilePath =
|
|
188998
|
-
const absImportFilePath =
|
|
188997
|
+
const absGeneratedFilePath = resolve13(baseDir, outputPath);
|
|
188998
|
+
const absImportFilePath = resolve13(baseDir, sourcePath);
|
|
188999
188999
|
return resolveRelativeImport(absGeneratedFilePath, absImportFilePath);
|
|
189000
189000
|
}
|
|
189001
189001
|
return sourcePath.replace(`~`, "");
|
|
@@ -189011,7 +189011,7 @@ function resolveRelativeImport(from, to) {
|
|
|
189011
189011
|
}
|
|
189012
189012
|
function clearExtension(path) {
|
|
189013
189013
|
const parsedPath = import_parse_filepath.default(path);
|
|
189014
|
-
return
|
|
189014
|
+
return join11(parsedPath.dir, parsedPath.name).replace(/\\/g, "/");
|
|
189015
189015
|
}
|
|
189016
189016
|
function fixLocalFilePath(path) {
|
|
189017
189017
|
return path.startsWith("..") ? path : `./${path}`;
|
|
@@ -190212,7 +190212,7 @@ var init_esm4 = __esm(() => {
|
|
|
190212
190212
|
});
|
|
190213
190213
|
|
|
190214
190214
|
// node_modules/@graphql-codegen/visitor-plugin-common/esm/client-side-base-visitor.js
|
|
190215
|
-
import { basename as
|
|
190215
|
+
import { basename as basename8, extname } from "path";
|
|
190216
190216
|
var import_dependency_graph, import_graphql54, import_graphql_tag, DocumentMode, EXTENSIONS_TO_REMOVE, ClientSideBaseVisitor;
|
|
190217
190217
|
var init_client_side_base_visitor = __esm(() => {
|
|
190218
190218
|
init_dist();
|
|
@@ -190534,7 +190534,7 @@ var init_client_side_base_visitor = __esm(() => {
|
|
|
190534
190534
|
case DocumentMode.external: {
|
|
190535
190535
|
if (this._collectedOperations.length > 0) {
|
|
190536
190536
|
if (this.config.importDocumentNodeExternallyFrom === "near-operation-file" && this._documents.length === 1) {
|
|
190537
|
-
let documentPath = `./${this.clearExtension(
|
|
190537
|
+
let documentPath = `./${this.clearExtension(basename8(this._documents[0].location || ""))}`;
|
|
190538
190538
|
documentPath += normalizeImportExtension({
|
|
190539
190539
|
emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
|
|
190540
190540
|
importExtension: this.config.importExtension
|
|
@@ -207626,7 +207626,7 @@ var ACTION_POLICY = {
|
|
|
207626
207626
|
},
|
|
207627
207627
|
database: {
|
|
207628
207628
|
read: ["list_tables", "describe_columns", "list_indexes", "list_constraints", "list_extensions", "extension_catalog", "rls_status", "rls_policies", "list_auth_users", "get_auth_user", "connections", "stats", "slow_queries", "list_migrations", "migration_inventory", "migration_import", "delivery_migration_plan", "project_url", "generate_types", "database_lint", "db_lint", "rpc_catalog", "list_rpcs"],
|
|
207629
|
-
local: ["lint_migrations", "lint"],
|
|
207629
|
+
local: ["lint_migrations", "lint", "rebase_migrations"],
|
|
207630
207630
|
write: ["query", "execute", "apply_migration", "push_migrations", "baseline_migrations", "create_table_rls", "enable_extension", "disable_extension"]
|
|
207631
207631
|
},
|
|
207632
207632
|
supabase: {
|
|
@@ -208205,7 +208205,7 @@ class HttpTransport {
|
|
|
208205
208205
|
// src/shared/tools/database-tools.ts
|
|
208206
208206
|
import { createHash as createHash3 } from "node:crypto";
|
|
208207
208207
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
208208
|
-
import { basename as
|
|
208208
|
+
import { basename as basename4, join as join5 } from "node:path";
|
|
208209
208209
|
|
|
208210
208210
|
// node_modules/@supacloud/delivery/src/delivery-artifact.ts
|
|
208211
208211
|
import { readdir as readdir2, realpath as realpath2 } from "node:fs/promises";
|
|
@@ -209054,6 +209054,748 @@ async function readDeliveryExecutableArchive(manifestPath) {
|
|
|
209054
209054
|
return { manifest, objects };
|
|
209055
209055
|
}
|
|
209056
209056
|
|
|
209057
|
+
// node_modules/@supacloud/db/dist/index.js
|
|
209058
|
+
import { createHash as createHash22 } from "node:crypto";
|
|
209059
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
209060
|
+
import { basename as basename2, join as join3, resolve as resolve5 } from "node:path";
|
|
209061
|
+
var TABLES_SQL = `
|
|
209062
|
+
SELECT n.nspname AS schema,
|
|
209063
|
+
c.relname AS name,
|
|
209064
|
+
c.relrowsecurity AS rls_enabled,
|
|
209065
|
+
c.relforcerowsecurity AS rls_forced
|
|
209066
|
+
FROM pg_class c
|
|
209067
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
209068
|
+
WHERE c.relkind = 'r'
|
|
209069
|
+
AND n.nspname = ANY($1)
|
|
209070
|
+
ORDER BY n.nspname, c.relname
|
|
209071
|
+
`;
|
|
209072
|
+
var POLICIES_SQL = `
|
|
209073
|
+
SELECT n.nspname AS schema,
|
|
209074
|
+
c.relname AS table,
|
|
209075
|
+
p.polname AS name,
|
|
209076
|
+
p.polcmd AS command,
|
|
209077
|
+
ARRAY(
|
|
209078
|
+
SELECT CASE WHEN r = 0 THEN 'PUBLIC' ELSE pg_get_userbyid(r) END
|
|
209079
|
+
FROM unnest(p.polroles) AS r
|
|
209080
|
+
) AS roles,
|
|
209081
|
+
pg_get_expr(p.polqual, p.polrelid) AS using_expr,
|
|
209082
|
+
pg_get_expr(p.polwithcheck, p.polrelid) AS check_expr
|
|
209083
|
+
FROM pg_policy p
|
|
209084
|
+
JOIN pg_class c ON c.oid = p.polrelid
|
|
209085
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
209086
|
+
WHERE n.nspname = ANY($1)
|
|
209087
|
+
ORDER BY n.nspname, c.relname, p.polname
|
|
209088
|
+
`;
|
|
209089
|
+
var FUNCTIONS_SQL = `
|
|
209090
|
+
SELECT n.nspname AS schema,
|
|
209091
|
+
p.proname AS name,
|
|
209092
|
+
p.prosecdef AS security_definer,
|
|
209093
|
+
p.proconfig AS config,
|
|
209094
|
+
l.lanname AS language
|
|
209095
|
+
FROM pg_proc p
|
|
209096
|
+
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
209097
|
+
JOIN pg_language l ON l.oid = p.prolang
|
|
209098
|
+
WHERE n.nspname = ANY($1)
|
|
209099
|
+
ORDER BY n.nspname, p.proname
|
|
209100
|
+
`;
|
|
209101
|
+
var TRIGGERS_SQL = `
|
|
209102
|
+
SELECT n.nspname AS schema,
|
|
209103
|
+
c.relname AS table,
|
|
209104
|
+
t.tgname AS name,
|
|
209105
|
+
t.tgenabled <> 'D' AS enabled
|
|
209106
|
+
FROM pg_trigger t
|
|
209107
|
+
JOIN pg_class c ON c.oid = t.tgrelid
|
|
209108
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
209109
|
+
WHERE NOT t.tgisinternal
|
|
209110
|
+
AND n.nspname = ANY($1)
|
|
209111
|
+
ORDER BY n.nspname, c.relname, t.tgname
|
|
209112
|
+
`;
|
|
209113
|
+
var GRANTS_SQL = `
|
|
209114
|
+
SELECT table_schema AS object_schema,
|
|
209115
|
+
table_name AS object_name,
|
|
209116
|
+
privilege_type AS privilege,
|
|
209117
|
+
grantee
|
|
209118
|
+
FROM information_schema.role_table_grants
|
|
209119
|
+
WHERE table_schema = ANY($1)
|
|
209120
|
+
UNION ALL
|
|
209121
|
+
SELECT routine_schema AS object_schema,
|
|
209122
|
+
routine_name AS object_name,
|
|
209123
|
+
privilege_type AS privilege,
|
|
209124
|
+
grantee
|
|
209125
|
+
FROM information_schema.routine_privileges
|
|
209126
|
+
WHERE routine_schema = ANY($1)
|
|
209127
|
+
`;
|
|
209128
|
+
var POLCMD_MAP = {
|
|
209129
|
+
r: "select",
|
|
209130
|
+
a: "insert",
|
|
209131
|
+
w: "update",
|
|
209132
|
+
d: "delete",
|
|
209133
|
+
"*": "all"
|
|
209134
|
+
};
|
|
209135
|
+
function extractSearchPath(config) {
|
|
209136
|
+
if (!config)
|
|
209137
|
+
return null;
|
|
209138
|
+
const entry = config.find((item) => item.startsWith("search_path="));
|
|
209139
|
+
if (!entry)
|
|
209140
|
+
return null;
|
|
209141
|
+
return entry.slice("search_path=".length);
|
|
209142
|
+
}
|
|
209143
|
+
async function readCatalog(executor, schemas = ["public"]) {
|
|
209144
|
+
const params = [schemas];
|
|
209145
|
+
const [tableRows, policyRows, functionRows, triggerRows, grantRows] = await Promise.all([
|
|
209146
|
+
executor.query(TABLES_SQL, params),
|
|
209147
|
+
executor.query(POLICIES_SQL, params),
|
|
209148
|
+
executor.query(FUNCTIONS_SQL, params),
|
|
209149
|
+
executor.query(TRIGGERS_SQL, params),
|
|
209150
|
+
executor.query(GRANTS_SQL, params)
|
|
209151
|
+
]);
|
|
209152
|
+
return {
|
|
209153
|
+
tables: tableRows.map((row) => ({
|
|
209154
|
+
schema: row.schema,
|
|
209155
|
+
name: row.name,
|
|
209156
|
+
rlsEnabled: row.rls_enabled,
|
|
209157
|
+
rlsForced: row.rls_forced
|
|
209158
|
+
})),
|
|
209159
|
+
policies: policyRows.map((row) => ({
|
|
209160
|
+
schema: row.schema,
|
|
209161
|
+
table: row.table,
|
|
209162
|
+
name: row.name,
|
|
209163
|
+
command: POLCMD_MAP[row.command] ?? "all",
|
|
209164
|
+
roles: row.roles ?? [],
|
|
209165
|
+
usingExpr: row.using_expr ?? undefined,
|
|
209166
|
+
checkExpr: row.check_expr ?? undefined
|
|
209167
|
+
})),
|
|
209168
|
+
functions: functionRows.map((row) => ({
|
|
209169
|
+
schema: row.schema,
|
|
209170
|
+
name: row.name,
|
|
209171
|
+
security: row.security_definer ? "definer" : "invoker",
|
|
209172
|
+
searchPath: extractSearchPath(row.config),
|
|
209173
|
+
language: row.language
|
|
209174
|
+
})),
|
|
209175
|
+
triggers: triggerRows.map((row) => ({
|
|
209176
|
+
schema: row.schema,
|
|
209177
|
+
table: row.table,
|
|
209178
|
+
name: row.name,
|
|
209179
|
+
enabled: row.enabled
|
|
209180
|
+
})),
|
|
209181
|
+
grants: grantRows.map((row) => ({
|
|
209182
|
+
objectSchema: row.object_schema,
|
|
209183
|
+
objectName: row.object_name,
|
|
209184
|
+
privilege: row.privilege,
|
|
209185
|
+
grantee: row.grantee
|
|
209186
|
+
}))
|
|
209187
|
+
};
|
|
209188
|
+
}
|
|
209189
|
+
function splitQualifiedName(name) {
|
|
209190
|
+
const dot = name.indexOf(".");
|
|
209191
|
+
if (dot === -1)
|
|
209192
|
+
return ["public", name];
|
|
209193
|
+
return [name.slice(0, dot), name.slice(dot + 1)];
|
|
209194
|
+
}
|
|
209195
|
+
function isFixedSearchPath(searchPath) {
|
|
209196
|
+
if (searchPath === null)
|
|
209197
|
+
return false;
|
|
209198
|
+
const parts = searchPath.split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
209199
|
+
return parts.every((part) => part !== "" && part.toLowerCase() !== "pg_temp");
|
|
209200
|
+
}
|
|
209201
|
+
function reconcileModule(module, catalog) {
|
|
209202
|
+
const issues = [];
|
|
209203
|
+
const ownedTables = new Set(module.tables);
|
|
209204
|
+
const push = (severity, code, object, message) => issues.push({ severity, code, object, message });
|
|
209205
|
+
for (const policy of module.policies) {
|
|
209206
|
+
const [schema, table] = splitQualifiedName(policy.table);
|
|
209207
|
+
const found = catalog.policies.some((cp) => cp.schema === schema && cp.table === table && cp.name === policy.name);
|
|
209208
|
+
if (!found) {
|
|
209209
|
+
push("error", "missing-policy", `${policy.table}.${policy.name}`, `声明的策略 ${policy.name} 在表 ${policy.table} 的 catalog 中不存在`);
|
|
209210
|
+
}
|
|
209211
|
+
}
|
|
209212
|
+
const declaredPolicyKeys = new Set(module.policies.map((p) => `${p.table}::${p.name}`));
|
|
209213
|
+
for (const cp of catalog.policies) {
|
|
209214
|
+
const qualified = `${cp.schema}.${cp.table}`;
|
|
209215
|
+
if (ownedTables.has(qualified) && !declaredPolicyKeys.has(`${qualified}::${cp.name}`)) {
|
|
209216
|
+
push("warn", "undeclared-policy", `${qualified}.${cp.name}`, `归属表 ${qualified} 上存在未声明的策略 ${cp.name},可能发生漂移`);
|
|
209217
|
+
}
|
|
209218
|
+
}
|
|
209219
|
+
for (const fn of module.functions) {
|
|
209220
|
+
const [schema, name] = splitQualifiedName(fn.name);
|
|
209221
|
+
const cf = catalog.functions.find((f) => f.schema === schema && f.name === name);
|
|
209222
|
+
if (!cf) {
|
|
209223
|
+
push("error", "missing-function", fn.name, `声明的函数 ${fn.name} 在 catalog 中不存在`);
|
|
209224
|
+
continue;
|
|
209225
|
+
}
|
|
209226
|
+
if (cf.security !== fn.security) {
|
|
209227
|
+
push("warn", "security-mismatch", fn.name, `函数 ${fn.name} 声明为 security ${fn.security},catalog 实际为 ${cf.security}`);
|
|
209228
|
+
}
|
|
209229
|
+
const effectiveDefiner = fn.security === "definer" || cf.security === "definer";
|
|
209230
|
+
if (effectiveDefiner && !isFixedSearchPath(cf.searchPath)) {
|
|
209231
|
+
push("error", "definer-without-search-path", fn.name, `security definer 函数 ${fn.name} 未设置固定 search_path(当前: ${cf.searchPath ?? "未设置"})`);
|
|
209232
|
+
}
|
|
209233
|
+
}
|
|
209234
|
+
for (const trigger of module.triggers) {
|
|
209235
|
+
const [schema, table] = splitQualifiedName(trigger.table);
|
|
209236
|
+
const found = catalog.triggers.some((ct) => ct.schema === schema && ct.table === table && ct.name === trigger.name);
|
|
209237
|
+
if (!found) {
|
|
209238
|
+
push("error", "missing-trigger", `${trigger.table}.${trigger.name}`, `声明的触发器 ${trigger.name} 在表 ${trigger.table} 的 catalog 中不存在`);
|
|
209239
|
+
}
|
|
209240
|
+
}
|
|
209241
|
+
const declaredTriggerKeys = new Set(module.triggers.map((t) => `${t.table}::${t.name}`));
|
|
209242
|
+
for (const ct of catalog.triggers) {
|
|
209243
|
+
const qualified = `${ct.schema}.${ct.table}`;
|
|
209244
|
+
if (ownedTables.has(qualified) && !declaredTriggerKeys.has(`${qualified}::${ct.name}`)) {
|
|
209245
|
+
push("warn", "undeclared-trigger", `${qualified}.${ct.name}`, `归属表 ${qualified} 上存在未声明的触发器 ${ct.name}${ct.enabled ? "" : "(已禁用)"},可能发生漂移`);
|
|
209246
|
+
}
|
|
209247
|
+
}
|
|
209248
|
+
for (const table of module.tables) {
|
|
209249
|
+
const [schema, name] = splitQualifiedName(table);
|
|
209250
|
+
const ct = catalog.tables.find((t) => t.schema === schema && t.name === name);
|
|
209251
|
+
if (ct && !ct.rlsEnabled) {
|
|
209252
|
+
push("error", "rls-disabled", table, `归属表 ${table} 未开启行级安全(relrowsecurity = false)`);
|
|
209253
|
+
}
|
|
209254
|
+
}
|
|
209255
|
+
for (const grant of catalog.grants) {
|
|
209256
|
+
const qualified = `${grant.objectSchema}.${grant.objectName}`;
|
|
209257
|
+
if (ownedTables.has(qualified) && grant.grantee.toUpperCase() === "PUBLIC") {
|
|
209258
|
+
push("error", "wildcard-grant", qualified, `归属表 ${qualified} 存在授予 PUBLIC 的 ${grant.privilege} 权限`);
|
|
209259
|
+
}
|
|
209260
|
+
}
|
|
209261
|
+
for (const grant of module.grants) {
|
|
209262
|
+
const [schema, name] = splitQualifiedName(grant.object);
|
|
209263
|
+
const found = catalog.grants.some((cg) => cg.objectSchema === schema && cg.objectName === name && cg.privilege.toLowerCase() === grant.privilege.toLowerCase() && cg.grantee.toLowerCase() === grant.role.toLowerCase());
|
|
209264
|
+
if (!found) {
|
|
209265
|
+
push("warn", "grant-drift", grant.object, `声明的授权 ${grant.privilege} ON ${grant.object} TO ${grant.role} 在 catalog 中不存在`);
|
|
209266
|
+
}
|
|
209267
|
+
}
|
|
209268
|
+
return {
|
|
209269
|
+
module: module.name,
|
|
209270
|
+
issues,
|
|
209271
|
+
ok: !issues.some((issue) => issue.severity === "error")
|
|
209272
|
+
};
|
|
209273
|
+
}
|
|
209274
|
+
var SECURITY_DEFINER_RE = /\bsecurity\s+definer\b/i;
|
|
209275
|
+
var SET_SEARCH_PATH_RE = /\bset\s+search_path\b/i;
|
|
209276
|
+
var GRANT_TO_PUBLIC_RE = /\bgrant\b[^;]*\bto\s+public\b/i;
|
|
209277
|
+
var DROP_WITHOUT_IF_EXISTS_RE = /\bdrop\s+(?:table|column)\s+(?!if\s+exists\b)/i;
|
|
209278
|
+
var ENABLE_RLS_RE = /\benable\s+row\s+level\s+security\b/i;
|
|
209279
|
+
var CREATE_POLICY_RE = /\bcreate\s+policy\b/i;
|
|
209280
|
+
var DROP_POLICY_IF_EXISTS_RE = /\bdrop\s+policy\s+if\s+exists\b/i;
|
|
209281
|
+
function lineOf(sql, index) {
|
|
209282
|
+
let line = 1;
|
|
209283
|
+
for (let i = 0;i < index; i += 1) {
|
|
209284
|
+
if (sql.charCodeAt(i) === 10)
|
|
209285
|
+
line += 1;
|
|
209286
|
+
}
|
|
209287
|
+
return line;
|
|
209288
|
+
}
|
|
209289
|
+
function lintSql(sql, file) {
|
|
209290
|
+
const issues = [];
|
|
209291
|
+
const definer = SECURITY_DEFINER_RE.exec(sql);
|
|
209292
|
+
if (definer && !SET_SEARCH_PATH_RE.test(sql)) {
|
|
209293
|
+
issues.push({
|
|
209294
|
+
severity: "error",
|
|
209295
|
+
code: "definer-no-search-path",
|
|
209296
|
+
message: "security definer 函数必须显式 set search_path,避免 search_path 劫持",
|
|
209297
|
+
file,
|
|
209298
|
+
line: lineOf(sql, definer.index)
|
|
209299
|
+
});
|
|
209300
|
+
}
|
|
209301
|
+
const grantPublic = GRANT_TO_PUBLIC_RE.exec(sql);
|
|
209302
|
+
if (grantPublic) {
|
|
209303
|
+
issues.push({
|
|
209304
|
+
severity: "error",
|
|
209305
|
+
code: "grant-to-public",
|
|
209306
|
+
message: "禁止将权限授予 PUBLIC 角色",
|
|
209307
|
+
file,
|
|
209308
|
+
line: lineOf(sql, grantPublic.index)
|
|
209309
|
+
});
|
|
209310
|
+
}
|
|
209311
|
+
const drop = DROP_WITHOUT_IF_EXISTS_RE.exec(sql);
|
|
209312
|
+
if (drop) {
|
|
209313
|
+
issues.push({
|
|
209314
|
+
severity: "warn",
|
|
209315
|
+
code: "drop-without-if-exists",
|
|
209316
|
+
message: "drop table/column 建议使用 if exists,保证迁移可重入",
|
|
209317
|
+
file,
|
|
209318
|
+
line: lineOf(sql, drop.index)
|
|
209319
|
+
});
|
|
209320
|
+
}
|
|
209321
|
+
const createPolicy = CREATE_POLICY_RE.exec(sql);
|
|
209322
|
+
if (createPolicy) {
|
|
209323
|
+
const dropPolicy = DROP_POLICY_IF_EXISTS_RE.exec(sql);
|
|
209324
|
+
if (!dropPolicy || dropPolicy.index > createPolicy.index) {
|
|
209325
|
+
issues.push({
|
|
209326
|
+
severity: "warn",
|
|
209327
|
+
code: "non-idempotent-policy",
|
|
209328
|
+
message: "create policy 前缺少 drop policy if exists,策略不可重复执行",
|
|
209329
|
+
file,
|
|
209330
|
+
line: lineOf(sql, createPolicy.index)
|
|
209331
|
+
});
|
|
209332
|
+
}
|
|
209333
|
+
}
|
|
209334
|
+
return issues;
|
|
209335
|
+
}
|
|
209336
|
+
async function lintModule(module, readFile) {
|
|
209337
|
+
const issues = [];
|
|
209338
|
+
const sources = new Set;
|
|
209339
|
+
for (const decl of [
|
|
209340
|
+
...module.policies,
|
|
209341
|
+
...module.functions,
|
|
209342
|
+
...module.triggers,
|
|
209343
|
+
...module.grants
|
|
209344
|
+
]) {
|
|
209345
|
+
sources.add(decl.source);
|
|
209346
|
+
}
|
|
209347
|
+
const contents = new Map;
|
|
209348
|
+
await Promise.all([...sources].map(async (path) => {
|
|
209349
|
+
contents.set(path, await readFile(path));
|
|
209350
|
+
}));
|
|
209351
|
+
for (const [file, sql] of contents) {
|
|
209352
|
+
issues.push(...lintSql(sql, file));
|
|
209353
|
+
}
|
|
209354
|
+
if (module.policies.length > 0) {
|
|
209355
|
+
const anyEnable = module.policies.some((policy) => ENABLE_RLS_RE.test(contents.get(policy.source) ?? ""));
|
|
209356
|
+
if (!anyEnable) {
|
|
209357
|
+
issues.push({
|
|
209358
|
+
severity: "warn",
|
|
209359
|
+
code: "missing-rls-enable",
|
|
209360
|
+
message: `模块 ${module.name} 声明了 ${module.policies.length} 条策略,但所有策略源文件都没有 enable row level security`,
|
|
209361
|
+
file: module.policies[0].source
|
|
209362
|
+
});
|
|
209363
|
+
}
|
|
209364
|
+
}
|
|
209365
|
+
for (const policy of module.policies) {
|
|
209366
|
+
if (!policy.tests || policy.tests.length === 0) {
|
|
209367
|
+
issues.push({
|
|
209368
|
+
severity: "warn",
|
|
209369
|
+
code: "policy-without-test",
|
|
209370
|
+
message: `策略 ${policy.name} 未声明测试文件`,
|
|
209371
|
+
file: policy.source
|
|
209372
|
+
});
|
|
209373
|
+
}
|
|
209374
|
+
}
|
|
209375
|
+
for (const fn of module.functions) {
|
|
209376
|
+
if (!fn.tests || fn.tests.length === 0) {
|
|
209377
|
+
issues.push({
|
|
209378
|
+
severity: "warn",
|
|
209379
|
+
code: "policy-without-test",
|
|
209380
|
+
message: `函数 ${fn.name} 未声明测试文件`,
|
|
209381
|
+
file: fn.source
|
|
209382
|
+
});
|
|
209383
|
+
}
|
|
209384
|
+
}
|
|
209385
|
+
return issues;
|
|
209386
|
+
}
|
|
209387
|
+
function buildDatabaseManifest(modules) {
|
|
209388
|
+
return {
|
|
209389
|
+
version: 1,
|
|
209390
|
+
modules: modules.map((module) => ({
|
|
209391
|
+
name: module.name,
|
|
209392
|
+
tables: module.tables,
|
|
209393
|
+
policies: module.policies,
|
|
209394
|
+
functions: module.functions,
|
|
209395
|
+
triggers: module.triggers,
|
|
209396
|
+
grants: module.grants
|
|
209397
|
+
}))
|
|
209398
|
+
};
|
|
209399
|
+
}
|
|
209400
|
+
function describePolicy(moduleName, policy) {
|
|
209401
|
+
const lines = [
|
|
209402
|
+
`对象: ${policy.table}.${policy.name}`,
|
|
209403
|
+
"类型: 策略 (policy)",
|
|
209404
|
+
`所属模块: ${moduleName}`,
|
|
209405
|
+
`表: ${policy.table}`,
|
|
209406
|
+
`操作: ${policy.operation}`,
|
|
209407
|
+
`角色: ${policy.roles.join(", ")}`,
|
|
209408
|
+
`源文件: ${policy.source}`
|
|
209409
|
+
];
|
|
209410
|
+
if (policy.tests && policy.tests.length > 0)
|
|
209411
|
+
lines.push(`测试: ${policy.tests.join(", ")}`);
|
|
209412
|
+
return lines.join(`
|
|
209413
|
+
`);
|
|
209414
|
+
}
|
|
209415
|
+
function describeFunction(moduleName, fn) {
|
|
209416
|
+
const lines = [
|
|
209417
|
+
`对象: ${fn.name}`,
|
|
209418
|
+
"类型: 函数 (function)",
|
|
209419
|
+
`所属模块: ${moduleName}`,
|
|
209420
|
+
`源文件: ${fn.source}`,
|
|
209421
|
+
`安全模式: ${fn.security}`
|
|
209422
|
+
];
|
|
209423
|
+
if (fn.permission)
|
|
209424
|
+
lines.push(`权限: ${fn.permission}`);
|
|
209425
|
+
if (fn.transaction)
|
|
209426
|
+
lines.push(`事务: ${fn.transaction}`);
|
|
209427
|
+
if (fn.audit)
|
|
209428
|
+
lines.push(`审计: ${fn.audit}`);
|
|
209429
|
+
if (fn.idempotency)
|
|
209430
|
+
lines.push(`幂等: ${fn.idempotency}`);
|
|
209431
|
+
if (fn.tests && fn.tests.length > 0)
|
|
209432
|
+
lines.push(`测试: ${fn.tests.join(", ")}`);
|
|
209433
|
+
return lines.join(`
|
|
209434
|
+
`);
|
|
209435
|
+
}
|
|
209436
|
+
function describeTrigger(moduleName, trigger) {
|
|
209437
|
+
return [
|
|
209438
|
+
`对象: ${trigger.name}`,
|
|
209439
|
+
"类型: 触发器 (trigger)",
|
|
209440
|
+
`所属模块: ${moduleName}`,
|
|
209441
|
+
`表: ${trigger.table}`,
|
|
209442
|
+
`源文件: ${trigger.source}`
|
|
209443
|
+
].join(`
|
|
209444
|
+
`);
|
|
209445
|
+
}
|
|
209446
|
+
function describeGrant(moduleName, grant) {
|
|
209447
|
+
return [
|
|
209448
|
+
`对象: ${grant.object}`,
|
|
209449
|
+
"类型: 授权 (grant)",
|
|
209450
|
+
`所属模块: ${moduleName}`,
|
|
209451
|
+
`权限: ${grant.privilege}`,
|
|
209452
|
+
`角色: ${grant.role}`,
|
|
209453
|
+
`源文件: ${grant.source}`
|
|
209454
|
+
].join(`
|
|
209455
|
+
`);
|
|
209456
|
+
}
|
|
209457
|
+
function explainObject(manifest, name) {
|
|
209458
|
+
for (const module of manifest.modules) {
|
|
209459
|
+
for (const policy of module.policies) {
|
|
209460
|
+
if (policy.name === name || `${policy.table}.${policy.name}` === name) {
|
|
209461
|
+
return describePolicy(module.name, policy);
|
|
209462
|
+
}
|
|
209463
|
+
}
|
|
209464
|
+
for (const fn of module.functions) {
|
|
209465
|
+
if (fn.name === name)
|
|
209466
|
+
return describeFunction(module.name, fn);
|
|
209467
|
+
}
|
|
209468
|
+
for (const trigger of module.triggers) {
|
|
209469
|
+
if (trigger.name === name || `${trigger.table}.${trigger.name}` === name) {
|
|
209470
|
+
return describeTrigger(module.name, trigger);
|
|
209471
|
+
}
|
|
209472
|
+
}
|
|
209473
|
+
for (const table of module.tables) {
|
|
209474
|
+
if (table === name) {
|
|
209475
|
+
return [
|
|
209476
|
+
`对象: ${table}`,
|
|
209477
|
+
"类型: 表 (table)",
|
|
209478
|
+
`所属模块: ${module.name}`
|
|
209479
|
+
].join(`
|
|
209480
|
+
`);
|
|
209481
|
+
}
|
|
209482
|
+
}
|
|
209483
|
+
for (const grant of module.grants) {
|
|
209484
|
+
if (grant.object === name)
|
|
209485
|
+
return describeGrant(module.name, grant);
|
|
209486
|
+
}
|
|
209487
|
+
}
|
|
209488
|
+
return `未找到对象: ${name}`;
|
|
209489
|
+
}
|
|
209490
|
+
var COMMAND_PERSISTENCE_SQL = `
|
|
209491
|
+
DO $runtime$
|
|
209492
|
+
BEGIN
|
|
209493
|
+
IF to_regprocedure('supacloud_workflows.start_run(uuid,text,text,text,jsonb,integer)') IS NULL
|
|
209494
|
+
OR to_regprocedure('supacloud_commands.status(jsonb)') IS NULL THEN
|
|
209495
|
+
RAISE EXCEPTION 'Install the SupaCloud Commands/Workflow/PGMQ runtime before command persistence';
|
|
209496
|
+
END IF;
|
|
209497
|
+
END
|
|
209498
|
+
$runtime$;
|
|
209499
|
+
CREATE SCHEMA IF NOT EXISTS supacloud_commands;
|
|
209500
|
+
REVOKE ALL ON SCHEMA supacloud_commands FROM PUBLIC;
|
|
209501
|
+
CREATE TABLE IF NOT EXISTS supacloud_commands.execution_receipts (
|
|
209502
|
+
tenant_id text NOT NULL,
|
|
209503
|
+
actor_id text NOT NULL,
|
|
209504
|
+
command text NOT NULL,
|
|
209505
|
+
operation_key text NOT NULL,
|
|
209506
|
+
kind text NOT NULL CHECK (kind IN ('transactional', 'external')),
|
|
209507
|
+
input_fingerprint text NOT NULL CHECK (input_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
209508
|
+
input_payload text,
|
|
209509
|
+
dispatch_key uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE,
|
|
209510
|
+
status text NOT NULL CHECK (status IN ('pending', 'unknown', 'confirmed', 'rejected')),
|
|
209511
|
+
audit_state text NOT NULL CHECK (audit_state IN ('pending', 'complete')),
|
|
209512
|
+
result jsonb,
|
|
209513
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
209514
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
209515
|
+
PRIMARY KEY (tenant_id, actor_id, command, operation_key),
|
|
209516
|
+
CHECK ((status = 'confirmed') = (result IS NOT NULL)),
|
|
209517
|
+
CONSTRAINT execution_receipts_audit_terminal_check CHECK (
|
|
209518
|
+
(audit_state <> 'complete' OR status IN ('confirmed', 'rejected'))
|
|
209519
|
+
AND (status <> 'rejected' OR audit_state = 'complete')),
|
|
209520
|
+
CONSTRAINT execution_receipts_transactional_check CHECK (
|
|
209521
|
+
kind <> 'transactional' OR (status = 'confirmed' AND audit_state = 'complete'))
|
|
209522
|
+
);
|
|
209523
|
+
CREATE TABLE IF NOT EXISTS supacloud_commands.execution_audit (
|
|
209524
|
+
tenant_id text NOT NULL,
|
|
209525
|
+
actor_id text NOT NULL,
|
|
209526
|
+
command text NOT NULL,
|
|
209527
|
+
operation_key text NOT NULL,
|
|
209528
|
+
event text NOT NULL,
|
|
209529
|
+
details jsonb NOT NULL,
|
|
209530
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
209531
|
+
PRIMARY KEY (tenant_id, actor_id, command, operation_key),
|
|
209532
|
+
FOREIGN KEY (tenant_id, actor_id, command, operation_key)
|
|
209533
|
+
REFERENCES supacloud_commands.execution_receipts (tenant_id, actor_id, command, operation_key)
|
|
209534
|
+
);
|
|
209535
|
+
REVOKE ALL ON supacloud_commands.execution_receipts, supacloud_commands.execution_audit FROM PUBLIC;
|
|
209536
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.protect_terminal_execution()
|
|
209537
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path = '' AS $terminal$
|
|
209538
|
+
BEGIN
|
|
209539
|
+
IF OLD.status IN ('confirmed','rejected') AND (
|
|
209540
|
+
NEW.status IS DISTINCT FROM OLD.status OR NEW.result IS DISTINCT FROM OLD.result
|
|
209541
|
+
OR (OLD.audit_state = 'complete' AND NEW.audit_state IS DISTINCT FROM OLD.audit_state)) THEN
|
|
209542
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_TERMINAL_RECEIPT';
|
|
209543
|
+
END IF;
|
|
209544
|
+
RETURN NEW;
|
|
209545
|
+
END
|
|
209546
|
+
$terminal$;
|
|
209547
|
+
CREATE OR REPLACE TRIGGER execution_terminal_guard
|
|
209548
|
+
BEFORE UPDATE ON supacloud_commands.execution_receipts
|
|
209549
|
+
FOR EACH ROW EXECUTE FUNCTION supacloud_commands.protect_terminal_execution();
|
|
209550
|
+
|
|
209551
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.require_rejection_audit()
|
|
209552
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path = '' AS $rejection$
|
|
209553
|
+
BEGIN
|
|
209554
|
+
IF NEW.status = 'rejected' AND NOT EXISTS (
|
|
209555
|
+
SELECT FROM supacloud_commands.execution_audit a
|
|
209556
|
+
WHERE a.tenant_id=NEW.tenant_id AND a.actor_id=NEW.actor_id
|
|
209557
|
+
AND a.command=NEW.command AND a.operation_key=NEW.operation_key
|
|
209558
|
+
) THEN
|
|
209559
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_REJECTION_AUDIT_REQUIRED';
|
|
209560
|
+
END IF;
|
|
209561
|
+
RETURN NEW;
|
|
209562
|
+
END
|
|
209563
|
+
$rejection$;
|
|
209564
|
+
DROP TRIGGER IF EXISTS execution_rejection_audit ON supacloud_commands.execution_receipts;
|
|
209565
|
+
CREATE CONSTRAINT TRIGGER execution_rejection_audit
|
|
209566
|
+
AFTER INSERT OR UPDATE ON supacloud_commands.execution_receipts
|
|
209567
|
+
DEFERRABLE INITIALLY DEFERRED
|
|
209568
|
+
FOR EACH ROW WHEN (NEW.status = 'rejected')
|
|
209569
|
+
EXECUTE FUNCTION supacloud_commands.require_rejection_audit();
|
|
209570
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.enqueue_execution_recovery()
|
|
209571
|
+
RETURNS trigger LANGUAGE plpgsql SET search_path = '' AS $recovery$
|
|
209572
|
+
DECLARE submission jsonb;
|
|
209573
|
+
BEGIN
|
|
209574
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(NEW.dispatch_key::text,0));
|
|
209575
|
+
SELECT supacloud_commands.snapshot(NEW.dispatch_key,false) INTO submission;
|
|
209576
|
+
IF submission IS NOT NULL THEN
|
|
209577
|
+
IF submission->>'commandType' IS DISTINCT FROM NEW.command
|
|
209578
|
+
OR submission->>'actorId' IS DISTINCT FROM NEW.actor_id
|
|
209579
|
+
OR submission->>'tenantId' IS DISTINCT FROM NEW.tenant_id
|
|
209580
|
+
OR NEW.operation_key <> NEW.dispatch_key::text THEN
|
|
209581
|
+
RAISE EXCEPTION 'SUPACLOUD_COMMAND_IDENTITY_CONFLICT';
|
|
209582
|
+
END IF;
|
|
209583
|
+
RETURN NEW;
|
|
209584
|
+
END IF;
|
|
209585
|
+
IF NEW.kind = 'external' AND NEW.status <> 'rejected'
|
|
209586
|
+
AND (NEW.status <> 'confirmed' OR NEW.audit_state <> 'complete') THEN
|
|
209587
|
+
PERFORM supacloud_workflows.start_run(
|
|
209588
|
+
NEW.dispatch_key, 'supacloud.command.reconcile', '1', 'reconcile',
|
|
209589
|
+
jsonb_build_object('commandId', NEW.dispatch_key, 'tenantId', NEW.tenant_id,
|
|
209590
|
+
'actorId', NEW.actor_id, 'command', NEW.command, 'operationId', NEW.operation_key),
|
|
209591
|
+
20
|
|
209592
|
+
);
|
|
209593
|
+
END IF;
|
|
209594
|
+
RETURN NEW;
|
|
209595
|
+
END
|
|
209596
|
+
$recovery$;
|
|
209597
|
+
CREATE OR REPLACE TRIGGER execution_recovery_enqueue
|
|
209598
|
+
AFTER INSERT ON supacloud_commands.execution_receipts
|
|
209599
|
+
FOR EACH ROW EXECUTE FUNCTION supacloud_commands.enqueue_execution_recovery();
|
|
209600
|
+
|
|
209601
|
+
CREATE OR REPLACE FUNCTION supacloud_commands.execution_status(request jsonb)
|
|
209602
|
+
RETURNS jsonb LANGUAGE sql STABLE SECURITY DEFINER SET search_path = '' AS $status$
|
|
209603
|
+
SELECT jsonb_build_object('kind','execution','commandId',r.dispatch_key,
|
|
209604
|
+
'execution',jsonb_build_object('tenantId',r.tenant_id,'actorId',r.actor_id,
|
|
209605
|
+
'command',r.command,'operationId',r.operation_key,'dispatchKey',r.dispatch_key,
|
|
209606
|
+
'status',r.status,'audit',r.audit_state)
|
|
209607
|
+
|| CASE WHEN r.status='confirmed' THEN jsonb_build_object('result',r.result) ELSE '{}'::jsonb END,
|
|
209608
|
+
'workflow',CASE WHEN w.id IS NULL THEN NULL ELSE jsonb_build_object('runId',w.id,'status',w.status) END)
|
|
209609
|
+
FROM supacloud_commands.execution_receipts r
|
|
209610
|
+
LEFT JOIN supacloud_workflows.runs w ON w.id=r.dispatch_key
|
|
209611
|
+
WHERE CASE WHEN request ? 'commandId' THEN r.dispatch_key::text=request->>'commandId'
|
|
209612
|
+
ELSE r.tenant_id=request->>'tenantId' AND r.actor_id=request->>'actorId'
|
|
209613
|
+
AND r.command=request->>'command' AND r.operation_key=request->>'operationId' END
|
|
209614
|
+
$status$;
|
|
209615
|
+
REVOKE ALL ON FUNCTION supacloud_commands.execution_status(jsonb) FROM PUBLIC;
|
|
209616
|
+
`;
|
|
209617
|
+
var COMMAND_PERSISTENCE_UPGRADE_SQL = `
|
|
209618
|
+
ALTER TABLE supacloud_commands.execution_receipts
|
|
209619
|
+
ADD COLUMN IF NOT EXISTS input_fingerprint text,
|
|
209620
|
+
ADD COLUMN IF NOT EXISTS input_payload text;
|
|
209621
|
+
DO $migration$
|
|
209622
|
+
BEGIN
|
|
209623
|
+
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='supacloud_commands'
|
|
209624
|
+
AND table_name='execution_receipts' AND column_name='input_key') THEN
|
|
209625
|
+
EXECUTE 'UPDATE supacloud_commands.execution_receipts
|
|
209626
|
+
SET input_fingerprint=encode(sha256(convert_to(input_key,''UTF8'')),''hex''),input_payload=input_key
|
|
209627
|
+
WHERE input_fingerprint IS NULL';
|
|
209628
|
+
ALTER TABLE supacloud_commands.execution_receipts DROP COLUMN input_key;
|
|
209629
|
+
END IF;
|
|
209630
|
+
END
|
|
209631
|
+
$migration$;
|
|
209632
|
+
ALTER TABLE supacloud_commands.execution_receipts ALTER COLUMN input_fingerprint SET NOT NULL;
|
|
209633
|
+
ALTER TABLE supacloud_commands.execution_receipts
|
|
209634
|
+
DROP CONSTRAINT IF EXISTS execution_receipts_status_check,
|
|
209635
|
+
DROP CONSTRAINT IF EXISTS execution_receipts_check1,
|
|
209636
|
+
DROP CONSTRAINT IF EXISTS execution_receipts_transactional_check,
|
|
209637
|
+
DROP CONSTRAINT IF EXISTS execution_receipts_audit_terminal_check;
|
|
209638
|
+
ALTER TABLE supacloud_commands.execution_receipts
|
|
209639
|
+
ADD CONSTRAINT execution_receipts_status_check CHECK (status IN ('pending','unknown','confirmed','rejected')),
|
|
209640
|
+
ADD CONSTRAINT execution_receipts_transactional_check CHECK (
|
|
209641
|
+
kind <> 'transactional' OR (status = 'confirmed' AND audit_state = 'complete')),
|
|
209642
|
+
ADD CONSTRAINT execution_receipts_audit_terminal_check CHECK (
|
|
209643
|
+
(audit_state <> 'complete' OR status IN ('confirmed','rejected'))
|
|
209644
|
+
AND (status <> 'rejected' OR audit_state = 'complete'));
|
|
209645
|
+
DO $constraint$
|
|
209646
|
+
BEGIN
|
|
209647
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint
|
|
209648
|
+
WHERE conrelid='supacloud_commands.execution_receipts'::regclass
|
|
209649
|
+
AND conname='execution_receipts_input_fingerprint_check') THEN
|
|
209650
|
+
ALTER TABLE supacloud_commands.execution_receipts
|
|
209651
|
+
ADD CONSTRAINT execution_receipts_input_fingerprint_check CHECK (input_fingerprint ~ '^[a-f0-9]{64}$');
|
|
209652
|
+
END IF;
|
|
209653
|
+
END
|
|
209654
|
+
$constraint$;
|
|
209655
|
+
${COMMAND_PERSISTENCE_SQL}
|
|
209656
|
+
DO $backfill$
|
|
209657
|
+
DECLARE r record;
|
|
209658
|
+
BEGIN
|
|
209659
|
+
FOR r IN SELECT * FROM supacloud_commands.execution_receipts
|
|
209660
|
+
WHERE kind='external' AND status<>'rejected' AND (status<>'confirmed' OR audit_state<>'complete')
|
|
209661
|
+
AND NOT EXISTS(SELECT FROM supacloud_workflows.runs w WHERE w.id=dispatch_key)
|
|
209662
|
+
LOOP
|
|
209663
|
+
PERFORM supacloud_workflows.start_run(
|
|
209664
|
+
r.dispatch_key,'supacloud.command.reconcile','1','reconcile',
|
|
209665
|
+
jsonb_build_object('commandId',r.dispatch_key,'tenantId',r.tenant_id,
|
|
209666
|
+
'actorId',r.actor_id,'command',r.command,'operationId',r.operation_key),20
|
|
209667
|
+
);
|
|
209668
|
+
END LOOP;
|
|
209669
|
+
END
|
|
209670
|
+
$backfill$;
|
|
209671
|
+
DROP INDEX IF EXISTS supacloud_commands.execution_pending_recovery;
|
|
209672
|
+
ALTER TABLE supacloud_commands.execution_receipts
|
|
209673
|
+
DROP COLUMN IF EXISTS lease_id,
|
|
209674
|
+
DROP COLUMN IF EXISTS lease_until,
|
|
209675
|
+
DROP COLUMN IF EXISTS next_attempt_at,
|
|
209676
|
+
DROP COLUMN IF EXISTS recovery_attempts;
|
|
209677
|
+
`;
|
|
209678
|
+
var MAX_MIGRATION_VERSION = 9223372036854775807n;
|
|
209679
|
+
function sha2562(value) {
|
|
209680
|
+
return createHash22("sha256").update(value).digest("hex");
|
|
209681
|
+
}
|
|
209682
|
+
function normalizedSql(value) {
|
|
209683
|
+
return value.replace(/\r\n?/g, `
|
|
209684
|
+
`).trimEnd() + `
|
|
209685
|
+
`;
|
|
209686
|
+
}
|
|
209687
|
+
function validVersion(value) {
|
|
209688
|
+
if (!/^\d{1,19}$/.test(value))
|
|
209689
|
+
return false;
|
|
209690
|
+
const version = BigInt(value);
|
|
209691
|
+
return version > 0n && version <= MAX_MIGRATION_VERSION;
|
|
209692
|
+
}
|
|
209693
|
+
function migrationVersion(file) {
|
|
209694
|
+
const match = basename2(file).match(/^(\d{8,19})[_-]/);
|
|
209695
|
+
if (!match || !validVersion(match[1]))
|
|
209696
|
+
throw new Error(`Invalid migration version in ${file}`);
|
|
209697
|
+
return BigInt(match[1]).toString();
|
|
209698
|
+
}
|
|
209699
|
+
function migrationName(file) {
|
|
209700
|
+
return basename2(file, ".sql").replace(/^\d{8,19}[_-]/, "");
|
|
209701
|
+
}
|
|
209702
|
+
function sortMigrations(migrations) {
|
|
209703
|
+
const sorted = [...migrations].sort((left, right) => {
|
|
209704
|
+
const leftVersion = BigInt(left.version);
|
|
209705
|
+
const rightVersion = BigInt(right.version);
|
|
209706
|
+
if (leftVersion !== rightVersion)
|
|
209707
|
+
return leftVersion < rightVersion ? -1 : 1;
|
|
209708
|
+
return left.file.localeCompare(right.file);
|
|
209709
|
+
});
|
|
209710
|
+
for (let index = 1;index < sorted.length; index += 1) {
|
|
209711
|
+
if (sorted[index - 1].version === sorted[index].version) {
|
|
209712
|
+
throw new Error(`Duplicate migration version ${sorted[index].version}`);
|
|
209713
|
+
}
|
|
209714
|
+
}
|
|
209715
|
+
return sorted;
|
|
209716
|
+
}
|
|
209717
|
+
function planMigrationRebase(migrations, options, baselineSql) {
|
|
209718
|
+
const sorted = sortMigrations([...migrations]);
|
|
209719
|
+
if (!sorted.length)
|
|
209720
|
+
throw new Error("No migration files found");
|
|
209721
|
+
if (!validVersion(options.baselineVersion))
|
|
209722
|
+
throw new Error("Invalid baseline version");
|
|
209723
|
+
if (options.retainAfterVersion !== undefined && !validVersion(options.retainAfterVersion)) {
|
|
209724
|
+
throw new Error("Invalid retain-after version");
|
|
209725
|
+
}
|
|
209726
|
+
const baselineName = options.baselineName?.trim() || "reconstructed_schema";
|
|
209727
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(baselineName))
|
|
209728
|
+
throw new Error("Invalid baseline name");
|
|
209729
|
+
const baselineMigration = `${options.baselineVersion}_${baselineName}.sql`;
|
|
209730
|
+
if (sorted.some((migration) => migration.version === options.baselineVersion)) {
|
|
209731
|
+
throw new Error(`Baseline version ${options.baselineVersion} collides with existing history`);
|
|
209732
|
+
}
|
|
209733
|
+
const retainAfter = options.retainAfterVersion === undefined ? null : BigInt(options.retainAfterVersion);
|
|
209734
|
+
const retained = sorted.filter((migration) => retainAfter !== null && BigInt(migration.version) > retainAfter);
|
|
209735
|
+
const archived = sorted.filter((migration) => !retained.includes(migration));
|
|
209736
|
+
const normalizedBaseline = normalizedSql(baselineSql);
|
|
209737
|
+
if (!normalizedBaseline.trim())
|
|
209738
|
+
throw new Error("Baseline SQL is empty");
|
|
209739
|
+
if (!/\bCREATE\b/i.test(normalizedBaseline))
|
|
209740
|
+
throw new Error("Baseline SQL does not look like a schema snapshot");
|
|
209741
|
+
const sourceHistory = sorted.map((migration) => ({
|
|
209742
|
+
file: migration.file,
|
|
209743
|
+
version: migration.version,
|
|
209744
|
+
name: migration.name,
|
|
209745
|
+
sha256: sha2562(normalizedSql(migration.sql))
|
|
209746
|
+
}));
|
|
209747
|
+
return {
|
|
209748
|
+
version: 1,
|
|
209749
|
+
sourceDirectory: options.sourceDirectory,
|
|
209750
|
+
outputDirectory: options.outputDirectory,
|
|
209751
|
+
baselineFile: options.baselineFile,
|
|
209752
|
+
baselineMigration,
|
|
209753
|
+
retainedMigrations: retained.map((migration) => migration.file),
|
|
209754
|
+
archivedMigrations: archived.map((migration) => migration.file),
|
|
209755
|
+
sourceHistorySha256: sha2562(JSON.stringify(sourceHistory)),
|
|
209756
|
+
baselineSha256: sha2562(normalizedBaseline),
|
|
209757
|
+
warnings: [
|
|
209758
|
+
"The baseline must come from a schema-only snapshot of the verified target database.",
|
|
209759
|
+
"Data backfills, reference data and non-replayable side effects need a separate review.",
|
|
209760
|
+
"Archive the old migration directory until clean replay and remote read-back have passed."
|
|
209761
|
+
]
|
|
209762
|
+
};
|
|
209763
|
+
}
|
|
209764
|
+
async function rebaseMigrations(options) {
|
|
209765
|
+
const sourceDirectory = resolve5(options.sourceDirectory);
|
|
209766
|
+
const outputDirectory = resolve5(options.outputDirectory);
|
|
209767
|
+
const baselineFile = resolve5(options.baselineFile);
|
|
209768
|
+
if (!(await stat(sourceDirectory)).isDirectory())
|
|
209769
|
+
throw new Error("Source migration directory is not a directory");
|
|
209770
|
+
if (!(await stat(baselineFile)).isFile())
|
|
209771
|
+
throw new Error("Baseline file is not a file");
|
|
209772
|
+
if (sourceDirectory === outputDirectory)
|
|
209773
|
+
throw new Error("Output directory must be separate from source directory");
|
|
209774
|
+
const files = (await readdir3(sourceDirectory)).filter((file) => file.endsWith(".sql")).sort();
|
|
209775
|
+
const migrations = await Promise.all(files.map(async (file) => {
|
|
209776
|
+
const sql = await readFile2(join3(sourceDirectory, file), "utf8");
|
|
209777
|
+
return { file, version: migrationVersion(file), name: migrationName(file), sql };
|
|
209778
|
+
}));
|
|
209779
|
+
const plan = planMigrationRebase(migrations, { ...options, sourceDirectory, outputDirectory, baselineFile }, await readFile2(baselineFile, "utf8"));
|
|
209780
|
+
await mkdir2(outputDirectory, { recursive: true });
|
|
209781
|
+
await writeFile2(join3(outputDirectory, plan.baselineMigration), normalizedSql(await readFile2(baselineFile, "utf8")));
|
|
209782
|
+
for (const migration of migrations.filter((item) => plan.retainedMigrations.includes(item.file))) {
|
|
209783
|
+
await writeFile2(join3(outputDirectory, migration.file), normalizedSql(migration.sql));
|
|
209784
|
+
}
|
|
209785
|
+
await writeFile2(join3(outputDirectory, "migration-rebase.manifest.json"), JSON.stringify({
|
|
209786
|
+
...plan,
|
|
209787
|
+
generatedAt: new Date().toISOString(),
|
|
209788
|
+
sourceHistory: migrations.map((migration) => ({
|
|
209789
|
+
file: migration.file,
|
|
209790
|
+
version: migration.version,
|
|
209791
|
+
name: migration.name,
|
|
209792
|
+
sha256: sha2562(normalizedSql(migration.sql))
|
|
209793
|
+
}))
|
|
209794
|
+
}, null, 2) + `
|
|
209795
|
+
`);
|
|
209796
|
+
return plan;
|
|
209797
|
+
}
|
|
209798
|
+
|
|
209057
209799
|
// src/shared/project-ref.ts
|
|
209058
209800
|
var PROJECT_REF_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
209059
209801
|
function projectRefPathSegment(ref, operation) {
|
|
@@ -209782,19 +210524,19 @@ function formatMigrationRiskReport(analysis) {
|
|
|
209782
210524
|
|
|
209783
210525
|
// src/shared/tools/migration-import.ts
|
|
209784
210526
|
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
|
|
209785
|
-
import { mkdir as
|
|
209786
|
-
import { basename as
|
|
210527
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
210528
|
+
import { basename as basename3, join as join4 } from "node:path";
|
|
209787
210529
|
async function writeMigrationImportPlan(plan, outputDirectory) {
|
|
209788
210530
|
if (!outputDirectory.trim())
|
|
209789
210531
|
throw new Error("Migration import output directory is required");
|
|
209790
|
-
await
|
|
210532
|
+
await mkdir3(outputDirectory, { recursive: true });
|
|
209791
210533
|
const written = [];
|
|
209792
210534
|
for (const file of plan.files) {
|
|
209793
210535
|
if (!/^[A-Za-z0-9_.-]+\.sql$/.test(file.file) || file.file.includes("..")) {
|
|
209794
210536
|
throw new Error(`Unsafe migration import filename: ${file.file}`);
|
|
209795
210537
|
}
|
|
209796
|
-
const path =
|
|
209797
|
-
await
|
|
210538
|
+
const path = join4(outputDirectory, file.file);
|
|
210539
|
+
await writeFile3(path, file.sql, { flag: "wx", encoding: "utf8" });
|
|
209798
210540
|
written.push(path);
|
|
209799
210541
|
}
|
|
209800
210542
|
return written;
|
|
@@ -209805,8 +210547,8 @@ function record3(value) {
|
|
|
209805
210547
|
function sqlFile(file, root, source) {
|
|
209806
210548
|
return {
|
|
209807
210549
|
file,
|
|
209808
|
-
name:
|
|
209809
|
-
sql: readFileSync2(
|
|
210550
|
+
name: basename3(file, ".sql"),
|
|
210551
|
+
sql: readFileSync2(join4(root, file), "utf8"),
|
|
209810
210552
|
source: "sql"
|
|
209811
210553
|
};
|
|
209812
210554
|
}
|
|
@@ -209908,7 +210650,7 @@ function createMigrationImportPlan(input) {
|
|
|
209908
210650
|
}
|
|
209909
210651
|
|
|
209910
210652
|
// src/shared/tools/database-tools.ts
|
|
209911
|
-
var
|
|
210653
|
+
var MAX_MIGRATION_VERSION2 = 9223372036854775807n;
|
|
209912
210654
|
var FALLBACK_MIGRATION_VERSION_BASE = 8000000000000000000n;
|
|
209913
210655
|
var FALLBACK_MIGRATION_VERSION_RANGE = 1000000000000000000n;
|
|
209914
210656
|
var FALLBACK_MIGRATION_VERSION_LIMIT = FALLBACK_MIGRATION_VERSION_BASE + FALLBACK_MIGRATION_VERSION_RANGE;
|
|
@@ -209918,7 +210660,7 @@ function isMigrationInventoryVersion(version) {
|
|
|
209918
210660
|
if (typeof version !== "string" || !/^\d{1,19}$/.test(version))
|
|
209919
210661
|
return false;
|
|
209920
210662
|
const numericVersion = BigInt(version);
|
|
209921
|
-
return numericVersion >= 1n && numericVersion <=
|
|
210663
|
+
return numericVersion >= 1n && numericVersion <= MAX_MIGRATION_VERSION2 && numericVersion.toString() === version;
|
|
209922
210664
|
}
|
|
209923
210665
|
function isMigrationInventoryName(name) {
|
|
209924
210666
|
return name === null || typeof name === "string" && name.length <= 255 && name.length > 0 && name.trim() === name;
|
|
@@ -210013,28 +210755,28 @@ function migrationInventoryResponse(response) {
|
|
|
210013
210755
|
return { content: [{ type: "text", text: JSON.stringify(inventory, null, 2) }] };
|
|
210014
210756
|
}
|
|
210015
210757
|
function readMigrationFile(dir, file) {
|
|
210016
|
-
const rawBytes = readFileSync3(
|
|
210758
|
+
const rawBytes = readFileSync3(join5(dir, file));
|
|
210017
210759
|
return {
|
|
210018
210760
|
file,
|
|
210019
|
-
name:
|
|
210761
|
+
name: basename4(file, ".sql"),
|
|
210020
210762
|
version: migrationVersionFromFilename(file),
|
|
210021
210763
|
sql: Buffer.from(rawBytes).toString("utf8"),
|
|
210022
210764
|
rawBytes
|
|
210023
210765
|
};
|
|
210024
210766
|
}
|
|
210025
210767
|
function migrationVersionFromFilename(file) {
|
|
210026
|
-
const match =
|
|
210768
|
+
const match = basename4(file).match(/^(\d{8,20})[_-]/);
|
|
210027
210769
|
if (match) {
|
|
210028
210770
|
const version = BigInt(match[1]);
|
|
210029
|
-
if (version < 1n || version >
|
|
210030
|
-
throw new Error(`Invalid migration version '${match[1]}' in ${
|
|
210771
|
+
if (version < 1n || version > MAX_MIGRATION_VERSION2) {
|
|
210772
|
+
throw new Error(`Invalid migration version '${match[1]}' in ${basename4(file)}: expected 1..${MAX_MIGRATION_VERSION2}`);
|
|
210031
210773
|
}
|
|
210032
210774
|
if (version >= FALLBACK_MIGRATION_VERSION_BASE && version < FALLBACK_MIGRATION_VERSION_LIMIT) {
|
|
210033
|
-
throw new Error(`Invalid migration version '${match[1]}' in ${
|
|
210775
|
+
throw new Error(`Invalid migration version '${match[1]}' in ${basename4(file)}: version range ${FALLBACK_MIGRATION_VERSION_BASE}..${FALLBACK_MIGRATION_VERSION_LIMIT - 1n} is reserved for non-timestamp migrations`);
|
|
210034
210776
|
}
|
|
210035
210777
|
return version.toString();
|
|
210036
210778
|
}
|
|
210037
|
-
const digest = createHash3("sha256").update(
|
|
210779
|
+
const digest = createHash3("sha256").update(basename4(file)).digest("hex");
|
|
210038
210780
|
const offset = BigInt(`0x${digest}`) % FALLBACK_MIGRATION_VERSION_RANGE;
|
|
210039
210781
|
return (FALLBACK_MIGRATION_VERSION_BASE + offset).toString();
|
|
210040
210782
|
}
|
|
@@ -210310,7 +211052,7 @@ function vectorWarningsForPendingMigrations(migrations, vectorEnabled) {
|
|
|
210310
211052
|
}
|
|
210311
211053
|
function registerDatabaseTools(server2, http, config = {}) {
|
|
210312
211054
|
const { localOnly = false, readOnly = false, projectRef } = config;
|
|
210313
|
-
const localActions = ["lint_migrations", "lint"];
|
|
211055
|
+
const localActions = ["lint_migrations", "lint", "rebase_migrations"];
|
|
210314
211056
|
const readActions = [
|
|
210315
211057
|
"query",
|
|
210316
211058
|
"list_tables",
|
|
@@ -210348,7 +211090,7 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
210348
211090
|
file: optional(String2(), "[query/execute/apply_migration/lint_migrations/lint] Read SQL from local file path (avoids shell escaping issues with $$ and multi-statement DDL)"),
|
|
210349
211091
|
mode: optional(stringEnum(["read", "migration", "admin"]), "[query/execute] SQL execution mode: read, migration, or admin. execute defaults to migration; query defaults to read/auto"),
|
|
210350
211092
|
admin: optional(Boolean2(), "[query/execute] Admin execution flag (required when mode=admin)"),
|
|
210351
|
-
dir: optional(String2(), "[push_migrations/baseline_migrations/lint_migrations/lint] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
211093
|
+
dir: optional(String2(), "[push_migrations/baseline_migrations/lint_migrations/lint/rebase_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
|
|
210352
211094
|
dry_run: optional(Boolean2(), "[push_migrations/baseline_migrations] Preview changes without applying them"),
|
|
210353
211095
|
strict: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Exit with error if high-risk issues or migrations are detected"),
|
|
210354
211096
|
fail_on_high: optional(Boolean2(), "[push_migrations/lint_migrations/lint/database_lint] Alias for --strict"),
|
|
@@ -210366,6 +211108,10 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
210366
211108
|
metadata_file: optional(String2(), "[migration_import] Hasura metadata JSON file"),
|
|
210367
211109
|
output_dir: optional(String2(), "[migration_import] Output migration directory when write=true"),
|
|
210368
211110
|
write: optional(Boolean2(), "[migration_import] Write the reviewed plan to output_dir; never applies SQL"),
|
|
211111
|
+
baseline_file: optional(String2(), "[rebase_migrations] Reviewed schema-only snapshot to use as the new baseline"),
|
|
211112
|
+
baseline_version: optional(String2(), "[rebase_migrations] New migration version for the baseline"),
|
|
211113
|
+
baseline_name: optional(String2(), "[rebase_migrations] New baseline migration name"),
|
|
211114
|
+
retain_after_version: optional(String2(), "[rebase_migrations] Retain migrations newer than this version"),
|
|
210369
211115
|
columns: optional(String2(), "[create_table_rls] Column definitions"),
|
|
210370
211116
|
policy_mode: optional(stringEnum(["deny_all", "owner"]), "[create_table_rls] RLS policy mode (default: deny_all)"),
|
|
210371
211117
|
owner_column: optional(String2(), "[create_table_rls owner] UUID owner column matched to auth.uid()"),
|
|
@@ -210383,6 +211129,34 @@ Actions: ${allActions.join(", ")}${localOnly ? " (local-only mode)" : readOnly ?
|
|
|
210383
211129
|
const ref = args.ref || projectRef;
|
|
210384
211130
|
const schema = args.schema || "public";
|
|
210385
211131
|
const schemas = args.schemas || ["public"];
|
|
211132
|
+
if (action === "rebase_migrations") {
|
|
211133
|
+
const sourceDirectory = args.dir || "supabase/migrations";
|
|
211134
|
+
const outputDirectory = args.output_dir;
|
|
211135
|
+
const baselineFile = args.baseline_file;
|
|
211136
|
+
const baselineVersion = args.baseline_version;
|
|
211137
|
+
if (typeof outputDirectory !== "string" || typeof baselineFile !== "string" || typeof baselineVersion !== "string") {
|
|
211138
|
+
throw new Error("rebase_migrations requires output_dir, baseline_file and baseline_version");
|
|
211139
|
+
}
|
|
211140
|
+
const options = {
|
|
211141
|
+
sourceDirectory,
|
|
211142
|
+
outputDirectory,
|
|
211143
|
+
baselineFile,
|
|
211144
|
+
baselineVersion,
|
|
211145
|
+
...typeof args.baseline_name === "string" ? { baselineName: args.baseline_name } : {},
|
|
211146
|
+
...typeof args.retain_after_version === "string" ? { retainAfterVersion: args.retain_after_version } : {}
|
|
211147
|
+
};
|
|
211148
|
+
if (args.write !== true) {
|
|
211149
|
+
const files = readdirSync2(sourceDirectory).filter((file) => file.endsWith(".sql")).sort();
|
|
211150
|
+
const migrations = files.map((file) => {
|
|
211151
|
+
const migration = readMigrationFile(sourceDirectory, file);
|
|
211152
|
+
return { file, version: migration.version, name: migration.name, sql: migration.sql };
|
|
211153
|
+
});
|
|
211154
|
+
const plan = planMigrationRebase(migrations, options, readFileSync3(baselineFile, "utf8"));
|
|
211155
|
+
return { content: [{ type: "text", text: JSON.stringify({ mode: "plan", ...plan }, null, 2) }] };
|
|
211156
|
+
}
|
|
211157
|
+
const plan = await rebaseMigrations(options);
|
|
211158
|
+
return { content: [{ type: "text", text: JSON.stringify({ mode: "written", ...plan }, null, 2) }] };
|
|
211159
|
+
}
|
|
210386
211160
|
if (action === "delivery_migration_plan") {
|
|
210387
211161
|
const failure = (code, httpStatus = null) => ({
|
|
210388
211162
|
isError: true,
|
|
@@ -211988,7 +212762,7 @@ function registerOAuthClientTools(server2, http) {
|
|
|
211988
212762
|
|
|
211989
212763
|
// src/shared/tools/storage-tools.ts
|
|
211990
212764
|
import { existsSync as existsSync4, openAsBlob, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
211991
|
-
import { basename as
|
|
212765
|
+
import { basename as basename5, resolve as resolve6 } from "node:path";
|
|
211992
212766
|
var MAX_BUCKET_ID_LENGTH = 100;
|
|
211993
212767
|
var MAX_MIME_TYPE_COUNT = 100;
|
|
211994
212768
|
var MAX_MIME_TYPE_LENGTH = 255;
|
|
@@ -212447,7 +213221,7 @@ Actions: status, list_buckets, get_bucket, create_bucket, update_bucket, delete_
|
|
|
212447
213221
|
if (typeof rawFilePath !== "string" || !rawFilePath.trim()) {
|
|
212448
213222
|
throw new Error(`'file_path' (or '--file') required for '${action}'`);
|
|
212449
213223
|
}
|
|
212450
|
-
const resolvedFilePath =
|
|
213224
|
+
const resolvedFilePath = resolve6(process.cwd(), rawFilePath.trim());
|
|
212451
213225
|
if (!existsSync4(resolvedFilePath)) {
|
|
212452
213226
|
throw new Error(`File not found: ${resolvedFilePath}`);
|
|
212453
213227
|
}
|
|
@@ -212455,7 +213229,7 @@ Actions: status, list_buckets, get_bucket, create_bucket, update_bucket, delete_
|
|
|
212455
213229
|
if (!stat.isFile()) {
|
|
212456
213230
|
throw new Error(`Path is not a regular file: ${resolvedFilePath}`);
|
|
212457
213231
|
}
|
|
212458
|
-
const filename = typeof args.filename === "string" && args.filename.trim() ? args.filename.trim() :
|
|
213232
|
+
const filename = typeof args.filename === "string" && args.filename.trim() ? args.filename.trim() : basename5(resolvedFilePath);
|
|
212459
213233
|
const mimeType = typeof args.mime_type === "string" && args.mime_type.trim() ? args.mime_type.trim() : "application/octet-stream";
|
|
212460
213234
|
let timeoutMs = DEFAULT_UPLOAD_TIMEOUT_MS;
|
|
212461
213235
|
if (args.timeout_ms !== undefined) {
|
|
@@ -212503,7 +213277,7 @@ import {
|
|
|
212503
213277
|
writeFileSync
|
|
212504
213278
|
} from "node:fs";
|
|
212505
213279
|
import { tmpdir } from "node:os";
|
|
212506
|
-
import { basename as
|
|
213280
|
+
import { basename as basename6, join as join6, relative as relative2, resolve as resolve7, sep as sep2 } from "node:path";
|
|
212507
213281
|
|
|
212508
213282
|
// src/shared/tools/edge-function-response.ts
|
|
212509
213283
|
var CANONICAL_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
|
|
@@ -212806,13 +213580,13 @@ function scaffoldFunction(pathArg, slug, framework) {
|
|
|
212806
213580
|
if (typeof framework !== "string" || !FUNCTION_FRAMEWORKS2.includes(framework)) {
|
|
212807
213581
|
throw new Error("'framework' required for 'scaffold'");
|
|
212808
213582
|
}
|
|
212809
|
-
const target =
|
|
213583
|
+
const target = resolve7(typeof pathArg === "string" ? pathArg : join6("supabase", "functions", slug));
|
|
212810
213584
|
if (existsSync5(target))
|
|
212811
213585
|
throw new Error(`Scaffold target already exists: ${target}`);
|
|
212812
213586
|
const files = scaffoldFiles(framework, slug);
|
|
212813
213587
|
for (const [relativePath, contents] of Object.entries(files)) {
|
|
212814
|
-
const destination =
|
|
212815
|
-
mkdirSync(
|
|
213588
|
+
const destination = join6(target, relativePath);
|
|
213589
|
+
mkdirSync(resolve7(destination, ".."), { recursive: true });
|
|
212816
213590
|
writeFileSync(destination, contents, { flag: "wx" });
|
|
212817
213591
|
}
|
|
212818
213592
|
return JSON.stringify({ success: true, framework, path: target, files: Object.keys(files) }, null, 2);
|
|
@@ -212857,7 +213631,7 @@ function assertExpectedSha256(bytes, expectedSha256) {
|
|
|
212857
213631
|
}
|
|
212858
213632
|
}
|
|
212859
213633
|
function readVerifiedPrebundledCode(pathArg, expectedSha256) {
|
|
212860
|
-
const descriptor = openSync(
|
|
213634
|
+
const descriptor = openSync(resolve7(pathArg), fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
|
|
212861
213635
|
try {
|
|
212862
213636
|
const identityBeforeRead = openFileIdentity(descriptor);
|
|
212863
213637
|
const bytes = readFileSync5(descriptor);
|
|
@@ -212883,7 +213657,7 @@ function readBundleDirectory(root, directory = root) {
|
|
|
212883
213657
|
const files = {};
|
|
212884
213658
|
const entries = readdirSync3(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
212885
213659
|
for (const entry of entries) {
|
|
212886
|
-
const entryPath =
|
|
213660
|
+
const entryPath = join6(directory, entry.name);
|
|
212887
213661
|
const bundlePath = canonicalBundlePath(root, entryPath);
|
|
212888
213662
|
const entryState = lstatSync(entryPath);
|
|
212889
213663
|
if (entryState.isSymbolicLink())
|
|
@@ -212906,7 +213680,7 @@ function preparedBundleFiles(args) {
|
|
|
212906
213680
|
}
|
|
212907
213681
|
if (typeof bundleDirectory !== "string")
|
|
212908
213682
|
return files;
|
|
212909
|
-
const resolvedDirectory =
|
|
213683
|
+
const resolvedDirectory = resolve7(bundleDirectory);
|
|
212910
213684
|
const rootState = lstatSync(resolvedDirectory);
|
|
212911
213685
|
if (rootState.isSymbolicLink() || !rootState.isDirectory()) {
|
|
212912
213686
|
throw new Error("'--bundle-dir' must be a regular directory and must not be a symlink");
|
|
@@ -212944,8 +213718,8 @@ async function runBunBuild(args) {
|
|
|
212944
213718
|
}
|
|
212945
213719
|
async function bundleEdgeFunctionPath(pathArg) {
|
|
212946
213720
|
const entrypoint = resolveEntrypoint(pathArg);
|
|
212947
|
-
const tmpDir = mkdtempSync(
|
|
212948
|
-
const outfile =
|
|
213721
|
+
const tmpDir = mkdtempSync(join6(tmpdir(), "supacloud-edge-"));
|
|
213722
|
+
const outfile = join6(tmpDir, `${basename6(entrypoint).replace(/\.[^.]+$/, "") || "index"}.js`);
|
|
212949
213723
|
try {
|
|
212950
213724
|
const { stderr } = await runBunBuild([entrypoint, "--target", "bun", "--outfile", outfile]);
|
|
212951
213725
|
const outFileHandle = Bun.file(outfile);
|
|
@@ -213008,11 +213782,11 @@ function rejectActionSpecificFlags(action, args) {
|
|
|
213008
213782
|
}
|
|
213009
213783
|
}
|
|
213010
213784
|
function resolveEntrypoint(pathArg) {
|
|
213011
|
-
const resolved =
|
|
213785
|
+
const resolved = resolve7(pathArg);
|
|
213012
213786
|
const stat = statSync4(resolved);
|
|
213013
213787
|
if (!stat.isDirectory())
|
|
213014
213788
|
return resolved;
|
|
213015
|
-
const entrypoint =
|
|
213789
|
+
const entrypoint = join6(resolved, "index.ts");
|
|
213016
213790
|
if (!existsSync5(entrypoint)) {
|
|
213017
213791
|
throw new Error(`Directory provided but no index.ts found at ${entrypoint}`);
|
|
213018
213792
|
}
|
|
@@ -213316,7 +214090,7 @@ function functionSourceOutput(slug, sourceCode, output) {
|
|
|
213316
214090
|
if (!output) {
|
|
213317
214091
|
return { content: [{ type: "text", text: JSON.stringify({ code: sourceCode }, null, 2) }] };
|
|
213318
214092
|
}
|
|
213319
|
-
const outputPath =
|
|
214093
|
+
const outputPath = resolve7(output);
|
|
213320
214094
|
writeFileSync(outputPath, sourceCode, { flag: "wx" });
|
|
213321
214095
|
return {
|
|
213322
214096
|
content: [{
|
|
@@ -213553,8 +214327,8 @@ Actions: list, get_config, deploy, deploy_bundle, config, source, activate, dele
|
|
|
213553
214327
|
});
|
|
213554
214328
|
const hasFunctionConfig = () => Object.keys(functionConfig()).length > 0;
|
|
213555
214329
|
const checkSyntax = async (sourceCode) => {
|
|
213556
|
-
const tmpDir = mkdtempSync(
|
|
213557
|
-
const tmpFile =
|
|
214330
|
+
const tmpDir = mkdtempSync(join6(tmpdir(), "supacloud-edge-check-"));
|
|
214331
|
+
const tmpFile = join6(tmpDir, "index.ts");
|
|
213558
214332
|
writeFileSync(tmpFile, sourceCode);
|
|
213559
214333
|
try {
|
|
213560
214334
|
await runBunBuild([tmpFile, "--external", "*"]);
|
|
@@ -213845,13 +214619,13 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
|
|
|
213845
214619
|
|
|
213846
214620
|
// src/shared/tools/frontend-tools.ts
|
|
213847
214621
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
213848
|
-
import { basename as
|
|
214622
|
+
import { basename as basename7 } from "node:path";
|
|
213849
214623
|
|
|
213850
214624
|
// src/shared/tools/frontend-release-control.ts
|
|
213851
214625
|
import { createHash as createHash5 } from "node:crypto";
|
|
213852
214626
|
import { constants as fsConstants2 } from "node:fs";
|
|
213853
214627
|
import { open as open2 } from "node:fs/promises";
|
|
213854
|
-
import { resolve as
|
|
214628
|
+
import { resolve as resolve8 } from "node:path";
|
|
213855
214629
|
var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
|
|
213856
214630
|
var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
213857
214631
|
var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
|
|
@@ -213991,7 +214765,7 @@ async function archiveSha256(handle, sizeBytes) {
|
|
|
213991
214765
|
return hash.digest("hex");
|
|
213992
214766
|
}
|
|
213993
214767
|
async function verifiedArchive(path) {
|
|
213994
|
-
const archivePath =
|
|
214768
|
+
const archivePath = resolve8(path);
|
|
213995
214769
|
const handle = await open2(archivePath, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
|
|
213996
214770
|
try {
|
|
213997
214771
|
const before = await handle.stat({ bigint: true });
|
|
@@ -214388,7 +215162,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
214388
215162
|
}
|
|
214389
215163
|
const zipBuffer = readFileSync6(zip_path);
|
|
214390
215164
|
const form = new FormData;
|
|
214391
|
-
form.append("file", new Blob([zipBuffer], { type: "application/zip" }),
|
|
215165
|
+
form.append("file", new Blob([zipBuffer], { type: "application/zip" }), basename7(zip_path));
|
|
214392
215166
|
text = ok(await http.postMultipart(`/v1/projects/${ref}/frontend/deployments/${id}/deploy/upload`, form));
|
|
214393
215167
|
break;
|
|
214394
215168
|
case "redeploy":
|
|
@@ -216143,7 +216917,7 @@ function registerBranchTools(server2, http, options = {}) {
|
|
|
216143
216917
|
|
|
216144
216918
|
// src/shared/tools/supabase-cli-tools.ts
|
|
216145
216919
|
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync2, statSync as statSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
216146
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
216920
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve as resolve9 } from "node:path";
|
|
216147
216921
|
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;
|
|
216148
216922
|
var VALID_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
216149
216923
|
var VALID_MIGRATION_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
|
|
@@ -216188,12 +216962,12 @@ function schemaArguments(schema) {
|
|
|
216188
216962
|
function workdirArguments(workdir) {
|
|
216189
216963
|
if (!workdir)
|
|
216190
216964
|
throw new Error("A workdir is required");
|
|
216191
|
-
return ["--workdir",
|
|
216965
|
+
return ["--workdir", resolve9(workdir)];
|
|
216192
216966
|
}
|
|
216193
216967
|
function resolveOutputPath(workdir, file, label) {
|
|
216194
216968
|
if (!file || /[\r\n\0]/.test(file))
|
|
216195
216969
|
throw new Error(`${label} file is required`);
|
|
216196
|
-
return isAbsolute2(file) ?
|
|
216970
|
+
return isAbsolute2(file) ? resolve9(file) : resolve9(workdir, file);
|
|
216197
216971
|
}
|
|
216198
216972
|
function databaseTargetArguments(databaseUrl) {
|
|
216199
216973
|
return databaseUrl ? ["--db-url", requirePostgresUrl(databaseUrl)] : ["--local"];
|
|
@@ -216367,7 +217141,7 @@ function actionArguments(request, workdir) {
|
|
|
216367
217141
|
function buildOfficialSupabaseArgs(request) {
|
|
216368
217142
|
if (request.action === "version")
|
|
216369
217143
|
return ["--version"];
|
|
216370
|
-
const workdir = request.workdir ?
|
|
217144
|
+
const workdir = request.workdir ? resolve9(request.workdir) : undefined;
|
|
216371
217145
|
if (!workdir)
|
|
216372
217146
|
throw new Error("A workdir is required");
|
|
216373
217147
|
return [...actionArguments(request, workdir), ...workdirArguments(workdir)];
|
|
@@ -216444,13 +217218,13 @@ function resolveOfficialSupabaseCommand(workdir, environment = process.env) {
|
|
|
216444
217218
|
}
|
|
216445
217219
|
return ["npx", "--yes", `supabase@${version}`];
|
|
216446
217220
|
}
|
|
216447
|
-
const localPackageEntry =
|
|
217221
|
+
const localPackageEntry = join7(resolve9(workdir), "node_modules", "supabase", "dist", "supabase.js");
|
|
216448
217222
|
if (existsSync7(localPackageEntry))
|
|
216449
217223
|
return [process.execPath, localPackageEntry];
|
|
216450
217224
|
return ["supabase"];
|
|
216451
217225
|
}
|
|
216452
217226
|
function resolveExistingWorkdir(workdirInput, fallback) {
|
|
216453
|
-
const workdir =
|
|
217227
|
+
const workdir = resolve9(workdirInput || fallback);
|
|
216454
217228
|
if (!existsSync7(workdir) || !statSync5(workdir).isDirectory()) {
|
|
216455
217229
|
throw new Error(`Supabase workdir not found: ${workdir}`);
|
|
216456
217230
|
}
|
|
@@ -216551,7 +217325,7 @@ async function executeMigrationPush(request, runtime) {
|
|
|
216551
217325
|
if (!projectRef)
|
|
216552
217326
|
return missingProjectRefResult();
|
|
216553
217327
|
const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
|
|
216554
|
-
const migrationDirectory =
|
|
217328
|
+
const migrationDirectory = resolve9(workdir, request.dir || "supabase/migrations");
|
|
216555
217329
|
const migrationResponse = await pushMigrations({
|
|
216556
217330
|
action: "push_migrations",
|
|
216557
217331
|
ref: projectRef,
|
|
@@ -216664,11 +217438,11 @@ function registerSupabaseCliTools(server2, options = {}) {
|
|
|
216664
217438
|
|
|
216665
217439
|
// src/shared/tools/lite-cli-tools.ts
|
|
216666
217440
|
import { existsSync as existsSync9, statSync as statSync7 } from "node:fs";
|
|
216667
|
-
import { resolve as
|
|
217441
|
+
import { resolve as resolve11 } from "node:path";
|
|
216668
217442
|
|
|
216669
217443
|
// src/shared/tools/lite-cli-command.ts
|
|
216670
217444
|
import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync6 } from "node:fs";
|
|
216671
|
-
import { isAbsolute as isAbsolute3, join as
|
|
217445
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve10 } from "node:path";
|
|
216672
217446
|
function isRecord3(value) {
|
|
216673
217447
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
216674
217448
|
}
|
|
@@ -216679,8 +217453,8 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
216679
217453
|
throw new Error("Invalid SUPACLOUD_LITE_CLI_BIN");
|
|
216680
217454
|
return [explicitBinary];
|
|
216681
217455
|
}
|
|
216682
|
-
const packageRoot =
|
|
216683
|
-
const manifestPath =
|
|
217456
|
+
const packageRoot = join8(resolve10(workdir), "node_modules", "@supacloud", "lite");
|
|
217457
|
+
const manifestPath = join8(packageRoot, "package.json");
|
|
216684
217458
|
if (!existsSync8(manifestPath))
|
|
216685
217459
|
return ["supacloud-lite"];
|
|
216686
217460
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
@@ -216691,7 +217465,7 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
216691
217465
|
if (typeof entry !== "string" || !entry.trim() || entry.includes("\x00") || isAbsolute3(entry)) {
|
|
216692
217466
|
throw new Error("Invalid installed @supacloud/lite bin");
|
|
216693
217467
|
}
|
|
216694
|
-
const executable =
|
|
217468
|
+
const executable = resolve10(packageRoot, entry);
|
|
216695
217469
|
const packageRelative = relative3(packageRoot, executable);
|
|
216696
217470
|
if (!packageRelative || packageRelative === ".." || packageRelative.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute3(packageRelative)) {
|
|
216697
217471
|
throw new Error("Installed @supacloud/lite bin must stay inside its package");
|
|
@@ -216703,7 +217477,7 @@ function resolveLiteCommand(workdir, environment = process.env) {
|
|
|
216703
217477
|
}
|
|
216704
217478
|
// src/shared/tools/lite-cli-tools.ts
|
|
216705
217479
|
function requireWorkdir(workdir, fallback) {
|
|
216706
|
-
const resolved =
|
|
217480
|
+
const resolved = resolve11(workdir || fallback);
|
|
216707
217481
|
if (!existsSync9(resolved) || !statSync7(resolved).isDirectory()) {
|
|
216708
217482
|
throw new Error(`Lite workdir not found: ${resolved}`);
|
|
216709
217483
|
}
|
|
@@ -216917,13 +217691,13 @@ import {
|
|
|
216917
217691
|
rmSync as rmSync2
|
|
216918
217692
|
} from "node:fs";
|
|
216919
217693
|
import { homedir as homedir2 } from "node:os";
|
|
216920
|
-
import { dirname as dirname4, join as
|
|
217694
|
+
import { dirname as dirname4, join as join9, relative as relative4, resolve as resolve12, sep as sep3 } from "node:path";
|
|
216921
217695
|
import { fileURLToPath } from "node:url";
|
|
216922
217696
|
var SKILL_NAME = "supacloud-cli";
|
|
216923
217697
|
function regularFiles(rootDirectory, currentDirectory = rootDirectory) {
|
|
216924
217698
|
const files = [];
|
|
216925
217699
|
for (const directoryEntry of readdirSync4(currentDirectory, { withFileTypes: true })) {
|
|
216926
|
-
const entryPath =
|
|
217700
|
+
const entryPath = join9(currentDirectory, directoryEntry.name);
|
|
216927
217701
|
if (directoryEntry.isSymbolicLink())
|
|
216928
217702
|
throw new Error(`Skill directories cannot contain symlinks: ${entryPath}`);
|
|
216929
217703
|
if (directoryEntry.isDirectory())
|
|
@@ -216940,7 +217714,7 @@ function directoriesMatch(sourceDirectory, destinationDirectory) {
|
|
|
216940
217714
|
const destinationFiles = regularFiles(destinationDirectory);
|
|
216941
217715
|
if (sourceFiles.join("\x00") !== destinationFiles.join("\x00"))
|
|
216942
217716
|
return false;
|
|
216943
|
-
return sourceFiles.every((file) => readFileSync8(
|
|
217717
|
+
return sourceFiles.every((file) => readFileSync8(join9(sourceDirectory, file)).equals(readFileSync8(join9(destinationDirectory, file))));
|
|
216944
217718
|
}
|
|
216945
217719
|
function backupTimestamp(now) {
|
|
216946
217720
|
return now.toISOString().replace(/[-:.]/g, "");
|
|
@@ -216957,8 +217731,8 @@ function availableBackupDirectory(destinationDirectory, now) {
|
|
|
216957
217731
|
}
|
|
216958
217732
|
function stagedSkill(sourceDirectory, targetRoot) {
|
|
216959
217733
|
mkdirSync3(targetRoot, { recursive: true });
|
|
216960
|
-
const stagingRoot = mkdtempSync2(
|
|
216961
|
-
const stagingSkill =
|
|
217734
|
+
const stagingRoot = mkdtempSync2(join9(targetRoot, ".supacloud-cli-install-"));
|
|
217735
|
+
const stagingSkill = join9(stagingRoot, SKILL_NAME);
|
|
216962
217736
|
try {
|
|
216963
217737
|
cpSync(sourceDirectory, stagingSkill, { recursive: true, errorOnExist: true });
|
|
216964
217738
|
} catch (error) {
|
|
@@ -216990,13 +217764,13 @@ function replaceSkill(sourceDirectory, targetRoot, destinationDirectory, backupD
|
|
|
216990
217764
|
}
|
|
216991
217765
|
}
|
|
216992
217766
|
function skillSummary(request, action, files, backupDirectory) {
|
|
216993
|
-
const sourceDirectory =
|
|
216994
|
-
const targetRoot =
|
|
217767
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
217768
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
216995
217769
|
return {
|
|
216996
217770
|
name: SKILL_NAME,
|
|
216997
217771
|
sourceDirectory,
|
|
216998
217772
|
targetRoot,
|
|
216999
|
-
destinationDirectory:
|
|
217773
|
+
destinationDirectory: join9(targetRoot, SKILL_NAME),
|
|
217000
217774
|
action,
|
|
217001
217775
|
mode: request.mode,
|
|
217002
217776
|
changed: action !== "none",
|
|
@@ -217005,10 +217779,10 @@ function skillSummary(request, action, files, backupDirectory) {
|
|
|
217005
217779
|
};
|
|
217006
217780
|
}
|
|
217007
217781
|
function installSkill(request) {
|
|
217008
|
-
const sourceDirectory =
|
|
217009
|
-
const targetRoot =
|
|
217010
|
-
const destinationDirectory =
|
|
217011
|
-
if (!existsSync10(
|
|
217782
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
217783
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
217784
|
+
const destinationDirectory = join9(targetRoot, SKILL_NAME);
|
|
217785
|
+
if (!existsSync10(join9(sourceDirectory, "SKILL.md"))) {
|
|
217012
217786
|
throw new Error(`Bundled SupaCloud CLI skill not found: ${sourceDirectory}`);
|
|
217013
217787
|
}
|
|
217014
217788
|
const files = regularFiles(sourceDirectory);
|
|
@@ -217023,17 +217797,17 @@ function installSkill(request) {
|
|
|
217023
217797
|
return installReplacementSkill(request, files);
|
|
217024
217798
|
}
|
|
217025
217799
|
function installNewSkill(request, files) {
|
|
217026
|
-
const sourceDirectory =
|
|
217027
|
-
const targetRoot =
|
|
217800
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
217801
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
217028
217802
|
if (request.mode === "write") {
|
|
217029
|
-
createSkill(sourceDirectory, targetRoot,
|
|
217803
|
+
createSkill(sourceDirectory, targetRoot, join9(targetRoot, SKILL_NAME));
|
|
217030
217804
|
}
|
|
217031
217805
|
return skillSummary(request, "create", files, null);
|
|
217032
217806
|
}
|
|
217033
217807
|
function installReplacementSkill(request, files) {
|
|
217034
|
-
const sourceDirectory =
|
|
217035
|
-
const targetRoot =
|
|
217036
|
-
const destinationDirectory =
|
|
217808
|
+
const sourceDirectory = resolve12(request.sourceDirectory);
|
|
217809
|
+
const targetRoot = resolve12(request.targetRoot);
|
|
217810
|
+
const destinationDirectory = join9(targetRoot, SKILL_NAME);
|
|
217037
217811
|
const backupDirectory = availableBackupDirectory(destinationDirectory, request.now);
|
|
217038
217812
|
if (request.mode === "write") {
|
|
217039
217813
|
replaceSkill(sourceDirectory, targetRoot, destinationDirectory, backupDirectory);
|
|
@@ -217042,15 +217816,15 @@ function installReplacementSkill(request, files) {
|
|
|
217042
217816
|
}
|
|
217043
217817
|
function resolveDefaultCodexSkillRoot(environment = process.env, homeDirectory = homedir2()) {
|
|
217044
217818
|
const codexHome = environment.CODEX_HOME?.trim();
|
|
217045
|
-
return
|
|
217819
|
+
return join9(resolve12(codexHome || join9(homeDirectory, ".codex")), "skills");
|
|
217046
217820
|
}
|
|
217047
217821
|
function resolveBundledSkillDirectory(moduleUrl = import.meta.url) {
|
|
217048
217822
|
const moduleDirectory = dirname4(fileURLToPath(moduleUrl));
|
|
217049
217823
|
const candidates = [
|
|
217050
|
-
|
|
217051
|
-
|
|
217824
|
+
resolve12(moduleDirectory, "../../../skills", SKILL_NAME),
|
|
217825
|
+
resolve12(moduleDirectory, "../skills", SKILL_NAME)
|
|
217052
217826
|
];
|
|
217053
|
-
const skillDirectory = candidates.find((candidate) => existsSync10(
|
|
217827
|
+
const skillDirectory = candidates.find((candidate) => existsSync10(join9(candidate, "SKILL.md")));
|
|
217054
217828
|
if (!skillDirectory)
|
|
217055
217829
|
throw new Error("Bundled SupaCloud CLI skill is missing from this installation");
|
|
217056
217830
|
return skillDirectory;
|
|
@@ -217072,7 +217846,7 @@ function registerAiTools(server2) {
|
|
|
217072
217846
|
name: SKILL_NAME,
|
|
217073
217847
|
sourceDirectory,
|
|
217074
217848
|
defaultTargetRoot,
|
|
217075
|
-
defaultDestination:
|
|
217849
|
+
defaultDestination: join9(defaultTargetRoot, SKILL_NAME)
|
|
217076
217850
|
});
|
|
217077
217851
|
}
|
|
217078
217852
|
return textResponse(installSkill({
|
|
@@ -217087,13 +217861,13 @@ function registerAiTools(server2) {
|
|
|
217087
217861
|
|
|
217088
217862
|
// src/shared/tools/app-tools.ts
|
|
217089
217863
|
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
217090
|
-
import { mkdir as
|
|
217091
|
-
import { basename as
|
|
217864
|
+
import { mkdir as mkdir7, readFile as readFile9, readdir as readdir7, writeFile as writeFile6 } from "node:fs/promises";
|
|
217865
|
+
import { basename as basename10, dirname as dirname13, join as join17, relative as relative13, resolve as resolve24 } from "node:path";
|
|
217092
217866
|
|
|
217093
217867
|
// node_modules/@supacloud/compiler/dist/index.js
|
|
217094
217868
|
import { dirname as dirname22, relative as relative6, sep as sep4 } from "node:path";
|
|
217095
217869
|
import { createHash as createHash42 } from "node:crypto";
|
|
217096
|
-
import { access, mkdir as
|
|
217870
|
+
import { access, mkdir as mkdir4, rename as rename2, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
217097
217871
|
import { join as join22 } from "node:path";
|
|
217098
217872
|
var ts10 = __toESM(require_typescript(), 1);
|
|
217099
217873
|
var ts11 = __toESM(require_typescript(), 1);
|
|
@@ -217103,7 +217877,7 @@ import { resolve as resolve52 } from "node:path";
|
|
|
217103
217877
|
import { resolve as resolve62 } from "node:path";
|
|
217104
217878
|
import { createHash as createHash52 } from "node:crypto";
|
|
217105
217879
|
import { createHash as createHash62 } from "node:crypto";
|
|
217106
|
-
import { readFile as
|
|
217880
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
217107
217881
|
import { relative as relative42, sep as sep42 } from "node:path";
|
|
217108
217882
|
|
|
217109
217883
|
// node_modules/@graphql-codegen/core/esm/codegen.js
|
|
@@ -219494,7 +220268,7 @@ var findTransformedDefinitions = (visitedResult) => {
|
|
|
219494
220268
|
// node_modules/@supacloud/compiler/dist/index.js
|
|
219495
220269
|
import { createHash as createHash8 } from "node:crypto";
|
|
219496
220270
|
import { constants as constants2 } from "node:fs";
|
|
219497
|
-
import { lstat as lstat2, mkdir as mkdir22, open as open3, readFile as
|
|
220271
|
+
import { lstat as lstat2, mkdir as mkdir22, open as open3, readFile as readFile32, readdir as readdir4, realpath as realpath3, rename as rename22, rm as rm2, writeFile as writeFile22 } from "node:fs/promises";
|
|
219498
220272
|
import { dirname as dirname42, isAbsolute as isAbsolute5, join as join52, relative as relative52, resolve as resolve82, sep as sep5 } from "node:path";
|
|
219499
220273
|
import { existsSync as existsSync32 } from "node:fs";
|
|
219500
220274
|
import { dirname as dirname52, join as join62, relative as relative62, sep as sep6 } from "node:path";
|
|
@@ -219513,9 +220287,9 @@ var ts3 = __toESM(require_typescript(), 1);
|
|
|
219513
220287
|
var ts5 = __toESM(require_typescript(), 1);
|
|
219514
220288
|
import { createHash as createHash32 } from "node:crypto";
|
|
219515
220289
|
import { relative as relative22, resolve as resolvePath, sep as sep22 } from "node:path";
|
|
219516
|
-
import { createHash as
|
|
220290
|
+
import { createHash as createHash23 } from "node:crypto";
|
|
219517
220291
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
219518
|
-
import { dirname as dirname6, join as
|
|
220292
|
+
import { dirname as dirname6, join as join12, resolve as resolve15 } from "node:path";
|
|
219519
220293
|
import { createHash as createHash7 } from "node:crypto";
|
|
219520
220294
|
import { resolve as resolve22 } from "node:path";
|
|
219521
220295
|
var ts9 = __toESM(require_typescript(), 1);
|
|
@@ -219538,7 +220312,7 @@ import { existsSync as existsSync42, watch } from "node:fs";
|
|
|
219538
220312
|
import { dirname as dirname9, relative as relative10, resolve as resolve16, sep as sep10 } from "node:path";
|
|
219539
220313
|
import { createHash as createHash10 } from "node:crypto";
|
|
219540
220314
|
import { access as access3, readdir as readdir32, readFile as readFile7 } from "node:fs/promises";
|
|
219541
|
-
import { isAbsolute as isAbsolute42, relative as relative9, resolve as
|
|
220315
|
+
import { isAbsolute as isAbsolute42, relative as relative9, resolve as resolve152, sep as sep9 } from "node:path";
|
|
219542
220316
|
var ts16 = __toESM(require_typescript(), 1);
|
|
219543
220317
|
import { existsSync as existsSync52 } from "node:fs";
|
|
219544
220318
|
import { join as join112, resolve as resolve21 } from "node:path";
|
|
@@ -220993,7 +221767,7 @@ function renderApplication(graph, options) {
|
|
|
220993
221767
|
};
|
|
220994
221768
|
}
|
|
220995
221769
|
async function writeRenderedApplication(rendered, options) {
|
|
220996
|
-
await
|
|
221770
|
+
await mkdir4(options.outDir, { recursive: true });
|
|
220997
221771
|
const applicationPath = join22(options.outDir, "application.ts");
|
|
220998
221772
|
const manifestPath = join22(options.outDir, "app.manifest.json");
|
|
220999
221773
|
const contractsManifestPath = join22(options.outDir, "contracts.manifest.json");
|
|
@@ -221035,7 +221809,7 @@ async function writeFileAtomic(path, content) {
|
|
|
221035
221809
|
if (typeof Bun !== "undefined" && typeof Bun.write === "function") {
|
|
221036
221810
|
await Bun.write(temporaryPath, content);
|
|
221037
221811
|
} else {
|
|
221038
|
-
await
|
|
221812
|
+
await writeFile4(temporaryPath, content, "utf8");
|
|
221039
221813
|
}
|
|
221040
221814
|
await rename2(temporaryPath, path);
|
|
221041
221815
|
} catch (error) {
|
|
@@ -223767,7 +224541,7 @@ async function renderGraphql(options) {
|
|
|
223767
224541
|
let schema;
|
|
223768
224542
|
let schemaContent;
|
|
223769
224543
|
try {
|
|
223770
|
-
schemaContent = await
|
|
224544
|
+
schemaContent = await readFile3(schemaPath, "utf8");
|
|
223771
224545
|
Object.assign(contract, graphqlSchemaHashes(schemaContent));
|
|
223772
224546
|
if (schemaPath.endsWith(".json")) {
|
|
223773
224547
|
const json = JSON.parse(schemaContent);
|
|
@@ -223786,7 +224560,7 @@ async function renderGraphql(options) {
|
|
|
223786
224560
|
const documents = [];
|
|
223787
224561
|
for (const path of paths.slice(1)) {
|
|
223788
224562
|
try {
|
|
223789
|
-
documents.push({ location: path, document: import_graphql64.parse(new import_graphql64.Source(await
|
|
224563
|
+
documents.push({ location: path, document: import_graphql64.parse(new import_graphql64.Source(await readFile3(path, "utf8"), path)) });
|
|
223790
224564
|
} catch (error) {
|
|
223791
224565
|
diagnostic("graphql-document-invalid", error, path);
|
|
223792
224566
|
}
|
|
@@ -232984,7 +233758,7 @@ async function readOwned(anchor, path) {
|
|
|
232984
233758
|
const stat = await lstat2(path);
|
|
232985
233759
|
if (!stat.isFile())
|
|
232986
233760
|
throw new Error("Expected a regular delivery input or artifact.");
|
|
232987
|
-
return
|
|
233761
|
+
return readFile32(path);
|
|
232988
233762
|
}
|
|
232989
233763
|
async function readBoundedOwned2(root, path, maximum) {
|
|
232990
233764
|
await noLinks2(root, path);
|
|
@@ -233021,7 +233795,7 @@ async function reserveOutput(project, requested) {
|
|
|
233021
233795
|
const marker = join52(path, "owner.json");
|
|
233022
233796
|
const owner = canonical2({ producer: "@supacloud/compiler/delivery-build-v1", project: base });
|
|
233023
233797
|
if (!await exists2(marker)) {
|
|
233024
|
-
if ((await
|
|
233798
|
+
if ((await readdir4(path)).length > 0)
|
|
233025
233799
|
throw new Error("Refusing to adopt an unowned delivery directory.");
|
|
233026
233800
|
await writeFile22(marker, owner, { flag: "wx" });
|
|
233027
233801
|
} else if (new TextDecoder().decode(await readOwned(base, marker)) !== owner) {
|
|
@@ -233049,7 +233823,7 @@ async function writeArtifact(root, path, content) {
|
|
|
233049
233823
|
async function artifactInventory(root) {
|
|
233050
233824
|
const files = [];
|
|
233051
233825
|
async function visit(directory) {
|
|
233052
|
-
for (const entry of await
|
|
233826
|
+
for (const entry of await readdir4(directory, { withFileTypes: true })) {
|
|
233053
233827
|
const path = join52(directory, entry.name);
|
|
233054
233828
|
if (entry.isSymbolicLink())
|
|
233055
233829
|
throw new Error("Artifact contains a symbolic link.");
|
|
@@ -233067,7 +233841,7 @@ async function artifactInventory(root) {
|
|
|
233067
233841
|
async function sourceInventory(root, output) {
|
|
233068
233842
|
const inputs = new Map;
|
|
233069
233843
|
async function visit(directory) {
|
|
233070
|
-
for (const entry of await
|
|
233844
|
+
for (const entry of await readdir4(directory, { withFileTypes: true })) {
|
|
233071
233845
|
const path = join52(directory, entry.name);
|
|
233072
233846
|
if (inside2(output, path) || entry.name === "node_modules" || entry.name === ".git")
|
|
233073
233847
|
continue;
|
|
@@ -242899,7 +243673,7 @@ var init_application_activation = __esm2(() => {
|
|
|
242899
243673
|
}, { additionalProperties: false });
|
|
242900
243674
|
});
|
|
242901
243675
|
var closed6;
|
|
242902
|
-
var
|
|
243676
|
+
var sha2563;
|
|
242903
243677
|
var isoDate2;
|
|
242904
243678
|
var status2;
|
|
242905
243679
|
var evidenceStatus2;
|
|
@@ -242914,7 +243688,7 @@ var init_deployment_evidence = __esm2(() => {
|
|
|
242914
243688
|
init_application_configuration();
|
|
242915
243689
|
init_application_release();
|
|
242916
243690
|
closed6 = { additionalProperties: false };
|
|
242917
|
-
|
|
243691
|
+
sha2563 = String22({ pattern: "^[a-f0-9]{64}$" });
|
|
242918
243692
|
isoDate2 = String22({ minLength: 20, maxLength: 40 });
|
|
242919
243693
|
status2 = Union2([
|
|
242920
243694
|
Literal2("confirmed"),
|
|
@@ -242933,7 +243707,7 @@ var init_deployment_evidence = __esm2(() => {
|
|
|
242933
243707
|
topology: Union2([Literal2("single-node"), Literal2("distributed")]),
|
|
242934
243708
|
migration: _Object_2({
|
|
242935
243709
|
status: status2,
|
|
242936
|
-
inventory_sha256: Union2([
|
|
243710
|
+
inventory_sha256: Union2([sha2563, Null2()]),
|
|
242937
243711
|
compatibility: Union2([
|
|
242938
243712
|
Literal2("verified"),
|
|
242939
243713
|
Literal2("not-proven"),
|
|
@@ -242981,9 +243755,9 @@ var init_deployment_evidence = __esm2(() => {
|
|
|
242981
243755
|
}, closed6),
|
|
242982
243756
|
source: _Object_2({
|
|
242983
243757
|
commit_sha: Union2([String22({ pattern: "^[0-9a-f]{7,64}$" }), Null2()]),
|
|
242984
|
-
manifest_sha256:
|
|
243758
|
+
manifest_sha256: sha2563,
|
|
242985
243759
|
contract_schema: Union2([String22({ minLength: 1, maxLength: 128 }), Null2()]),
|
|
242986
|
-
environment_binding_version: Union2([
|
|
243760
|
+
environment_binding_version: Union2([sha2563, Null2()])
|
|
242987
243761
|
}, closed6),
|
|
242988
243762
|
database: DatabaseProviderSchema2,
|
|
242989
243763
|
components: _Array_2(DeploymentComponentEvidenceSchema2, { minItems: 1, maxItems: 32 }),
|
|
@@ -243487,7 +244261,7 @@ function decoratedDeclaration(decorator) {
|
|
|
243487
244261
|
};
|
|
243488
244262
|
}
|
|
243489
244263
|
function createIncrementalProgramSession(projectRoot) {
|
|
243490
|
-
const rootDir =
|
|
244264
|
+
const rootDir = resolve15(projectRoot);
|
|
243491
244265
|
let projectConfig = readProjectConfig(rootDir);
|
|
243492
244266
|
let projectConfigKey = configKey(projectConfig);
|
|
243493
244267
|
let builder;
|
|
@@ -243516,8 +244290,8 @@ function createIncrementalProgramSession(projectRoot) {
|
|
|
243516
244290
|
}
|
|
243517
244291
|
projectConfig = nextProjectConfig;
|
|
243518
244292
|
projectConfigKey = nextProjectConfigKey;
|
|
243519
|
-
const normalizedRoots = [...new Set(rootNames.map((file) =>
|
|
243520
|
-
const normalizedChanged = [...new Set(changedPaths.map((file) =>
|
|
244293
|
+
const normalizedRoots = [...new Set(rootNames.map((file) => resolve15(rootDir, file)))].sort();
|
|
244294
|
+
const normalizedChanged = [...new Set(changedPaths.map((file) => resolve15(rootDir, file)))];
|
|
243521
244295
|
const invalidatedPaths = new Set(normalizedChanged.map(canonical3));
|
|
243522
244296
|
for (const sourceFile of oldSourceFiles.values()) {
|
|
243523
244297
|
if (sourceVersion(sourceFile.fileName) !== sourceFileVersion(sourceFile)) {
|
|
@@ -243618,7 +244392,7 @@ function createHost(options, rootDir, sourceFileCache, invalidatedPaths, invalid
|
|
|
243618
244392
|
return host;
|
|
243619
244393
|
}
|
|
243620
244394
|
function canonical3(fileName) {
|
|
243621
|
-
const normalized =
|
|
244395
|
+
const normalized = resolve15(fileName);
|
|
243622
244396
|
return ts2.sys.useCaseSensitiveFileNames ? normalized : normalized.toLowerCase();
|
|
243623
244397
|
}
|
|
243624
244398
|
function sourceFileParseKey(languageVersion) {
|
|
@@ -243636,7 +244410,7 @@ function configKey(config) {
|
|
|
243636
244410
|
});
|
|
243637
244411
|
}
|
|
243638
244412
|
function hashText(text) {
|
|
243639
|
-
return
|
|
244413
|
+
return createHash23("sha1").update(text).digest("hex");
|
|
243640
244414
|
}
|
|
243641
244415
|
function sourceVersion(fileName) {
|
|
243642
244416
|
try {
|
|
@@ -243650,7 +244424,7 @@ function sourceFileVersion(sourceFile) {
|
|
|
243650
244424
|
return typeof descriptor?.value === "string" ? descriptor.value : undefined;
|
|
243651
244425
|
}
|
|
243652
244426
|
function readProjectConfig(rootDir) {
|
|
243653
|
-
const configPath =
|
|
244427
|
+
const configPath = join12(rootDir, "tsconfig.json");
|
|
243654
244428
|
if (!existsSync11(configPath)) {
|
|
243655
244429
|
return {
|
|
243656
244430
|
options: {
|
|
@@ -243960,7 +244734,7 @@ function withDiagnosticMetadata(diagnostic) {
|
|
|
243960
244734
|
function nodeText(node) {
|
|
243961
244735
|
return node.getText(node.getSourceFile());
|
|
243962
244736
|
}
|
|
243963
|
-
function
|
|
244737
|
+
function lineOf2(node) {
|
|
243964
244738
|
const sourceFile = node.getSourceFile();
|
|
243965
244739
|
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
243966
244740
|
}
|
|
@@ -244054,7 +244828,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
244054
244828
|
options,
|
|
244055
244829
|
className: cls.name?.text ?? "<anonymous>",
|
|
244056
244830
|
file: sf.fileName,
|
|
244057
|
-
line:
|
|
244831
|
+
line: lineOf2(cls)
|
|
244058
244832
|
});
|
|
244059
244833
|
}
|
|
244060
244834
|
}
|
|
@@ -244073,7 +244847,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
244073
244847
|
options: arg,
|
|
244074
244848
|
className: variableName3(parent),
|
|
244075
244849
|
file: sf.fileName,
|
|
244076
|
-
line:
|
|
244850
|
+
line: lineOf2(parent)
|
|
244077
244851
|
});
|
|
244078
244852
|
}
|
|
244079
244853
|
}
|
|
@@ -244184,7 +244958,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
244184
244958
|
if (injectable?.providedIn === "root") {
|
|
244185
244959
|
const { deps, optionalDeps, selfDeps, skipSelfDeps, hostDeps, functionalInjects, missing } = classDeps(classInfo.decl, ctx);
|
|
244186
244960
|
const file = sourcePath(ctx.rootDir, classInfo.file);
|
|
244187
|
-
const line =
|
|
244961
|
+
const line = lineOf2(classInfo.decl);
|
|
244188
244962
|
if (missing) {
|
|
244189
244963
|
warn(ctx, "missing-deps", `root provider ${name} 的部分构造依赖无法静态解析`, file, line);
|
|
244190
244964
|
}
|
|
@@ -244231,8 +245005,8 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
244231
245005
|
const rpc = checkedRpc(meta, ctx);
|
|
244232
245006
|
const audit = stringLiteralProp(meta, "audit");
|
|
244233
245007
|
const standaloneName = stringLiteralProp(meta, "name") ?? classInfo.decl.name?.text ?? name;
|
|
244234
|
-
const standaloneUses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file),
|
|
244235
|
-
rejectUndeclaredResourceUses(standaloneUses, standaloneResourceNames, ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file),
|
|
245008
|
+
const standaloneUses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file), lineOf2(classInfo.decl));
|
|
245009
|
+
rejectUndeclaredResourceUses(standaloneUses, standaloneResourceNames, ctx, `command ${standaloneName}`, sourcePath(ctx.rootDir, classInfo.file), lineOf2(classInfo.decl));
|
|
244236
245010
|
standaloneCommands.push({
|
|
244237
245011
|
className: classInfo.decl.name?.text ?? name,
|
|
244238
245012
|
name: standaloneName,
|
|
@@ -244432,7 +245206,7 @@ function parseTokenVariable(decl, file) {
|
|
|
244432
245206
|
if (nodeText(init.expression) !== "InjectionToken")
|
|
244433
245207
|
return;
|
|
244434
245208
|
const [nameArg, optionsArg] = init.arguments ?? [];
|
|
244435
|
-
const info = { name: variableName3(decl), file, line:
|
|
245209
|
+
const info = { name: variableName3(decl), file, line: lineOf2(decl) };
|
|
244436
245210
|
if (nameArg && ts7.isStringLiteral(nameArg)) {
|
|
244437
245211
|
info.stringName = nameArg.text;
|
|
244438
245212
|
}
|
|
@@ -244488,7 +245262,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
244488
245262
|
}
|
|
244489
245263
|
if (ts7.isCallExpression(el)) {
|
|
244490
245264
|
const helper = nodeText(el.expression).split(".").pop() ?? nodeText(el.expression);
|
|
244491
|
-
warn(ctx, "unsupported-provider-helper", `无法静态展开 provider helper '${helper}';请改用显式 Provider 或实现编译器支持的 helper`, sourcePath(ctx.rootDir, el.getSourceFile().fileName),
|
|
245265
|
+
warn(ctx, "unsupported-provider-helper", `无法静态展开 provider helper '${helper}';请改用显式 Provider 或实现编译器支持的 helper`, sourcePath(ctx.rootDir, el.getSourceFile().fileName), lineOf2(el));
|
|
244492
245266
|
continue;
|
|
244493
245267
|
}
|
|
244494
245268
|
const provider = parseProvider(el, exportsSet, ctx);
|
|
@@ -244524,7 +245298,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
244524
245298
|
...hasInitHook(decl) ? { hasOnInit: true } : {},
|
|
244525
245299
|
exported: exportsSet.has(className),
|
|
244526
245300
|
file: sourcePath(ctx.rootDir, decl.getSourceFile().fileName),
|
|
244527
|
-
line:
|
|
245301
|
+
line: lineOf2(decl),
|
|
244528
245302
|
importPath: modulePath(ctx.rootDir, decl.getSourceFile().fileName)
|
|
244529
245303
|
});
|
|
244530
245304
|
}
|
|
@@ -244571,8 +245345,8 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
244571
245345
|
const rpc = checkedRpc(meta, ctx);
|
|
244572
245346
|
const audit = stringLiteralProp(meta, "audit");
|
|
244573
245347
|
const commandName = stringLiteralProp(meta, "name") ?? cls.name?.text ?? "<anonymous>";
|
|
244574
|
-
const uses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName),
|
|
244575
|
-
rejectUndeclaredResourceUses(uses, moduleResourceNames, ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName),
|
|
245348
|
+
const uses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf2(cls));
|
|
245349
|
+
rejectUndeclaredResourceUses(uses, moduleResourceNames, ctx, `command ${commandName}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf2(cls));
|
|
244576
245350
|
commands.push({
|
|
244577
245351
|
className: cls.name?.text ?? "<anonymous>",
|
|
244578
245352
|
name: commandName,
|
|
@@ -244601,7 +245375,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
244601
245375
|
code: "invalid-job-scope",
|
|
244602
245376
|
message: `job ${className} 不能使用 request scope;Job 只能使用 application 或 job scope`,
|
|
244603
245377
|
file: sourcePath(ctx.rootDir, cls.getSourceFile().fileName),
|
|
244604
|
-
line:
|
|
245378
|
+
line: lineOf2(cls),
|
|
244605
245379
|
suggestion: "移除 request scope,或改用 application/job scope。",
|
|
244606
245380
|
errorCode: "SC4007",
|
|
244607
245381
|
docsUrl: "https://supacloud.dev/errors/SC4007"
|
|
@@ -244609,8 +245383,8 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
244609
245383
|
}
|
|
244610
245384
|
const aspects = parseAspectRefs(getProp(meta, "aspects"), ctx, `job ${className}`);
|
|
244611
245385
|
const contract = parseJobOptions(meta, className, ctx);
|
|
244612
|
-
const jobUses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `job ${stringLiteralProp(meta, "name") ?? className}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName),
|
|
244613
|
-
rejectUndeclaredResourceUses(jobUses, moduleResourceNames, ctx, `job ${stringLiteralProp(meta, "name") ?? className}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName),
|
|
245386
|
+
const jobUses = parseInfraResourceUseRefs(infraResourceProp(meta, "uses", ctx), ctx, `job ${stringLiteralProp(meta, "name") ?? className}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf2(cls));
|
|
245387
|
+
rejectUndeclaredResourceUses(jobUses, moduleResourceNames, ctx, `job ${stringLiteralProp(meta, "name") ?? className}`, sourcePath(ctx.rootDir, cls.getSourceFile().fileName), lineOf2(cls));
|
|
244614
245388
|
jobs.push({
|
|
244615
245389
|
className,
|
|
244616
245390
|
name: stringLiteralProp(meta, "name") ?? className,
|
|
@@ -244676,7 +245450,7 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
244676
245450
|
code: "invalid-feature-spec",
|
|
244677
245451
|
message: "Feature spec must use static name, states and transition objects.",
|
|
244678
245452
|
file: sourcePath(ctx.rootDir, input.getSourceFile().fileName),
|
|
244679
|
-
line:
|
|
245453
|
+
line: lineOf2(input)
|
|
244680
245454
|
});
|
|
244681
245455
|
return;
|
|
244682
245456
|
};
|
|
@@ -244722,7 +245496,7 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
244722
245496
|
...audit === undefined ? {} : { audit }
|
|
244723
245497
|
});
|
|
244724
245498
|
}
|
|
244725
|
-
return { name, states, transitions, file: sourcePath(ctx.rootDir, input.getSourceFile().fileName), line:
|
|
245499
|
+
return { name, states, transitions, file: sourcePath(ctx.rootDir, input.getSourceFile().fileName), line: lineOf2(input) };
|
|
244726
245500
|
}
|
|
244727
245501
|
function resolveStaticObjectLiteral(input, ctx, seen = new Set) {
|
|
244728
245502
|
if (!input || seen.has(input))
|
|
@@ -244779,7 +245553,7 @@ function checkedCommandMode(object, property, ctx, command) {
|
|
|
244779
245553
|
docsUrl: "https://supacloud.dev/errors/SC4012",
|
|
244780
245554
|
message: `${command}.${property} must be the explicit literal "required" or "none"; invalid governance cannot be disabled silently.`,
|
|
244781
245555
|
file,
|
|
244782
|
-
line:
|
|
245556
|
+
line: lineOf2(expression),
|
|
244783
245557
|
suggestion: `Choose the intended ${property} policy explicitly; use set_command_mode with value "required" or "none".`,
|
|
244784
245558
|
fix: {
|
|
244785
245559
|
type: "set_command_mode",
|
|
@@ -244794,7 +245568,7 @@ function checkedCommandMode(object, property, ctx, command) {
|
|
|
244794
245568
|
}
|
|
244795
245569
|
function parseProvider(el, exportsSet, ctx) {
|
|
244796
245570
|
const file = sourcePath(ctx.rootDir, el.getSourceFile().fileName);
|
|
244797
|
-
const line =
|
|
245571
|
+
const line = lineOf2(el);
|
|
244798
245572
|
const unwrappedEl = unwrapForwardRef(el);
|
|
244799
245573
|
if (ts7.isIdentifier(unwrappedEl)) {
|
|
244800
245574
|
const decl = resolveDeclaration(unwrappedEl, ctx)[0];
|
|
@@ -244998,7 +245772,7 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
244998
245772
|
const helper = nodeText(expression.expression).split(".").pop();
|
|
244999
245773
|
const args = expression.arguments;
|
|
245000
245774
|
const file = sourcePath(ctx.rootDir, expression.getSourceFile().fileName);
|
|
245001
|
-
const line =
|
|
245775
|
+
const line = lineOf2(expression);
|
|
245002
245776
|
if (helper === "provideToken") {
|
|
245003
245777
|
const tokenExpr = args[0];
|
|
245004
245778
|
const valueExpr = args[1];
|
|
@@ -245238,7 +246012,7 @@ function parseController(input, ctx) {
|
|
|
245238
246012
|
const { deps, optionalDeps, selfDeps, skipSelfDeps, hostDeps, functionalInjects, missing } = classDeps(decl, ctx);
|
|
245239
246013
|
const file = sourcePath(ctx.rootDir, decl.getSourceFile().fileName);
|
|
245240
246014
|
if (missing) {
|
|
245241
|
-
warn(ctx, "missing-deps", `controller ${decl.name?.text} 的部分构造依赖无法静态解析`, file,
|
|
246015
|
+
warn(ctx, "missing-deps", `controller ${decl.name?.text} 的部分构造依赖无法静态解析`, file, lineOf2(decl));
|
|
245242
246016
|
}
|
|
245243
246017
|
const injectable = parseInjectableOptions(decl, ctx);
|
|
245244
246018
|
const routes = [];
|
|
@@ -245472,7 +246246,7 @@ function parseController(input, ctx) {
|
|
|
245472
246246
|
errorCode: "SC3026",
|
|
245473
246247
|
docsUrl: "https://supacloud.dev/errors/SC3026",
|
|
245474
246248
|
file,
|
|
245475
|
-
line:
|
|
246249
|
+
line: lineOf2(optionsObject),
|
|
245476
246250
|
message: `Route ${route.handler} declares both response and responses; choose the status-map responses field.`,
|
|
245477
246251
|
suggestion: "Remove response and represent the successful response as responses: { 200: Schema }."
|
|
245478
246252
|
});
|
|
@@ -245521,7 +246295,7 @@ function parseController(input, ctx) {
|
|
|
245521
246295
|
errorCode: "SC3025",
|
|
245522
246296
|
docsUrl: "https://supacloud.dev/errors/SC3025",
|
|
245523
246297
|
file,
|
|
245524
|
-
line:
|
|
246298
|
+
line: lineOf2(property.name),
|
|
245525
246299
|
message: `Route ${route.handler} uses unsupported response selector "${status}". Use an HTTP status (100-599), a status family such as 4XX/5XX, or default.`
|
|
245526
246300
|
});
|
|
245527
246301
|
continue;
|
|
@@ -245535,7 +246309,7 @@ function parseController(input, ctx) {
|
|
|
245535
246309
|
errorCode: "SC3027",
|
|
245536
246310
|
docsUrl: "https://supacloud.dev/errors/SC3027",
|
|
245537
246311
|
file,
|
|
245538
|
-
line:
|
|
246312
|
+
line: lineOf2(property.name),
|
|
245539
246313
|
message: `Route ${route.handler} declares duplicate response selectors "${existingSelector}" and "${status}".`,
|
|
245540
246314
|
suggestion: `Keep one ${canonical} selector so runtime, clients and OpenAPI choose the same schema.`
|
|
245541
246315
|
});
|
|
@@ -245725,7 +246499,7 @@ function jobOptionError(ctx, code, message, node, errorCode, suggestion) {
|
|
|
245725
246499
|
code,
|
|
245726
246500
|
message,
|
|
245727
246501
|
file: sourcePath(ctx.rootDir, node.getSourceFile().fileName),
|
|
245728
|
-
line:
|
|
246502
|
+
line: lineOf2(node),
|
|
245729
246503
|
...suggestion === undefined ? {} : { suggestion },
|
|
245730
246504
|
errorCode,
|
|
245731
246505
|
docsUrl: `https://supacloud.dev/errors/${errorCode}`
|
|
@@ -245774,7 +246548,7 @@ function checkedRpc(meta, ctx) {
|
|
|
245774
246548
|
severity: "error",
|
|
245775
246549
|
code: "invalid-command-rpc",
|
|
245776
246550
|
file: sourcePath(ctx.rootDir, meta.getSourceFile().fileName),
|
|
245777
|
-
line:
|
|
246551
|
+
line: lineOf2(meta),
|
|
245778
246552
|
message: "Command rpc must be a non-empty string literal identifying a configured adapter.",
|
|
245779
246553
|
suggestion: "Declare a named RPC adapter and its tested persistence capabilities."
|
|
245780
246554
|
});
|
|
@@ -246090,7 +246864,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
246090
246864
|
code: "dynamic-aspect-reference",
|
|
246091
246865
|
message: `${owner} 的 aspects 必须是显式数组字面量,并且每一项必须是可解析的函数引用`,
|
|
246092
246866
|
file: sourcePath(ctx.rootDir, expression.getSourceFile().fileName),
|
|
246093
|
-
line:
|
|
246867
|
+
line: lineOf2(expression),
|
|
246094
246868
|
suggestion: "使用 aspects: [auditAspect, transactionAspect],不要使用变量、调用表达式或字符串 pointcut。",
|
|
246095
246869
|
errorCode: "SC4010",
|
|
246096
246870
|
docsUrl: "https://supacloud.dev/errors/SC4010"
|
|
@@ -246105,7 +246879,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
246105
246879
|
code: "dynamic-aspect-reference",
|
|
246106
246880
|
message: `${owner} 的 aspects 只能包含显式的函数标识符引用,无法静态编译 '${nodeText(element)}'`,
|
|
246107
246881
|
file: sourcePath(ctx.rootDir, element.getSourceFile().fileName),
|
|
246108
|
-
line:
|
|
246882
|
+
line: lineOf2(element),
|
|
246109
246883
|
suggestion: "将 aspect 直接写入数组,例如 aspects: [auditAspect]。",
|
|
246110
246884
|
errorCode: "SC4010",
|
|
246111
246885
|
docsUrl: "https://supacloud.dev/errors/SC4010"
|
|
@@ -246119,7 +246893,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
246119
246893
|
code: "invalid-aspect-reference",
|
|
246120
246894
|
message: `${owner} 引用了 '${element.text}',但它不是可静态解析的 aspect 函数`,
|
|
246121
246895
|
file: sourcePath(ctx.rootDir, element.getSourceFile().fileName),
|
|
246122
|
-
line:
|
|
246896
|
+
line: lineOf2(element),
|
|
246123
246897
|
suggestion: "aspect 必须是函数声明、箭头函数或函数表达式的直接引用。",
|
|
246124
246898
|
errorCode: "SC4011",
|
|
246125
246899
|
docsUrl: "https://supacloud.dev/errors/SC4011"
|
|
@@ -246244,7 +247018,7 @@ function infraResourceProp(obj, name, ctx) {
|
|
|
246244
247018
|
return;
|
|
246245
247019
|
const member = members[0];
|
|
246246
247020
|
if (members.length !== 1 || !ts7.isPropertyAssignment(member) || ts7.isComputedPropertyName(member.name) || obj.properties.some(ts7.isSpreadAssignment)) {
|
|
246247
|
-
pushResourceError(ctx, "unknown-resource", `${name} 必须是唯一、显式的静态属性,且不能被对象展开覆盖`, sourcePath(ctx.rootDir, obj.getSourceFile().fileName),
|
|
247021
|
+
pushResourceError(ctx, "unknown-resource", `${name} 必须是唯一、显式的静态属性,且不能被对象展开覆盖`, sourcePath(ctx.rootDir, obj.getSourceFile().fileName), lineOf2(member), `使用 ${name}: [...],不要使用简写属性、计算属性或对象展开。`);
|
|
246248
247022
|
return;
|
|
246249
247023
|
}
|
|
246250
247024
|
return member.initializer;
|
|
@@ -246254,7 +247028,7 @@ function indexInfraResource(cls, className, sf, ctx) {
|
|
|
246254
247028
|
if (!decorator)
|
|
246255
247029
|
return;
|
|
246256
247030
|
const file = sourcePath(ctx.rootDir, sf.fileName);
|
|
246257
|
-
const line =
|
|
247031
|
+
const line = lineOf2(cls);
|
|
246258
247032
|
const options = decoratorObjectArg(decorator);
|
|
246259
247033
|
if (!options || options.properties.some((property) => !ts7.isPropertyAssignment(property) || ts7.isComputedPropertyName(property.name)) || new Set(options.properties.map((property) => property.name && propertyName(property.name))).size !== options.properties.length) {
|
|
246260
247034
|
pushResourceError(ctx, "invalid-resource-kind", "@InfraResource 需要静态的 { name, kind } 对象参数", file, line, '使用 @InfraResource({ name: "orders-db", kind: "database" }),不要使用对象展开或重复属性。');
|
|
@@ -246312,7 +247086,7 @@ function parseModuleInfraResources(value, ctx, owner, file, line) {
|
|
|
246312
247086
|
return names;
|
|
246313
247087
|
}
|
|
246314
247088
|
for (const element of array.elements) {
|
|
246315
|
-
const resource = resolveInfraResourceRef(element, ctx, owner, file,
|
|
247089
|
+
const resource = resolveInfraResourceRef(element, ctx, owner, file, lineOf2(element));
|
|
246316
247090
|
if (resource)
|
|
246317
247091
|
names.add(resource);
|
|
246318
247092
|
}
|
|
@@ -246330,15 +247104,15 @@ function parseInfraResourceUseRefs(value, ctx, owner, file, line) {
|
|
|
246330
247104
|
for (const element of array.elements) {
|
|
246331
247105
|
const entry = unwrapInfraResourceExpression(element);
|
|
246332
247106
|
if (!ts7.isObjectLiteralExpression(entry) || entry.properties.some((property) => !ts7.isPropertyAssignment(property) || ts7.isComputedPropertyName(property.name) || !["resource", "operations"].includes(propertyName(property.name))) || new Set(entry.properties.map((property) => property.name && propertyName(property.name))).size !== entry.properties.length) {
|
|
246333
|
-
pushResourceError(ctx, "unknown-resource", `${owner} 的 uses 项必须是显式的 { resource, operations } 对象`, file,
|
|
247107
|
+
pushResourceError(ctx, "unknown-resource", `${owner} 的 uses 项必须是显式的 { resource, operations } 对象`, file, lineOf2(element), "使用显式属性,不要使用对象/数组展开、简写、计算属性或重复属性。");
|
|
246334
247108
|
continue;
|
|
246335
247109
|
}
|
|
246336
247110
|
const resourceEl = getProp(entry, "resource");
|
|
246337
247111
|
if (!resourceEl) {
|
|
246338
|
-
pushResourceError(ctx, "unknown-resource", `${owner} 的 uses 项缺少 resource`, file,
|
|
247112
|
+
pushResourceError(ctx, "unknown-resource", `${owner} 的 uses 项缺少 resource`, file, lineOf2(entry), "在该 uses 项中显式指定 resource: ResourceClass。");
|
|
246339
247113
|
continue;
|
|
246340
247114
|
}
|
|
246341
|
-
const resource = resolveInfraResourceRef(resourceEl, ctx, owner, file,
|
|
247115
|
+
const resource = resolveInfraResourceRef(resourceEl, ctx, owner, file, lineOf2(resourceEl));
|
|
246342
247116
|
if (!resource)
|
|
246343
247117
|
continue;
|
|
246344
247118
|
const operationsEl = getProp(entry, "operations");
|
|
@@ -246348,14 +247122,14 @@ function parseInfraResourceUseRefs(value, ctx, owner, file, line) {
|
|
|
246348
247122
|
} else {
|
|
246349
247123
|
const operationArray = unwrapInfraResourceExpression(operationsEl);
|
|
246350
247124
|
if (!ts7.isArrayLiteralExpression(operationArray) || operationArray.elements.length === 0) {
|
|
246351
|
-
pushResourceError(ctx, "invalid-resource-operation", `${owner} 对资源 '${resource}' 的 operations 必须是非空静态数组`, file,
|
|
247125
|
+
pushResourceError(ctx, "invalid-resource-operation", `${owner} 对资源 '${resource}' 的 operations 必须是非空静态数组`, file, lineOf2(operationsEl), "仅省略 operations 时默认 read;显式填写时必须包含有效操作。");
|
|
246352
247126
|
continue;
|
|
246353
247127
|
}
|
|
246354
247128
|
let invalid = false;
|
|
246355
247129
|
for (const operationEl of operationArray.elements) {
|
|
246356
247130
|
const operation = unwrapInfraResourceExpression(operationEl);
|
|
246357
247131
|
if (!ts7.isStringLiteral(operation) || !isInfraResourceOperation(operation.text)) {
|
|
246358
|
-
pushResourceError(ctx, "invalid-resource-operation", `${owner} 对资源 '${resource}' 声明了无效操作 '${nodeText(operationEl)}'`, file,
|
|
247132
|
+
pushResourceError(ctx, "invalid-resource-operation", `${owner} 对资源 '${resource}' 声明了无效操作 '${nodeText(operationEl)}'`, file, lineOf2(operationEl), `operations 只能包含字符串字面量 ${INFRA_RESOURCE_OPERATIONS.join(" | ")}。`);
|
|
246359
247133
|
invalid = true;
|
|
246360
247134
|
continue;
|
|
246361
247135
|
}
|
|
@@ -247973,11 +248747,11 @@ async function readSourceBytes(path) {
|
|
|
247973
248747
|
return readFile7(path);
|
|
247974
248748
|
}
|
|
247975
248749
|
async function updateSnapshot(previous, options, changedPaths) {
|
|
247976
|
-
const rootDir =
|
|
247977
|
-
const outDir =
|
|
248750
|
+
const rootDir = resolve152(options.rootDir);
|
|
248751
|
+
const outDir = resolve152(options.outDir);
|
|
247978
248752
|
const files = { ...previous.files };
|
|
247979
248753
|
await Promise.all(changedPaths.map(async (changedPath) => {
|
|
247980
|
-
const absolutePath = isAbsolute42(changedPath) ?
|
|
248754
|
+
const absolutePath = isAbsolute42(changedPath) ? resolve152(changedPath) : resolve152(rootDir, changedPath);
|
|
247981
248755
|
const relativeChangedPath = relative9(rootDir, absolutePath);
|
|
247982
248756
|
if (relativeChangedPath === ".." || relativeChangedPath.startsWith(`..${sep9}`))
|
|
247983
248757
|
return;
|
|
@@ -247995,8 +248769,8 @@ async function updateSnapshot(previous, options, changedPaths) {
|
|
|
247995
248769
|
return { files, optionsKey: optionsKeyOf(options) };
|
|
247996
248770
|
}
|
|
247997
248771
|
async function createSnapshot(options) {
|
|
247998
|
-
const rootDir =
|
|
247999
|
-
const outDir =
|
|
248772
|
+
const rootDir = resolve152(options.rootDir);
|
|
248773
|
+
const outDir = resolve152(options.outDir);
|
|
248000
248774
|
const paths = await listSourceFiles(rootDir, outDir);
|
|
248001
248775
|
const files = {};
|
|
248002
248776
|
const entries = await Promise.all(paths.map(async (path) => {
|
|
@@ -248019,8 +248793,8 @@ async function createSnapshot(options) {
|
|
|
248019
248793
|
}
|
|
248020
248794
|
function optionsKeyOf(options) {
|
|
248021
248795
|
return JSON.stringify({
|
|
248022
|
-
rootDir:
|
|
248023
|
-
outDir:
|
|
248796
|
+
rootDir: resolve152(options.rootDir),
|
|
248797
|
+
outDir: resolve152(options.outDir),
|
|
248024
248798
|
include: options.include,
|
|
248025
248799
|
strict: options.strict,
|
|
248026
248800
|
writeOnError: options.writeOnError,
|
|
@@ -248044,7 +248818,7 @@ async function listSourceFiles(rootDir, outDir) {
|
|
|
248044
248818
|
const result = [];
|
|
248045
248819
|
const visit = async (directory) => {
|
|
248046
248820
|
for (const entry of await readdir32(directory, { withFileTypes: true })) {
|
|
248047
|
-
const path =
|
|
248821
|
+
const path = resolve152(directory, entry.name);
|
|
248048
248822
|
if (entry.isDirectory()) {
|
|
248049
248823
|
if (entry.name === "node_modules" || entry.name === ".git" || path === outDir)
|
|
248050
248824
|
continue;
|
|
@@ -248618,7 +249392,7 @@ var starter_metadata_default = {
|
|
|
248618
249392
|
version: "0.8.0"
|
|
248619
249393
|
},
|
|
248620
249394
|
db: {
|
|
248621
|
-
version: "0.
|
|
249395
|
+
version: "0.14.0",
|
|
248622
249396
|
peerDependencies: {
|
|
248623
249397
|
"drizzle-orm": "1.0.0-rc.5-169397b"
|
|
248624
249398
|
},
|
|
@@ -248726,8 +249500,8 @@ test("cancelled stream consumers release their subscription", async () => {
|
|
|
248726
249500
|
`;
|
|
248727
249501
|
|
|
248728
249502
|
// src/shared/tools/app-starter.ts
|
|
248729
|
-
import { lstat as lstat3, mkdir as
|
|
248730
|
-
import { basename as
|
|
249503
|
+
import { lstat as lstat3, mkdir as mkdir5, readdir as readdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
249504
|
+
import { basename as basename9, dirname as dirname10, join as join13, resolve as resolve17 } from "node:path";
|
|
248731
249505
|
|
|
248732
249506
|
// src/shared/tools/app-starter-environment.ts
|
|
248733
249507
|
var STARTER_ENVIRONMENT = String.raw`import { existsSync, readFileSync } from "node:fs";
|
|
@@ -251271,15 +252045,15 @@ and governance; TypeScript checks both application and generated source.
|
|
|
251271
252045
|
}
|
|
251272
252046
|
async function initializeAppProject(options) {
|
|
251273
252047
|
const root = resolve17(options.root ?? process.cwd());
|
|
251274
|
-
const name = options.name ??
|
|
252048
|
+
const name = options.name ?? basename9(root);
|
|
251275
252049
|
if (!/^[a-z][a-z0-9-]*$/.test(name) || name.length > 100) {
|
|
251276
252050
|
throw new Error("Project name must be lowercase kebab-case (1-100 characters)");
|
|
251277
252051
|
}
|
|
251278
|
-
await
|
|
252052
|
+
await mkdir5(root, { recursive: true });
|
|
251279
252053
|
const stat = await lstat3(root);
|
|
251280
252054
|
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
251281
252055
|
throw new Error("Project root must be a real directory");
|
|
251282
|
-
if ((await
|
|
252056
|
+
if ((await readdir5(root)).some((entry) => entry !== ".git")) {
|
|
251283
252057
|
throw new Error("app init requires an empty directory (an existing .git directory is allowed)");
|
|
251284
252058
|
}
|
|
251285
252059
|
const template = options.template ?? "minimal";
|
|
@@ -251288,16 +252062,16 @@ async function initializeAppProject(options) {
|
|
|
251288
252062
|
"@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"]
|
|
251289
252063
|
});
|
|
251290
252064
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
251291
|
-
const path =
|
|
251292
|
-
await
|
|
251293
|
-
await
|
|
252065
|
+
const path = join13(root, relativePath);
|
|
252066
|
+
await mkdir5(dirname10(path), { recursive: true });
|
|
252067
|
+
await writeFile5(path, content, { encoding: "utf8", flag: "wx" });
|
|
251294
252068
|
}
|
|
251295
252069
|
return { root, name, files: Object.keys(files) };
|
|
251296
252070
|
}
|
|
251297
252071
|
|
|
251298
252072
|
// src/shared/tools/app-database-check.ts
|
|
251299
252073
|
import { existsSync as existsSync12 } from "node:fs";
|
|
251300
|
-
import { join as
|
|
252074
|
+
import { join as join14, resolve as resolve18 } from "node:path";
|
|
251301
252075
|
var CHECK_ENTRY = `
|
|
251302
252076
|
import { createRequire } from "node:module";
|
|
251303
252077
|
import { join } from "node:path";
|
|
@@ -251309,7 +252083,7 @@ await import(pathToFileURL(cli).href);
|
|
|
251309
252083
|
`;
|
|
251310
252084
|
function checkAppDatabaseSources(projectRoot) {
|
|
251311
252085
|
const root = resolve18(projectRoot);
|
|
251312
|
-
if (!existsSync12(
|
|
252086
|
+
if (!existsSync12(join14(root, "database.sources.json")))
|
|
251313
252087
|
return;
|
|
251314
252088
|
const name = "database-source-contracts";
|
|
251315
252089
|
try {
|
|
@@ -251440,7 +252214,7 @@ test("${name} controller preserves an asynchronous read failure", async () => {
|
|
|
251440
252214
|
// src/shared/tools/app-local-dev.ts
|
|
251441
252215
|
import { spawn } from "node:child_process";
|
|
251442
252216
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
251443
|
-
import { join as
|
|
252217
|
+
import { join as join15, resolve as resolve19 } from "node:path";
|
|
251444
252218
|
function localDatabaseUrl(value) {
|
|
251445
252219
|
try {
|
|
251446
252220
|
if (!value || /[\x00-\x20\x7f]/.test(value))
|
|
@@ -251499,7 +252273,7 @@ async function runLocalDevelopment(options) {
|
|
|
251499
252273
|
throw new Error("Inherited remote context must be complete and explicitly tagged with SUPACLOUD_ENV=test");
|
|
251500
252274
|
}
|
|
251501
252275
|
const script = profile === "integration" ? "dev:integration" : "dev";
|
|
251502
|
-
const manifest = JSON.parse(await readFile4(
|
|
252276
|
+
const manifest = JSON.parse(await readFile4(join15(root, "package.json"), "utf8"));
|
|
251503
252277
|
const scripts = manifest && typeof manifest === "object" && "scripts" in manifest ? manifest.scripts : undefined;
|
|
251504
252278
|
const command = scripts && typeof scripts === "object" ? Reflect.get(scripts, script) : undefined;
|
|
251505
252279
|
if (typeof command !== "string" || !command.trim()) {
|
|
@@ -251587,7 +252361,7 @@ async function runLocalDevelopment(options) {
|
|
|
251587
252361
|
}
|
|
251588
252362
|
|
|
251589
252363
|
// src/shared/tools/app-verification-plan.ts
|
|
251590
|
-
import { readdir as
|
|
252364
|
+
import { readdir as readdir6, realpath as realpath5 } from "node:fs/promises";
|
|
251591
252365
|
import { dirname as dirname11, isAbsolute as isAbsolute6, relative as relative11, resolve as resolve20 } from "node:path";
|
|
251592
252366
|
function inside3(root, file) {
|
|
251593
252367
|
const path = relative11(root, file).replaceAll("\\", "/");
|
|
@@ -251618,7 +252392,7 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
251618
252392
|
manual.push("A source directory is outside the project; select its owning project explicitly");
|
|
251619
252393
|
continue;
|
|
251620
252394
|
}
|
|
251621
|
-
for (const entry of await
|
|
252395
|
+
for (const entry of await readdir6(actual, { withFileTypes: true })) {
|
|
251622
252396
|
if (entry.isFile() && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
251623
252397
|
tests.add(relative11(project, resolve20(actual, entry.name)).replaceAll("\\", "/"));
|
|
251624
252398
|
}
|
|
@@ -251648,8 +252422,8 @@ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
|
|
|
251648
252422
|
|
|
251649
252423
|
// src/shared/tools/app-scaffold-writes.ts
|
|
251650
252424
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
251651
|
-
import { link, lstat as lstat4, mkdir as
|
|
251652
|
-
import { dirname as dirname12, isAbsolute as isAbsolute7, join as
|
|
252425
|
+
import { link, lstat as lstat4, mkdir as mkdir6, open as open4, readFile as readFile8, realpath as realpath6, rename as rename5, rmdir, unlink as unlink3 } from "node:fs/promises";
|
|
252426
|
+
import { dirname as dirname12, isAbsolute as isAbsolute7, join as join16, relative as relative12, resolve as resolve23, sep as sep11, win32 } from "node:path";
|
|
251653
252427
|
|
|
251654
252428
|
class ScaffoldError extends Error {
|
|
251655
252429
|
code;
|
|
@@ -251682,7 +252456,7 @@ async function snapshot(root, path) {
|
|
|
251682
252456
|
const parts = relative12(root, target).split(sep11);
|
|
251683
252457
|
let current = root;
|
|
251684
252458
|
for (const [index, part] of parts.entries()) {
|
|
251685
|
-
current =
|
|
252459
|
+
current = join16(current, part);
|
|
251686
252460
|
let stat;
|
|
251687
252461
|
try {
|
|
251688
252462
|
stat = await lstat4(current);
|
|
@@ -251732,7 +252506,7 @@ async function planScaffoldWrites(root, writes) {
|
|
|
251732
252506
|
return Object.freeze({ root: canonical, writes: Object.freeze(planned) });
|
|
251733
252507
|
}
|
|
251734
252508
|
async function applyScaffoldWrites(plan) {
|
|
251735
|
-
const lockPath =
|
|
252509
|
+
const lockPath = join16(plan.root, ".supacloud-generate.lock");
|
|
251736
252510
|
const lock = await open4(lockPath, "wx").catch((error) => {
|
|
251737
252511
|
if (fsCode(error) === "EEXIST")
|
|
251738
252512
|
throw new ScaffoldError("SCAFFOLD_BUSY", "Another generator owns .supacloud-generate.lock; no files written");
|
|
@@ -251753,9 +252527,9 @@ async function applyScaffoldWrites(plan) {
|
|
|
251753
252527
|
const target = scaffoldPath(plan.root, write.path);
|
|
251754
252528
|
let current = plan.root;
|
|
251755
252529
|
for (const part of relative12(plan.root, dirname12(target)).split(sep11).filter(Boolean)) {
|
|
251756
|
-
current =
|
|
252530
|
+
current = join16(current, part);
|
|
251757
252531
|
try {
|
|
251758
|
-
await
|
|
252532
|
+
await mkdir6(current);
|
|
251759
252533
|
directories.push(current);
|
|
251760
252534
|
} catch (error) {
|
|
251761
252535
|
if (fsCode(error) !== "EEXIST")
|
|
@@ -251986,19 +252760,19 @@ async function generateScaffold(args) {
|
|
|
251986
252760
|
let contractName;
|
|
251987
252761
|
if (kind === "module" || kind === "resource") {
|
|
251988
252762
|
const name = requireIdentifier(args.name, "name");
|
|
251989
|
-
modulePath =
|
|
252763
|
+
modulePath = join17(dir, name, `${name}.module.ts`).replaceAll("\\", "/");
|
|
251990
252764
|
moduleSymbol = `${pascalName2(name)}Module`;
|
|
251991
252765
|
if (kind === "module")
|
|
251992
252766
|
writes.push({ path: modulePath, content: moduleScaffold(name), overwrite: args.force === true });
|
|
251993
252767
|
else
|
|
251994
252768
|
for (const [file, content] of Object.entries(resourceScaffold(name, pascalName2(name)))) {
|
|
251995
|
-
writes.push({ path:
|
|
252769
|
+
writes.push({ path: join17(dir, name, file).replaceAll("\\", "/"), content });
|
|
251996
252770
|
}
|
|
251997
252771
|
} else {
|
|
251998
252772
|
const moduleName = requireIdentifier(args.module, "module");
|
|
251999
252773
|
const name = kind === "controller" ? moduleName : requireIdentifier(args.name, "name");
|
|
252000
252774
|
const subdir = kind === "command" ? "commands" : kind === "query" ? "queries" : kind === "job" ? "jobs" : kind === "contract" ? "contracts" : "";
|
|
252001
|
-
const path =
|
|
252775
|
+
const path = join17(dir, moduleName, subdir, `${name}.${kind}.ts`).replaceAll("\\", "/");
|
|
252002
252776
|
if (kind === "controller" && existsSync13(scaffoldPath(root, path))) {
|
|
252003
252777
|
throw new ScaffoldError("SCAFFOLD_EXISTS", `Controller already exists: ${path}(请手工合并路由到现有 controller)`);
|
|
252004
252778
|
}
|
|
@@ -252048,7 +252822,7 @@ async function generateScaffold(args) {
|
|
|
252048
252822
|
written: !args.dry_run,
|
|
252049
252823
|
changes
|
|
252050
252824
|
}, null, 2));
|
|
252051
|
-
const lines = changes.map((change) => `${args.dry_run ? "PREVIEW" : "✅"} ${change.action}: ${
|
|
252825
|
+
const lines = changes.map((change) => `${args.dry_run ? "PREVIEW" : "✅"} ${change.action}: ${join17(plan.root, change.path)}`);
|
|
252052
252826
|
if (args.dry_run)
|
|
252053
252827
|
lines.push("No files written. Omit --dry-run to apply this generation.");
|
|
252054
252828
|
if (kind === "resource")
|
|
@@ -252084,12 +252858,12 @@ function parseInclude(include) {
|
|
|
252084
252858
|
return patterns && patterns.length > 0 ? patterns : undefined;
|
|
252085
252859
|
}
|
|
252086
252860
|
function sourceRoot(args, root, configured) {
|
|
252087
|
-
const hasConfig = ["ts", "mts", "js", "mjs"].some((extension) => existsSync13(
|
|
252861
|
+
const hasConfig = ["ts", "mts", "js", "mjs"].some((extension) => existsSync13(join17(root, `supacloud.config.${extension}`)));
|
|
252088
252862
|
return args.root && !hasConfig ? "." : configured;
|
|
252089
252863
|
}
|
|
252090
252864
|
async function projectCompileConfig(args) {
|
|
252091
252865
|
const root = resolve24(args.root || process.cwd());
|
|
252092
|
-
const configFile = ["ts", "mts", "js", "mjs"].map((extension) =>
|
|
252866
|
+
const configFile = ["ts", "mts", "js", "mjs"].map((extension) => join17(root, `supacloud.config.${extension}`)).find((candidate) => existsSync13(candidate)) ?? null;
|
|
252093
252867
|
const loadedConfig = await loadSupacloudConfig(root);
|
|
252094
252868
|
const defaults = resolveSupacloudConfig(loadedConfig, root);
|
|
252095
252869
|
const configuredRoot = sourceRoot(args, root, loadedConfig.root);
|
|
@@ -252106,7 +252880,7 @@ async function projectCompileConfig(args) {
|
|
|
252106
252880
|
return { root, sourceDir: compileOptions.rootDir, configFile, outDir, result };
|
|
252107
252881
|
}
|
|
252108
252882
|
async function readAllowedDependencies(root) {
|
|
252109
|
-
const path =
|
|
252883
|
+
const path = join17(root, "package.json");
|
|
252110
252884
|
if (!existsSync13(path))
|
|
252111
252885
|
return { dependencies: [], devDependencies: [] };
|
|
252112
252886
|
try {
|
|
@@ -252187,14 +252961,14 @@ async function findUnwiredContracts(rootDir) {
|
|
|
252187
252961
|
async function walk(directory) {
|
|
252188
252962
|
let entries;
|
|
252189
252963
|
try {
|
|
252190
|
-
entries = await
|
|
252964
|
+
entries = await readdir7(directory, { withFileTypes: true });
|
|
252191
252965
|
} catch {
|
|
252192
252966
|
return;
|
|
252193
252967
|
}
|
|
252194
252968
|
for (const entry of entries) {
|
|
252195
252969
|
if (skip.has(entry.name))
|
|
252196
252970
|
continue;
|
|
252197
|
-
const path =
|
|
252971
|
+
const path = join17(directory, entry.name);
|
|
252198
252972
|
if (entry.isDirectory())
|
|
252199
252973
|
await walk(path);
|
|
252200
252974
|
else if (entry.isFile() && entry.name.endsWith(".ts"))
|
|
@@ -252212,7 +252986,7 @@ async function findUnwiredContracts(rootDir) {
|
|
|
252212
252986
|
const symbols = [...source.matchAll(/export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)/g)].map((match) => match[1]);
|
|
252213
252987
|
if (symbols.length === 0)
|
|
252214
252988
|
continue;
|
|
252215
|
-
const moduleName =
|
|
252989
|
+
const moduleName = basename10(contract).replace(/\.ts$/, "");
|
|
252216
252990
|
const wired = otherSources.some((text) => text.includes(moduleName) || symbols.some((symbol) => new RegExp(`\\b${symbol}\\b`).test(text)));
|
|
252217
252991
|
if (!wired)
|
|
252218
252992
|
unwired.push({ file: relative13(rootDir, contract), symbols });
|
|
@@ -252305,7 +253079,7 @@ function devDatabase(profile, explicit) {
|
|
|
252305
253079
|
}
|
|
252306
253080
|
function readManifestModules(outDir) {
|
|
252307
253081
|
try {
|
|
252308
|
-
const parsed = JSON.parse(readFileSync10(
|
|
253082
|
+
const parsed = JSON.parse(readFileSync10(join17(outDir, "app.manifest.json"), "utf8"));
|
|
252309
253083
|
return Array.isArray(parsed.modules) ? parsed.modules.map((module) => module.name) : [];
|
|
252310
253084
|
} catch {
|
|
252311
253085
|
return [];
|
|
@@ -252512,7 +253286,7 @@ async function runCheck(args) {
|
|
|
252512
253286
|
async function readManifest(root, args) {
|
|
252513
253287
|
const config = await loadSupacloudConfig(root);
|
|
252514
253288
|
const outDir = args.out_dir ? resolve24(root, args.out_dir) : resolveSupacloudConfig(config, root).outDir;
|
|
252515
|
-
const manifestPath =
|
|
253289
|
+
const manifestPath = join17(outDir, "app.manifest.json");
|
|
252516
253290
|
if (!existsSync13(manifestPath)) {
|
|
252517
253291
|
throw new Error(`Manifest not found: ${manifestPath}(先运行 app compile 生成)`);
|
|
252518
253292
|
}
|
|
@@ -252702,15 +253476,15 @@ async function runExportTools(args) {
|
|
|
252702
253476
|
const root = resolve24(args.root || process.cwd());
|
|
252703
253477
|
const manifest = await readManifest(root, { ...args, out_dir: undefined });
|
|
252704
253478
|
const definitions = buildToolDefinitions(manifest);
|
|
252705
|
-
const outDir = resolve24(args.out_dir ||
|
|
253479
|
+
const outDir = resolve24(args.out_dir || join17(root, "generated"));
|
|
252706
253480
|
if (args.format === "json") {
|
|
252707
253481
|
return textResult(JSON.stringify(definitions, null, 2));
|
|
252708
253482
|
}
|
|
252709
|
-
await
|
|
252710
|
-
const openaiPath =
|
|
252711
|
-
const mcpPath =
|
|
252712
|
-
await
|
|
252713
|
-
await
|
|
253483
|
+
await mkdir7(outDir, { recursive: true });
|
|
253484
|
+
const openaiPath = join17(outDir, "tool-definitions.openai.json");
|
|
253485
|
+
const mcpPath = join17(outDir, "tool-definitions.mcp.json");
|
|
253486
|
+
await writeFile6(openaiPath, JSON.stringify(definitions.openai, null, 2), "utf8");
|
|
253487
|
+
await writeFile6(mcpPath, JSON.stringify(definitions.mcp, null, 2), "utf8");
|
|
252714
253488
|
const summary = [
|
|
252715
253489
|
`exported ${definitions.openai.length} tool(s):`,
|
|
252716
253490
|
` openai → ${openaiPath}`,
|
|
@@ -252904,440 +253678,7 @@ function registerAppAliases(server2) {
|
|
|
252904
253678
|
|
|
252905
253679
|
// src/shared/tools/db-governance-tools.ts
|
|
252906
253680
|
import { existsSync as existsSync14 } from "node:fs";
|
|
252907
|
-
import { join as
|
|
252908
|
-
|
|
252909
|
-
// node_modules/@supacloud/db/dist/index.js
|
|
252910
|
-
var TABLES_SQL = `
|
|
252911
|
-
SELECT n.nspname AS schema,
|
|
252912
|
-
c.relname AS name,
|
|
252913
|
-
c.relrowsecurity AS rls_enabled,
|
|
252914
|
-
c.relforcerowsecurity AS rls_forced
|
|
252915
|
-
FROM pg_class c
|
|
252916
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
252917
|
-
WHERE c.relkind = 'r'
|
|
252918
|
-
AND n.nspname = ANY($1)
|
|
252919
|
-
ORDER BY n.nspname, c.relname
|
|
252920
|
-
`;
|
|
252921
|
-
var POLICIES_SQL = `
|
|
252922
|
-
SELECT n.nspname AS schema,
|
|
252923
|
-
c.relname AS table,
|
|
252924
|
-
p.polname AS name,
|
|
252925
|
-
p.polcmd AS command,
|
|
252926
|
-
ARRAY(
|
|
252927
|
-
SELECT CASE WHEN r = 0 THEN 'PUBLIC' ELSE pg_get_userbyid(r) END
|
|
252928
|
-
FROM unnest(p.polroles) AS r
|
|
252929
|
-
) AS roles,
|
|
252930
|
-
pg_get_expr(p.polqual, p.polrelid) AS using_expr,
|
|
252931
|
-
pg_get_expr(p.polwithcheck, p.polrelid) AS check_expr
|
|
252932
|
-
FROM pg_policy p
|
|
252933
|
-
JOIN pg_class c ON c.oid = p.polrelid
|
|
252934
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
252935
|
-
WHERE n.nspname = ANY($1)
|
|
252936
|
-
ORDER BY n.nspname, c.relname, p.polname
|
|
252937
|
-
`;
|
|
252938
|
-
var FUNCTIONS_SQL = `
|
|
252939
|
-
SELECT n.nspname AS schema,
|
|
252940
|
-
p.proname AS name,
|
|
252941
|
-
p.prosecdef AS security_definer,
|
|
252942
|
-
p.proconfig AS config,
|
|
252943
|
-
l.lanname AS language
|
|
252944
|
-
FROM pg_proc p
|
|
252945
|
-
JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
252946
|
-
JOIN pg_language l ON l.oid = p.prolang
|
|
252947
|
-
WHERE n.nspname = ANY($1)
|
|
252948
|
-
ORDER BY n.nspname, p.proname
|
|
252949
|
-
`;
|
|
252950
|
-
var TRIGGERS_SQL = `
|
|
252951
|
-
SELECT n.nspname AS schema,
|
|
252952
|
-
c.relname AS table,
|
|
252953
|
-
t.tgname AS name,
|
|
252954
|
-
t.tgenabled <> 'D' AS enabled
|
|
252955
|
-
FROM pg_trigger t
|
|
252956
|
-
JOIN pg_class c ON c.oid = t.tgrelid
|
|
252957
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
252958
|
-
WHERE NOT t.tgisinternal
|
|
252959
|
-
AND n.nspname = ANY($1)
|
|
252960
|
-
ORDER BY n.nspname, c.relname, t.tgname
|
|
252961
|
-
`;
|
|
252962
|
-
var GRANTS_SQL = `
|
|
252963
|
-
SELECT table_schema AS object_schema,
|
|
252964
|
-
table_name AS object_name,
|
|
252965
|
-
privilege_type AS privilege,
|
|
252966
|
-
grantee
|
|
252967
|
-
FROM information_schema.role_table_grants
|
|
252968
|
-
WHERE table_schema = ANY($1)
|
|
252969
|
-
UNION ALL
|
|
252970
|
-
SELECT routine_schema AS object_schema,
|
|
252971
|
-
routine_name AS object_name,
|
|
252972
|
-
privilege_type AS privilege,
|
|
252973
|
-
grantee
|
|
252974
|
-
FROM information_schema.routine_privileges
|
|
252975
|
-
WHERE routine_schema = ANY($1)
|
|
252976
|
-
`;
|
|
252977
|
-
var POLCMD_MAP = {
|
|
252978
|
-
r: "select",
|
|
252979
|
-
a: "insert",
|
|
252980
|
-
w: "update",
|
|
252981
|
-
d: "delete",
|
|
252982
|
-
"*": "all"
|
|
252983
|
-
};
|
|
252984
|
-
function extractSearchPath(config) {
|
|
252985
|
-
if (!config)
|
|
252986
|
-
return null;
|
|
252987
|
-
const entry = config.find((item) => item.startsWith("search_path="));
|
|
252988
|
-
if (!entry)
|
|
252989
|
-
return null;
|
|
252990
|
-
return entry.slice("search_path=".length);
|
|
252991
|
-
}
|
|
252992
|
-
async function readCatalog(executor, schemas = ["public"]) {
|
|
252993
|
-
const params = [schemas];
|
|
252994
|
-
const [tableRows, policyRows, functionRows, triggerRows, grantRows] = await Promise.all([
|
|
252995
|
-
executor.query(TABLES_SQL, params),
|
|
252996
|
-
executor.query(POLICIES_SQL, params),
|
|
252997
|
-
executor.query(FUNCTIONS_SQL, params),
|
|
252998
|
-
executor.query(TRIGGERS_SQL, params),
|
|
252999
|
-
executor.query(GRANTS_SQL, params)
|
|
253000
|
-
]);
|
|
253001
|
-
return {
|
|
253002
|
-
tables: tableRows.map((row) => ({
|
|
253003
|
-
schema: row.schema,
|
|
253004
|
-
name: row.name,
|
|
253005
|
-
rlsEnabled: row.rls_enabled,
|
|
253006
|
-
rlsForced: row.rls_forced
|
|
253007
|
-
})),
|
|
253008
|
-
policies: policyRows.map((row) => ({
|
|
253009
|
-
schema: row.schema,
|
|
253010
|
-
table: row.table,
|
|
253011
|
-
name: row.name,
|
|
253012
|
-
command: POLCMD_MAP[row.command] ?? "all",
|
|
253013
|
-
roles: row.roles ?? [],
|
|
253014
|
-
usingExpr: row.using_expr ?? undefined,
|
|
253015
|
-
checkExpr: row.check_expr ?? undefined
|
|
253016
|
-
})),
|
|
253017
|
-
functions: functionRows.map((row) => ({
|
|
253018
|
-
schema: row.schema,
|
|
253019
|
-
name: row.name,
|
|
253020
|
-
security: row.security_definer ? "definer" : "invoker",
|
|
253021
|
-
searchPath: extractSearchPath(row.config),
|
|
253022
|
-
language: row.language
|
|
253023
|
-
})),
|
|
253024
|
-
triggers: triggerRows.map((row) => ({
|
|
253025
|
-
schema: row.schema,
|
|
253026
|
-
table: row.table,
|
|
253027
|
-
name: row.name,
|
|
253028
|
-
enabled: row.enabled
|
|
253029
|
-
})),
|
|
253030
|
-
grants: grantRows.map((row) => ({
|
|
253031
|
-
objectSchema: row.object_schema,
|
|
253032
|
-
objectName: row.object_name,
|
|
253033
|
-
privilege: row.privilege,
|
|
253034
|
-
grantee: row.grantee
|
|
253035
|
-
}))
|
|
253036
|
-
};
|
|
253037
|
-
}
|
|
253038
|
-
function splitQualifiedName(name) {
|
|
253039
|
-
const dot = name.indexOf(".");
|
|
253040
|
-
if (dot === -1)
|
|
253041
|
-
return ["public", name];
|
|
253042
|
-
return [name.slice(0, dot), name.slice(dot + 1)];
|
|
253043
|
-
}
|
|
253044
|
-
function isFixedSearchPath(searchPath) {
|
|
253045
|
-
if (searchPath === null)
|
|
253046
|
-
return false;
|
|
253047
|
-
const parts = searchPath.split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
253048
|
-
return parts.every((part) => part !== "" && part.toLowerCase() !== "pg_temp");
|
|
253049
|
-
}
|
|
253050
|
-
function reconcileModule(module, catalog) {
|
|
253051
|
-
const issues = [];
|
|
253052
|
-
const ownedTables = new Set(module.tables);
|
|
253053
|
-
const push = (severity, code, object, message) => issues.push({ severity, code, object, message });
|
|
253054
|
-
for (const policy of module.policies) {
|
|
253055
|
-
const [schema, table] = splitQualifiedName(policy.table);
|
|
253056
|
-
const found = catalog.policies.some((cp) => cp.schema === schema && cp.table === table && cp.name === policy.name);
|
|
253057
|
-
if (!found) {
|
|
253058
|
-
push("error", "missing-policy", `${policy.table}.${policy.name}`, `声明的策略 ${policy.name} 在表 ${policy.table} 的 catalog 中不存在`);
|
|
253059
|
-
}
|
|
253060
|
-
}
|
|
253061
|
-
const declaredPolicyKeys = new Set(module.policies.map((p) => `${p.table}::${p.name}`));
|
|
253062
|
-
for (const cp of catalog.policies) {
|
|
253063
|
-
const qualified = `${cp.schema}.${cp.table}`;
|
|
253064
|
-
if (ownedTables.has(qualified) && !declaredPolicyKeys.has(`${qualified}::${cp.name}`)) {
|
|
253065
|
-
push("warn", "undeclared-policy", `${qualified}.${cp.name}`, `归属表 ${qualified} 上存在未声明的策略 ${cp.name},可能发生漂移`);
|
|
253066
|
-
}
|
|
253067
|
-
}
|
|
253068
|
-
for (const fn of module.functions) {
|
|
253069
|
-
const [schema, name] = splitQualifiedName(fn.name);
|
|
253070
|
-
const cf = catalog.functions.find((f) => f.schema === schema && f.name === name);
|
|
253071
|
-
if (!cf) {
|
|
253072
|
-
push("error", "missing-function", fn.name, `声明的函数 ${fn.name} 在 catalog 中不存在`);
|
|
253073
|
-
continue;
|
|
253074
|
-
}
|
|
253075
|
-
if (cf.security !== fn.security) {
|
|
253076
|
-
push("warn", "security-mismatch", fn.name, `函数 ${fn.name} 声明为 security ${fn.security},catalog 实际为 ${cf.security}`);
|
|
253077
|
-
}
|
|
253078
|
-
const effectiveDefiner = fn.security === "definer" || cf.security === "definer";
|
|
253079
|
-
if (effectiveDefiner && !isFixedSearchPath(cf.searchPath)) {
|
|
253080
|
-
push("error", "definer-without-search-path", fn.name, `security definer 函数 ${fn.name} 未设置固定 search_path(当前: ${cf.searchPath ?? "未设置"})`);
|
|
253081
|
-
}
|
|
253082
|
-
}
|
|
253083
|
-
for (const trigger of module.triggers) {
|
|
253084
|
-
const [schema, table] = splitQualifiedName(trigger.table);
|
|
253085
|
-
const found = catalog.triggers.some((ct) => ct.schema === schema && ct.table === table && ct.name === trigger.name);
|
|
253086
|
-
if (!found) {
|
|
253087
|
-
push("error", "missing-trigger", `${trigger.table}.${trigger.name}`, `声明的触发器 ${trigger.name} 在表 ${trigger.table} 的 catalog 中不存在`);
|
|
253088
|
-
}
|
|
253089
|
-
}
|
|
253090
|
-
const declaredTriggerKeys = new Set(module.triggers.map((t) => `${t.table}::${t.name}`));
|
|
253091
|
-
for (const ct of catalog.triggers) {
|
|
253092
|
-
const qualified = `${ct.schema}.${ct.table}`;
|
|
253093
|
-
if (ownedTables.has(qualified) && !declaredTriggerKeys.has(`${qualified}::${ct.name}`)) {
|
|
253094
|
-
push("warn", "undeclared-trigger", `${qualified}.${ct.name}`, `归属表 ${qualified} 上存在未声明的触发器 ${ct.name}${ct.enabled ? "" : "(已禁用)"},可能发生漂移`);
|
|
253095
|
-
}
|
|
253096
|
-
}
|
|
253097
|
-
for (const table of module.tables) {
|
|
253098
|
-
const [schema, name] = splitQualifiedName(table);
|
|
253099
|
-
const ct = catalog.tables.find((t) => t.schema === schema && t.name === name);
|
|
253100
|
-
if (ct && !ct.rlsEnabled) {
|
|
253101
|
-
push("error", "rls-disabled", table, `归属表 ${table} 未开启行级安全(relrowsecurity = false)`);
|
|
253102
|
-
}
|
|
253103
|
-
}
|
|
253104
|
-
for (const grant of catalog.grants) {
|
|
253105
|
-
const qualified = `${grant.objectSchema}.${grant.objectName}`;
|
|
253106
|
-
if (ownedTables.has(qualified) && grant.grantee.toUpperCase() === "PUBLIC") {
|
|
253107
|
-
push("error", "wildcard-grant", qualified, `归属表 ${qualified} 存在授予 PUBLIC 的 ${grant.privilege} 权限`);
|
|
253108
|
-
}
|
|
253109
|
-
}
|
|
253110
|
-
for (const grant of module.grants) {
|
|
253111
|
-
const [schema, name] = splitQualifiedName(grant.object);
|
|
253112
|
-
const found = catalog.grants.some((cg) => cg.objectSchema === schema && cg.objectName === name && cg.privilege.toLowerCase() === grant.privilege.toLowerCase() && cg.grantee.toLowerCase() === grant.role.toLowerCase());
|
|
253113
|
-
if (!found) {
|
|
253114
|
-
push("warn", "grant-drift", grant.object, `声明的授权 ${grant.privilege} ON ${grant.object} TO ${grant.role} 在 catalog 中不存在`);
|
|
253115
|
-
}
|
|
253116
|
-
}
|
|
253117
|
-
return {
|
|
253118
|
-
module: module.name,
|
|
253119
|
-
issues,
|
|
253120
|
-
ok: !issues.some((issue) => issue.severity === "error")
|
|
253121
|
-
};
|
|
253122
|
-
}
|
|
253123
|
-
var SECURITY_DEFINER_RE = /\bsecurity\s+definer\b/i;
|
|
253124
|
-
var SET_SEARCH_PATH_RE = /\bset\s+search_path\b/i;
|
|
253125
|
-
var GRANT_TO_PUBLIC_RE = /\bgrant\b[^;]*\bto\s+public\b/i;
|
|
253126
|
-
var DROP_WITHOUT_IF_EXISTS_RE = /\bdrop\s+(?:table|column)\s+(?!if\s+exists\b)/i;
|
|
253127
|
-
var ENABLE_RLS_RE = /\benable\s+row\s+level\s+security\b/i;
|
|
253128
|
-
var CREATE_POLICY_RE = /\bcreate\s+policy\b/i;
|
|
253129
|
-
var DROP_POLICY_IF_EXISTS_RE = /\bdrop\s+policy\s+if\s+exists\b/i;
|
|
253130
|
-
function lineOf2(sql, index) {
|
|
253131
|
-
let line = 1;
|
|
253132
|
-
for (let i = 0;i < index; i += 1) {
|
|
253133
|
-
if (sql.charCodeAt(i) === 10)
|
|
253134
|
-
line += 1;
|
|
253135
|
-
}
|
|
253136
|
-
return line;
|
|
253137
|
-
}
|
|
253138
|
-
function lintSql(sql, file) {
|
|
253139
|
-
const issues = [];
|
|
253140
|
-
const definer = SECURITY_DEFINER_RE.exec(sql);
|
|
253141
|
-
if (definer && !SET_SEARCH_PATH_RE.test(sql)) {
|
|
253142
|
-
issues.push({
|
|
253143
|
-
severity: "error",
|
|
253144
|
-
code: "definer-no-search-path",
|
|
253145
|
-
message: "security definer 函数必须显式 set search_path,避免 search_path 劫持",
|
|
253146
|
-
file,
|
|
253147
|
-
line: lineOf2(sql, definer.index)
|
|
253148
|
-
});
|
|
253149
|
-
}
|
|
253150
|
-
const grantPublic = GRANT_TO_PUBLIC_RE.exec(sql);
|
|
253151
|
-
if (grantPublic) {
|
|
253152
|
-
issues.push({
|
|
253153
|
-
severity: "error",
|
|
253154
|
-
code: "grant-to-public",
|
|
253155
|
-
message: "禁止将权限授予 PUBLIC 角色",
|
|
253156
|
-
file,
|
|
253157
|
-
line: lineOf2(sql, grantPublic.index)
|
|
253158
|
-
});
|
|
253159
|
-
}
|
|
253160
|
-
const drop = DROP_WITHOUT_IF_EXISTS_RE.exec(sql);
|
|
253161
|
-
if (drop) {
|
|
253162
|
-
issues.push({
|
|
253163
|
-
severity: "warn",
|
|
253164
|
-
code: "drop-without-if-exists",
|
|
253165
|
-
message: "drop table/column 建议使用 if exists,保证迁移可重入",
|
|
253166
|
-
file,
|
|
253167
|
-
line: lineOf2(sql, drop.index)
|
|
253168
|
-
});
|
|
253169
|
-
}
|
|
253170
|
-
const createPolicy = CREATE_POLICY_RE.exec(sql);
|
|
253171
|
-
if (createPolicy) {
|
|
253172
|
-
const dropPolicy = DROP_POLICY_IF_EXISTS_RE.exec(sql);
|
|
253173
|
-
if (!dropPolicy || dropPolicy.index > createPolicy.index) {
|
|
253174
|
-
issues.push({
|
|
253175
|
-
severity: "warn",
|
|
253176
|
-
code: "non-idempotent-policy",
|
|
253177
|
-
message: "create policy 前缺少 drop policy if exists,策略不可重复执行",
|
|
253178
|
-
file,
|
|
253179
|
-
line: lineOf2(sql, createPolicy.index)
|
|
253180
|
-
});
|
|
253181
|
-
}
|
|
253182
|
-
}
|
|
253183
|
-
return issues;
|
|
253184
|
-
}
|
|
253185
|
-
async function lintModule(module, readFile) {
|
|
253186
|
-
const issues = [];
|
|
253187
|
-
const sources = new Set;
|
|
253188
|
-
for (const decl of [
|
|
253189
|
-
...module.policies,
|
|
253190
|
-
...module.functions,
|
|
253191
|
-
...module.triggers,
|
|
253192
|
-
...module.grants
|
|
253193
|
-
]) {
|
|
253194
|
-
sources.add(decl.source);
|
|
253195
|
-
}
|
|
253196
|
-
const contents = new Map;
|
|
253197
|
-
await Promise.all([...sources].map(async (path) => {
|
|
253198
|
-
contents.set(path, await readFile(path));
|
|
253199
|
-
}));
|
|
253200
|
-
for (const [file, sql] of contents) {
|
|
253201
|
-
issues.push(...lintSql(sql, file));
|
|
253202
|
-
}
|
|
253203
|
-
if (module.policies.length > 0) {
|
|
253204
|
-
const anyEnable = module.policies.some((policy) => ENABLE_RLS_RE.test(contents.get(policy.source) ?? ""));
|
|
253205
|
-
if (!anyEnable) {
|
|
253206
|
-
issues.push({
|
|
253207
|
-
severity: "warn",
|
|
253208
|
-
code: "missing-rls-enable",
|
|
253209
|
-
message: `模块 ${module.name} 声明了 ${module.policies.length} 条策略,但所有策略源文件都没有 enable row level security`,
|
|
253210
|
-
file: module.policies[0].source
|
|
253211
|
-
});
|
|
253212
|
-
}
|
|
253213
|
-
}
|
|
253214
|
-
for (const policy of module.policies) {
|
|
253215
|
-
if (!policy.tests || policy.tests.length === 0) {
|
|
253216
|
-
issues.push({
|
|
253217
|
-
severity: "warn",
|
|
253218
|
-
code: "policy-without-test",
|
|
253219
|
-
message: `策略 ${policy.name} 未声明测试文件`,
|
|
253220
|
-
file: policy.source
|
|
253221
|
-
});
|
|
253222
|
-
}
|
|
253223
|
-
}
|
|
253224
|
-
for (const fn of module.functions) {
|
|
253225
|
-
if (!fn.tests || fn.tests.length === 0) {
|
|
253226
|
-
issues.push({
|
|
253227
|
-
severity: "warn",
|
|
253228
|
-
code: "policy-without-test",
|
|
253229
|
-
message: `函数 ${fn.name} 未声明测试文件`,
|
|
253230
|
-
file: fn.source
|
|
253231
|
-
});
|
|
253232
|
-
}
|
|
253233
|
-
}
|
|
253234
|
-
return issues;
|
|
253235
|
-
}
|
|
253236
|
-
function buildDatabaseManifest(modules) {
|
|
253237
|
-
return {
|
|
253238
|
-
version: 1,
|
|
253239
|
-
modules: modules.map((module) => ({
|
|
253240
|
-
name: module.name,
|
|
253241
|
-
tables: module.tables,
|
|
253242
|
-
policies: module.policies,
|
|
253243
|
-
functions: module.functions,
|
|
253244
|
-
triggers: module.triggers,
|
|
253245
|
-
grants: module.grants
|
|
253246
|
-
}))
|
|
253247
|
-
};
|
|
253248
|
-
}
|
|
253249
|
-
function describePolicy(moduleName, policy) {
|
|
253250
|
-
const lines = [
|
|
253251
|
-
`对象: ${policy.table}.${policy.name}`,
|
|
253252
|
-
"类型: 策略 (policy)",
|
|
253253
|
-
`所属模块: ${moduleName}`,
|
|
253254
|
-
`表: ${policy.table}`,
|
|
253255
|
-
`操作: ${policy.operation}`,
|
|
253256
|
-
`角色: ${policy.roles.join(", ")}`,
|
|
253257
|
-
`源文件: ${policy.source}`
|
|
253258
|
-
];
|
|
253259
|
-
if (policy.tests && policy.tests.length > 0)
|
|
253260
|
-
lines.push(`测试: ${policy.tests.join(", ")}`);
|
|
253261
|
-
return lines.join(`
|
|
253262
|
-
`);
|
|
253263
|
-
}
|
|
253264
|
-
function describeFunction(moduleName, fn) {
|
|
253265
|
-
const lines = [
|
|
253266
|
-
`对象: ${fn.name}`,
|
|
253267
|
-
"类型: 函数 (function)",
|
|
253268
|
-
`所属模块: ${moduleName}`,
|
|
253269
|
-
`源文件: ${fn.source}`,
|
|
253270
|
-
`安全模式: ${fn.security}`
|
|
253271
|
-
];
|
|
253272
|
-
if (fn.permission)
|
|
253273
|
-
lines.push(`权限: ${fn.permission}`);
|
|
253274
|
-
if (fn.transaction)
|
|
253275
|
-
lines.push(`事务: ${fn.transaction}`);
|
|
253276
|
-
if (fn.audit)
|
|
253277
|
-
lines.push(`审计: ${fn.audit}`);
|
|
253278
|
-
if (fn.idempotency)
|
|
253279
|
-
lines.push(`幂等: ${fn.idempotency}`);
|
|
253280
|
-
if (fn.tests && fn.tests.length > 0)
|
|
253281
|
-
lines.push(`测试: ${fn.tests.join(", ")}`);
|
|
253282
|
-
return lines.join(`
|
|
253283
|
-
`);
|
|
253284
|
-
}
|
|
253285
|
-
function describeTrigger(moduleName, trigger) {
|
|
253286
|
-
return [
|
|
253287
|
-
`对象: ${trigger.name}`,
|
|
253288
|
-
"类型: 触发器 (trigger)",
|
|
253289
|
-
`所属模块: ${moduleName}`,
|
|
253290
|
-
`表: ${trigger.table}`,
|
|
253291
|
-
`源文件: ${trigger.source}`
|
|
253292
|
-
].join(`
|
|
253293
|
-
`);
|
|
253294
|
-
}
|
|
253295
|
-
function describeGrant(moduleName, grant) {
|
|
253296
|
-
return [
|
|
253297
|
-
`对象: ${grant.object}`,
|
|
253298
|
-
"类型: 授权 (grant)",
|
|
253299
|
-
`所属模块: ${moduleName}`,
|
|
253300
|
-
`权限: ${grant.privilege}`,
|
|
253301
|
-
`角色: ${grant.role}`,
|
|
253302
|
-
`源文件: ${grant.source}`
|
|
253303
|
-
].join(`
|
|
253304
|
-
`);
|
|
253305
|
-
}
|
|
253306
|
-
function explainObject(manifest, name) {
|
|
253307
|
-
for (const module of manifest.modules) {
|
|
253308
|
-
for (const policy of module.policies) {
|
|
253309
|
-
if (policy.name === name || `${policy.table}.${policy.name}` === name) {
|
|
253310
|
-
return describePolicy(module.name, policy);
|
|
253311
|
-
}
|
|
253312
|
-
}
|
|
253313
|
-
for (const fn of module.functions) {
|
|
253314
|
-
if (fn.name === name)
|
|
253315
|
-
return describeFunction(module.name, fn);
|
|
253316
|
-
}
|
|
253317
|
-
for (const trigger of module.triggers) {
|
|
253318
|
-
if (trigger.name === name || `${trigger.table}.${trigger.name}` === name) {
|
|
253319
|
-
return describeTrigger(module.name, trigger);
|
|
253320
|
-
}
|
|
253321
|
-
}
|
|
253322
|
-
for (const table of module.tables) {
|
|
253323
|
-
if (table === name) {
|
|
253324
|
-
return [
|
|
253325
|
-
`对象: ${table}`,
|
|
253326
|
-
"类型: 表 (table)",
|
|
253327
|
-
`所属模块: ${module.name}`
|
|
253328
|
-
].join(`
|
|
253329
|
-
`);
|
|
253330
|
-
}
|
|
253331
|
-
}
|
|
253332
|
-
for (const grant of module.grants) {
|
|
253333
|
-
if (grant.object === name)
|
|
253334
|
-
return describeGrant(module.name, grant);
|
|
253335
|
-
}
|
|
253336
|
-
}
|
|
253337
|
-
return `未找到对象: ${name}`;
|
|
253338
|
-
}
|
|
253339
|
-
|
|
253340
|
-
// src/shared/tools/db-governance-tools.ts
|
|
253681
|
+
import { join as join18, resolve as resolve25 } from "node:path";
|
|
253341
253682
|
function textResult2(text, isError = false) {
|
|
253342
253683
|
return { isError, content: [{ type: "text", text }] };
|
|
253343
253684
|
}
|
|
@@ -253356,7 +253697,7 @@ function normalizeModule(candidate, sourcePath) {
|
|
|
253356
253697
|
};
|
|
253357
253698
|
}
|
|
253358
253699
|
async function loadDatabaseModules(root, moduleFile) {
|
|
253359
|
-
const filePath = moduleFile ? resolve25(root, moduleFile) :
|
|
253700
|
+
const filePath = moduleFile ? resolve25(root, moduleFile) : join18(root, "db", "modules.ts");
|
|
253360
253701
|
if (!existsSync14(filePath)) {
|
|
253361
253702
|
throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
|
|
253362
253703
|
}
|
|
@@ -254183,7 +254524,7 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
254183
254524
|
// src/shared/tools/release-tools.ts
|
|
254184
254525
|
import { createHash as createHash11 } from "node:crypto";
|
|
254185
254526
|
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "node:fs";
|
|
254186
|
-
import { dirname as dirname14, join as
|
|
254527
|
+
import { dirname as dirname14, join as join19, relative as relative14, resolve as resolve27 } from "node:path";
|
|
254187
254528
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
254188
254529
|
var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
|
|
254189
254530
|
var SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -254511,7 +254852,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
|
|
|
254511
254852
|
const entries = readdirSync5(directory, { withFileTypes: true });
|
|
254512
254853
|
for (const entry of entries) {
|
|
254513
254854
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
254514
|
-
const fullPath =
|
|
254855
|
+
const fullPath = join19(directory, entry.name);
|
|
254515
254856
|
if (!taskFilter) {
|
|
254516
254857
|
results.push(fullPath);
|
|
254517
254858
|
} else if (entry.name.toLowerCase().includes(taskFilter)) {
|
|
@@ -254896,10 +255237,10 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
254896
255237
|
}
|
|
254897
255238
|
|
|
254898
255239
|
// src/shared/tools/deploy-tools.ts
|
|
254899
|
-
import { access as access4, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile10, rm as rm3, writeFile as
|
|
255240
|
+
import { access as access4, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile10, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
254900
255241
|
import { createHash as createHash12 } from "node:crypto";
|
|
254901
255242
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
254902
|
-
import { basename as
|
|
255243
|
+
import { basename as basename11, dirname as dirname15, join as join20, relative as relative15, resolve as resolve28, sep as sep12 } from "node:path";
|
|
254903
255244
|
|
|
254904
255245
|
// node_modules/fflate/esm/index.mjs
|
|
254905
255246
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -255592,7 +255933,7 @@ async function readJson(path) {
|
|
|
255592
255933
|
async function findDeployConfigRoot(startDirectory) {
|
|
255593
255934
|
let directory = resolve28(startDirectory);
|
|
255594
255935
|
while (true) {
|
|
255595
|
-
if (await pathExists(
|
|
255936
|
+
if (await pathExists(join20(directory, "supacloud.json")))
|
|
255596
255937
|
return directory;
|
|
255597
255938
|
const parent = dirname15(directory);
|
|
255598
255939
|
if (parent === directory)
|
|
@@ -255639,7 +255980,7 @@ function deployTargetConfig(candidate, name) {
|
|
|
255639
255980
|
};
|
|
255640
255981
|
}
|
|
255641
255982
|
async function readDeployConfig(configRoot) {
|
|
255642
|
-
const configPath =
|
|
255983
|
+
const configPath = join20(configRoot, "supacloud.json");
|
|
255643
255984
|
const candidate = await readJson(configPath);
|
|
255644
255985
|
if (candidate === null)
|
|
255645
255986
|
return {};
|
|
@@ -255661,25 +256002,25 @@ async function readDeployConfig(configRoot) {
|
|
|
255661
256002
|
};
|
|
255662
256003
|
}
|
|
255663
256004
|
async function packageName(projectDirectory) {
|
|
255664
|
-
const candidate = record5(await readJson(
|
|
256005
|
+
const candidate = record5(await readJson(join20(projectDirectory, "package.json")));
|
|
255665
256006
|
return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
|
|
255666
256007
|
}
|
|
255667
256008
|
async function pathExists(path) {
|
|
255668
256009
|
return access4(path).then(() => true, () => false);
|
|
255669
256010
|
}
|
|
255670
256011
|
async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
255671
|
-
const packageJson = record5(await readJson(
|
|
256012
|
+
const packageJson = record5(await readJson(join20(projectDirectory, "package.json")));
|
|
255672
256013
|
const scripts = record5(packageJson?.scripts);
|
|
255673
256014
|
if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
|
|
255674
256015
|
throw new Error("No build command was configured and package.json has no build script");
|
|
255675
256016
|
}
|
|
255676
256017
|
let directory = projectDirectory;
|
|
255677
256018
|
while (true) {
|
|
255678
|
-
if (await pathExists(
|
|
256019
|
+
if (await pathExists(join20(directory, "bun.lock")) || await pathExists(join20(directory, "bun.lockb")))
|
|
255679
256020
|
return "bun run build";
|
|
255680
|
-
if (await pathExists(
|
|
256021
|
+
if (await pathExists(join20(directory, "pnpm-lock.yaml")))
|
|
255681
256022
|
return "pnpm run build";
|
|
255682
|
-
if (await pathExists(
|
|
256023
|
+
if (await pathExists(join20(directory, "yarn.lock")))
|
|
255683
256024
|
return "yarn build";
|
|
255684
256025
|
if (directory === configRoot)
|
|
255685
256026
|
break;
|
|
@@ -255798,7 +256139,7 @@ async function collectArchiveFiles(root) {
|
|
|
255798
256139
|
entries.push(entry);
|
|
255799
256140
|
entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
|
|
255800
256141
|
for (const entry of entries) {
|
|
255801
|
-
const path =
|
|
256142
|
+
const path = join20(directory, entry.name);
|
|
255802
256143
|
const stats = await lstat5(path);
|
|
255803
256144
|
if (stats.isSymbolicLink())
|
|
255804
256145
|
throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
|
|
@@ -255827,11 +256168,11 @@ async function collectArchiveFiles(root) {
|
|
|
255827
256168
|
}
|
|
255828
256169
|
async function createFrontendArchive(outputDirectory) {
|
|
255829
256170
|
const collected = await collectArchiveFiles(outputDirectory);
|
|
255830
|
-
const temporaryDirectory = await mkdtemp2(
|
|
255831
|
-
const archivePath =
|
|
256171
|
+
const temporaryDirectory = await mkdtemp2(join20(tmpdir2(), "supacloud-deploy-"));
|
|
256172
|
+
const archivePath = join20(temporaryDirectory, `${basename11(outputDirectory)}.zip`);
|
|
255832
256173
|
try {
|
|
255833
256174
|
const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
|
|
255834
|
-
await
|
|
256175
|
+
await writeFile7(archivePath, archiveBytes);
|
|
255835
256176
|
const sha256 = createHash12("sha256").update(archiveBytes).digest("hex");
|
|
255836
256177
|
return {
|
|
255837
256178
|
archivePath,
|
|
@@ -256052,9 +256393,9 @@ function registerDeployTools(server2, http, options = {}) {
|
|
|
256052
256393
|
// src/shared/tools/remote-dev-tools.ts
|
|
256053
256394
|
import { existsSync as existsSync16 } from "node:fs";
|
|
256054
256395
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
256055
|
-
import { readdir as
|
|
256396
|
+
import { readdir as readdir8 } from "node:fs/promises";
|
|
256056
256397
|
import { createHash as createHash13 } from "node:crypto";
|
|
256057
|
-
import { join as
|
|
256398
|
+
import { join as join21, resolve as resolve29 } from "node:path";
|
|
256058
256399
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
256059
256400
|
var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
|
|
256060
256401
|
var remoteDevToolSchema = {
|
|
@@ -256088,7 +256429,7 @@ async function runProcess(executable, args, cwd) {
|
|
|
256088
256429
|
function resolveDrizzleCommand(root, configured) {
|
|
256089
256430
|
if (configured?.trim())
|
|
256090
256431
|
return configured.trim();
|
|
256091
|
-
const local =
|
|
256432
|
+
const local = join21(root, "node_modules", ".bin", "drizzle-kit");
|
|
256092
256433
|
return existsSync16(local) ? local : "drizzle-kit";
|
|
256093
256434
|
}
|
|
256094
256435
|
function toolFailed(value) {
|
|
@@ -256120,7 +256461,7 @@ async function migrateDatabase(args, options) {
|
|
|
256120
256461
|
return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
|
|
256121
256462
|
}
|
|
256122
256463
|
async function readProjectConfig3(root) {
|
|
256123
|
-
const file =
|
|
256464
|
+
const file = join21(root, "supacloud.json");
|
|
256124
256465
|
if (!existsSync16(file))
|
|
256125
256466
|
return {};
|
|
256126
256467
|
try {
|
|
@@ -256137,9 +256478,9 @@ async function readDevConfig(root) {
|
|
|
256137
256478
|
async function sourceFingerprint(root) {
|
|
256138
256479
|
const files = [];
|
|
256139
256480
|
const visit = async (directory) => {
|
|
256140
|
-
const entries = await
|
|
256481
|
+
const entries = await readdir8(directory, { withFileTypes: true });
|
|
256141
256482
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
256142
|
-
const path =
|
|
256483
|
+
const path = join21(directory, entry.name);
|
|
256143
256484
|
if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
|
|
256144
256485
|
await visit(path);
|
|
256145
256486
|
else if (entry.isFile())
|
|
@@ -256167,17 +256508,17 @@ function remoteRoot(value) {
|
|
|
256167
256508
|
}
|
|
256168
256509
|
function targetDirectory(root, target, functionSlug, config = {}) {
|
|
256169
256510
|
if (target === "db")
|
|
256170
|
-
return
|
|
256511
|
+
return join21(root, "supabase", "migrations");
|
|
256171
256512
|
if (target === "functions") {
|
|
256172
256513
|
const targets = config.targets && typeof config.targets === "object" ? config.targets : {};
|
|
256173
256514
|
const match = Object.values(targets).find((entry) => entry?.type === "edge_function" && (!functionSlug || String(entry.slug || "") === functionSlug));
|
|
256174
|
-
const base = match?.root ? resolve29(root, String(match.root)) :
|
|
256175
|
-
return match?.root ? base : functionSlug ?
|
|
256515
|
+
const base = match?.root ? resolve29(root, String(match.root)) : join21(root, "supabase", "functions");
|
|
256516
|
+
return match?.root ? base : functionSlug ? join21(base, safeToken(functionSlug, "function")) : base;
|
|
256176
256517
|
}
|
|
256177
256518
|
if (target === "frontend") {
|
|
256178
256519
|
const targets = config.targets && typeof config.targets === "object" ? config.targets : {};
|
|
256179
256520
|
const match = Object.values(targets).find((entry) => entry?.type === "frontend");
|
|
256180
|
-
return match?.root ? resolve29(root, String(match.root)) :
|
|
256521
|
+
return match?.root ? resolve29(root, String(match.root)) : join21(root, "apps", "web");
|
|
256181
256522
|
}
|
|
256182
256523
|
return root;
|
|
256183
256524
|
}
|
|
@@ -256314,7 +256655,7 @@ function registerRemoteDevTools(server2, options = {}) {
|
|
|
256314
256655
|
// package.json
|
|
256315
256656
|
var package_default = {
|
|
256316
256657
|
name: "@supacloud/cli",
|
|
256317
|
-
version: "0.
|
|
256658
|
+
version: "0.71.0",
|
|
256318
256659
|
description: "Project-scoped CLI for SupaCloud users",
|
|
256319
256660
|
type: "module",
|
|
256320
256661
|
main: "./dist/index.js",
|
|
@@ -256350,7 +256691,7 @@ var package_default = {
|
|
|
256350
256691
|
dependencies: {
|
|
256351
256692
|
"@supacloud/compiler": "^0.40.1",
|
|
256352
256693
|
"@supacloud/delivery": "0.8.0",
|
|
256353
|
-
"@supacloud/db": "^0.
|
|
256694
|
+
"@supacloud/db": "^0.14.0",
|
|
256354
256695
|
fflate: "^0.8.3",
|
|
256355
256696
|
typebox: "1.3.34"
|
|
256356
256697
|
},
|
|
@@ -256599,6 +256940,7 @@ EXAMPLES
|
|
|
256599
256940
|
${preferredCommand} database query --ref abc123 --file ./queries/vector-search.sql
|
|
256600
256941
|
${preferredCommand} database migration_inventory --ref abc123
|
|
256601
256942
|
${preferredCommand} database lint_migrations --dir supabase/migrations
|
|
256943
|
+
${preferredCommand} database rebase_migrations --dir supabase/migrations --baseline_file backups/schema.sql --baseline_version 20261008000000 --output_dir supabase/migrations-rebased
|
|
256602
256944
|
${preferredCommand} database push_migrations --ref abc123 --dir supabase/migrations --dry_run
|
|
256603
256945
|
${preferredCommand} supabase migration_new --name add_accounts
|
|
256604
256946
|
${preferredCommand} supabase db_diff --schema public --name add_accounts
|