@supacloud/cli 0.40.0 → 0.41.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.
Files changed (2) hide show
  1. package/dist/index.js +188 -18
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -239502,6 +239502,61 @@ function migrationExecutionStatements(statements) {
239502
239502
  const hasOuterTransaction = /^(?:BEGIN(?:\s+(?:WORK|TRANSACTION))?|START\s+TRANSACTION)$/i.test(first) && /^(?:COMMIT|END)(?:\s+(?:WORK|TRANSACTION))?$/i.test(last);
239503
239503
  return hasOuterTransaction ? statements.slice(1, -1) : [...statements];
239504
239504
  }
239505
+ function skipSqlTrivia(sql, start) {
239506
+ let cursor = start;
239507
+ for (;; ) {
239508
+ while (cursor < sql.length && /\s/.test(sql[cursor]))
239509
+ cursor++;
239510
+ if (sql.startsWith("--", cursor)) {
239511
+ cursor = lineCommentEnd(sql, cursor);
239512
+ continue;
239513
+ }
239514
+ if (sql.startsWith("/*", cursor)) {
239515
+ cursor = blockCommentEnd(sql, cursor);
239516
+ continue;
239517
+ }
239518
+ return cursor;
239519
+ }
239520
+ }
239521
+ function doStatementBody(sql, keywordEnd) {
239522
+ let cursor = skipSqlTrivia(sql, keywordEnd);
239523
+ if (/^LANGUAGE\b/i.test(sql.slice(cursor))) {
239524
+ cursor += "LANGUAGE".length;
239525
+ cursor = skipSqlTrivia(sql, cursor);
239526
+ if (sql[cursor] === "'")
239527
+ cursor = maskSingleQuotedString(sql, cursor).end;
239528
+ else if (sql[cursor] === '"')
239529
+ cursor = maskDoubleQuotedIdentifier(sql, cursor).end;
239530
+ else {
239531
+ const languageName = /^[A-Za-z_][A-Za-z0-9_]*/.exec(sql.slice(cursor));
239532
+ if (!languageName)
239533
+ return null;
239534
+ cursor += languageName[0].length;
239535
+ }
239536
+ cursor = skipSqlTrivia(sql, cursor);
239537
+ }
239538
+ if (sql[cursor] === "'") {
239539
+ const end = maskSingleQuotedString(sql, cursor).end;
239540
+ return sql.slice(cursor + 1, Math.max(cursor + 1, end - 1));
239541
+ }
239542
+ const tag = sql[cursor] === "$" ? dollarQuoteTagAt(sql, cursor) : "";
239543
+ if (!tag)
239544
+ return null;
239545
+ const bodyEnd = sql.indexOf(tag, cursor + tag.length);
239546
+ return bodyEnd === -1 ? null : sql.slice(cursor + tag.length, bodyEnd);
239547
+ }
239548
+ function topLevelDoBodies(sql) {
239549
+ const masked = maskSqlPolicyNoise(sql);
239550
+ const doKeywordPattern = /(?:^|;)\s*DO\b/gi;
239551
+ const bodies = [];
239552
+ let match;
239553
+ while ((match = doKeywordPattern.exec(masked)) !== null) {
239554
+ const body = doStatementBody(sql, doKeywordPattern.lastIndex);
239555
+ if (body !== null)
239556
+ bodies.push(body);
239557
+ }
239558
+ return bodies;
239559
+ }
239505
239560
  function splitTopLevelClauses(sql) {
239506
239561
  const masked = maskSqlNoise(sql);
239507
239562
  const clauses = [];
@@ -239596,14 +239651,6 @@ var RISK_RULES = [
239596
239651
  description: "Renames table. Causes immediate downtime for application code.",
239597
239652
  recommendation: "Follow Expand-Contract: Create a view or alias table during transition."
239598
239653
  },
239599
- {
239600
- type: "manual_review_do_block",
239601
- level: "HIGH",
239602
- pattern: /^\s*DO\b/i,
239603
- description: "DO blocks can execute dynamic or procedural DDL that static rules cannot inspect safely.",
239604
- recommendation: "Move schema changes into explicit SQL statements; push_migrations rejects opaque procedural SQL.",
239605
- blocksTransactionalPush: true
239606
- },
239607
239654
  {
239608
239655
  type: "manual_review_procedural_definition",
239609
239656
  level: "HIGH",
@@ -239705,7 +239752,7 @@ var MIGRATION_LEDGER_RELATION_END = String.raw`(?=$|[\s,;(*])`;
239705
239752
  var MIGRATION_LEDGER_DDL_OR_MAINTENANCE_PREFIX = String.raw`(?:CREATE\s+(?:UNIQUE\s+)?INDEX\b[^;]*\bON\s+(?:ONLY\s+)?|CREATE\s+(?:CONSTRAINT\s+)?TRIGGER\b[^;]*\bON\s+|DROP\s+TRIGGER\s+(?:IF\s+EXISTS\s+)?[^;]*\bON\s+|CREATE\s+RULE\b[^;]*\bTO\s+|DROP\s+RULE\s+(?:IF\s+EXISTS\s+)?[^;]*\bON\s+|CREATE\s+POLICY\b[^;]*\bON\s+|ALTER\s+POLICY\b[^;]*\bON\s+|DROP\s+POLICY\s+(?:IF\s+EXISTS\s+)?[^;]*\bON\s+|COMMENT\s+ON\s+TABLE\s+|SECURITY\s+LABEL(?:\s+FOR\s+[^;\s]+)?\s+ON\s+TABLE\s+|REINDEX(?:\s*\([^;)]*\))?\s+TABLE\s+(?:CONCURRENTLY\s+)?|CLUSTER(?:\s+VERBOSE)?\s+|VACUUM(?:\s*\([^;)]*\))?(?:\s+(?:FULL|FREEZE|VERBOSE|ANALYZE))*\s+|ANALYZE(?:\s*\([^;)]*\))?(?:\s+VERBOSE)?\s+)`;
239706
239753
  var MIGRATION_LEDGER_MODIFICATION_PATTERN = new RegExp(String.raw`\b(?:(?:CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?|INSERT\s+INTO\s+(?:ONLY\s+)?|UPDATE\s+(?:ONLY\s+)?|DELETE\s+FROM\s+(?:ONLY\s+)?|MERGE\s+INTO\s+(?:ONLY\s+)?|ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?|COPY\s+)` + MIGRATION_LEDGER_RELATION + MIGRATION_LEDGER_RELATION_END + String.raw`|(?:DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?|TRUNCATE(?:\s+TABLE)?\s+|LOCK\s+(?:TABLE\s+)?)[^;]*` + MIGRATION_LEDGER_RELATION + MIGRATION_LEDGER_RELATION_END + String.raw`|` + MIGRATION_LEDGER_DDL_OR_MAINTENANCE_PREFIX + MIGRATION_LEDGER_RELATION + MIGRATION_LEDGER_RELATION_END + String.raw`)`, "i");
239707
239754
  var NON_TABLE_PRIVILEGE_TARGET = String.raw`(?:ALL\s+(?:FUNCTIONS|PROCEDURES|ROUTINES|SEQUENCES)\s+IN\s+SCHEMA|DATABASE|DOMAIN|FOREIGN\s+DATA\s+WRAPPER|FOREIGN\s+SERVER|FUNCTION|LANGUAGE|LARGE\s+OBJECT|PARAMETER|PROCEDURE|ROUTINE|SCHEMA|SEQUENCE|TABLESPACE|TYPE)\b`;
239708
- var MIGRATION_LEDGER_PRIVILEGE_PATTERN = new RegExp(String.raw`\b(?:GRANT|REVOKE)\b[^;]*\bON\s+(?:(?:TABLE\s+)?(?!` + NON_TABLE_PRIVILEGE_TARGET + String.raw`)(?:(?!\b(?:TO|FROM)\b)[^;])*?` + MIGRATION_LEDGER_RELATION + MIGRATION_LEDGER_RELATION_END + String.raw`(?=[^;]*\b(?:TO|FROM)\b)|ALL\s+TABLES\s+IN\s+SCHEMA\s+"?(?:supabase_migrations|public)"?(?=$|[\s,;]))`, "i");
239755
+ var MIGRATION_LEDGER_PRIVILEGE_PATTERN = new RegExp(String.raw`\b(?:GRANT|REVOKE)\b[^;]*\bON\s+(?:(?:TABLE\s+)?(?!` + NON_TABLE_PRIVILEGE_TARGET + String.raw`)(?:(?!\b(?:TO|FROM)\b)[^;])*?` + MIGRATION_LEDGER_RELATION + MIGRATION_LEDGER_RELATION_END + String.raw`(?=[^;]*\b(?:TO|FROM)\b)|ALL\s+TABLES\s+IN\s+SCHEMA\s+"?supabase_migrations"?(?=$|[\s,;]))`, "i");
239709
239756
  var PUSH_BLOCKER_RULES = [
239710
239757
  {
239711
239758
  type: "unsupported_project_scope_management",
@@ -239832,6 +239879,7 @@ var MIGRATION_LEDGER_BLOCKER_RULES = [
239832
239879
  blocksTransactionalPush: true
239833
239880
  }
239834
239881
  ];
239882
+ var DO_BODY_BLOCKER_RULES = PUSH_BLOCKER_RULES.filter((rule) => rule.type !== "unsupported_transaction_control");
239835
239883
  function matchingRisks(rawStatement, maskedStatement, rules) {
239836
239884
  return rules.filter((rule) => rule.pattern.test(maskedStatement) && !rule.excludePattern?.test(maskedStatement)).map((rule) => ({
239837
239885
  level: rule.level,
@@ -239908,6 +239956,13 @@ function analyzeMigrationSql(sql) {
239908
239956
  risks.push(...matchingRisks(rawStatement, masked, PUSH_BLOCKER_RULES));
239909
239957
  risks.push(...matchingRisks(rawStatement, policyMasked, QUOTED_FUNCTION_BLOCKER_RULES));
239910
239958
  risks.push(...matchingRisks(rawStatement, policyMasked, MIGRATION_LEDGER_BLOCKER_RULES));
239959
+ for (const body of topLevelDoBodies(rawStatement)) {
239960
+ const maskedBody = maskSqlNoise(body);
239961
+ const policyMaskedBody = maskSqlPolicyNoise(body);
239962
+ risks.push(...matchingRisks(body, maskedBody, DO_BODY_BLOCKER_RULES));
239963
+ risks.push(...matchingRisks(body, policyMaskedBody, QUOTED_FUNCTION_BLOCKER_RULES));
239964
+ risks.push(...matchingRisks(body, policyMaskedBody, MIGRATION_LEDGER_BLOCKER_RULES));
239965
+ }
239911
239966
  }
239912
239967
  return risks;
239913
239968
  }
@@ -246635,9 +246690,9 @@ function parseModule(candidate, nameByNode, ctx) {
246635
246690
  className: cls.getName() ?? "<anonymous>",
246636
246691
  name: stringLiteralProp(meta, "name") ?? cls.getName() ?? "<anonymous>",
246637
246692
  permission: stringLiteralProp(meta, "permission"),
246638
- transaction: stringLiteralProp(meta, "transaction"),
246693
+ transaction: commandModeProp(meta, "transaction") ?? "none",
246639
246694
  audit: stringLiteralProp(meta, "audit"),
246640
- idempotency: stringLiteralProp(meta, "idempotency")
246695
+ idempotency: commandModeProp(meta, "idempotency") ?? "none"
246641
246696
  });
246642
246697
  }
246643
246698
  }
@@ -246665,6 +246720,10 @@ function parseModule(candidate, nameByNode, ctx) {
246665
246720
  exports
246666
246721
  };
246667
246722
  }
246723
+ function commandModeProp(object, name) {
246724
+ const value = stringLiteralProp(object, name);
246725
+ return value === "required" || value === "none" ? value : undefined;
246726
+ }
246668
246727
  function parseProvider(el, exportsSet, ctx) {
246669
246728
  const file = sourcePath(ctx.rootDir, el.getSourceFile().getFilePath());
246670
246729
  const line = el.getStartLineNumber();
@@ -246817,6 +246876,11 @@ function parseController(el, ctx) {
246817
246876
  schemaImports[schemaExpr.getText()] = importPath;
246818
246877
  }
246819
246878
  }
246879
+ const commandExpr = getProp(optionsArg, "command");
246880
+ if (commandExpr && import_ts_morph.Node.isIdentifier(commandExpr)) {
246881
+ const commandDecl = resolveDeclaration(commandExpr)[0];
246882
+ route.command = commandDecl && import_ts_morph.Node.isClassDeclaration(commandDecl) ? commandDecl.getName() ?? commandExpr.getText() : commandExpr.getText();
246883
+ }
246820
246884
  }
246821
246885
  routes.push(route);
246822
246886
  }
@@ -247052,6 +247116,16 @@ var INTERFACES = `export interface CompiledRoute {
247052
247116
  params?: unknown;
247053
247117
  query?: unknown;
247054
247118
  response?: unknown;
247119
+ command?: string;
247120
+ }
247121
+
247122
+ export interface CompiledCommand {
247123
+ className: string;
247124
+ name: string;
247125
+ permission: string;
247126
+ transaction: "required" | "none";
247127
+ audit?: string;
247128
+ idempotency: "required" | "none";
247055
247129
  }
247056
247130
 
247057
247131
  export interface CompiledController {
@@ -247070,12 +247144,15 @@ export interface CompiledModule {
247070
247144
  createRequestScope?(
247071
247145
  services: Record<string, unknown>,
247072
247146
  ctx: unknown,
247147
+ imported?: Record<string, Record<string, unknown>>,
247073
247148
  ): Record<string, unknown>;
247074
247149
  createJobScope?(
247075
247150
  services: Record<string, unknown>,
247076
247151
  ctx: unknown,
247152
+ imported?: Record<string, Record<string, unknown>>,
247077
247153
  ): Record<string, unknown>;
247078
247154
  controllers: CompiledController[];
247155
+ commands: CompiledCommand[];
247079
247156
  }`;
247080
247157
  async function generateApplication(graph, options) {
247081
247158
  const modules = topoSortModules(graph.modules);
@@ -247094,7 +247171,7 @@ async function generateApplication(graph, options) {
247094
247171
  ...imports.size > 0 ? [""] : [],
247095
247172
  INTERFACES,
247096
247173
  "",
247097
- "export function createCompiledModules(deps: Record<string, unknown>): CompiledModule[] {",
247174
+ "export function createCompiledModules(): CompiledModule[] {",
247098
247175
  " return [",
247099
247176
  ...descriptorEntries.map((entry) => indent(entry, 4) + ","),
247100
247177
  " ];",
@@ -247225,6 +247302,7 @@ class ModuleGenerator {
247225
247302
  lines.push(` createJobScope: create${this.pascal}JobScope,`);
247226
247303
  }
247227
247304
  lines.push(` controllers: ${this.renderControllers()},`);
247305
+ lines.push(` commands: ${JSON.stringify(this.module.commands)},`);
247228
247306
  lines.push(`}`);
247229
247307
  return lines.join(`
247230
247308
  `);
@@ -247249,6 +247327,8 @@ class ModuleGenerator {
247249
247327
  fields.push(`${field}: ${local}`);
247250
247328
  }
247251
247329
  }
247330
+ if (route.command)
247331
+ fields.push(`command: ${JSON.stringify(route.command)}`);
247252
247332
  return `{ ${fields.join(", ")} }`;
247253
247333
  });
247254
247334
  return [
@@ -247282,6 +247362,7 @@ ${indent(item, 2)}`).join(",")}
247282
247362
  `function create${this.pascal}${suffix}(`,
247283
247363
  ` services: Record<string, unknown>,`,
247284
247364
  ` ctx: unknown,`,
247365
+ ` imported: Record<string, Record<string, unknown>> = {},`,
247285
247366
  `): Record<string, unknown> {`,
247286
247367
  indent(this.renderFactoryBody(kind), 2),
247287
247368
  `}`
@@ -247380,7 +247461,7 @@ ${indent(item, 2)}`).join(",")}
247380
247461
  continue;
247381
247462
  if (kind === "services")
247382
247463
  return `imported.${importName}.${camelName(token)}`;
247383
- return `services.${camelName(token)}`;
247464
+ return `imported.${importName}.${camelName(token)}`;
247384
247465
  }
247385
247466
  if (kind === "services")
247386
247467
  return `deps.${camelName(token)}`;
@@ -247441,6 +247522,42 @@ function validateGraph(graph, strict = false) {
247441
247522
  const warn2 = (code, message, file, line) => {
247442
247523
  diagnostics.push({ severity: strict ? "error" : "warn", code, message, file, line });
247443
247524
  };
247525
+ const modulesByName = new Map;
247526
+ const commandsByName = new Map;
247527
+ const routesByKey = new Map;
247528
+ for (const module of graph.modules) {
247529
+ const previousModule = modulesByName.get(module.name);
247530
+ if (previousModule) {
247531
+ error("duplicate-module", `模块名 ${module.name} 重复(首次声明于 ${previousModule.file}:${previousModule.line})`, module.file, module.line);
247532
+ } else {
247533
+ modulesByName.set(module.name, module);
247534
+ }
247535
+ for (const command of module.commands) {
247536
+ const previousName = commandsByName.get(command.name);
247537
+ if (previousName) {
247538
+ error("duplicate-command", `command 名 ${command.name} 重复(首次由模块 ${previousName.module.name} 的 ${previousName.className} 声明)`, module.file, module.line);
247539
+ } else {
247540
+ commandsByName.set(command.name, { module, className: command.className });
247541
+ }
247542
+ }
247543
+ }
247544
+ for (const module of graph.modules) {
247545
+ for (const controller of module.controllers) {
247546
+ for (const route of controller.routes) {
247547
+ const fullPath = joinRoutePaths(controller.path, route.path);
247548
+ const key = `${route.method} ${fullPath}`;
247549
+ const previous = routesByKey.get(key);
247550
+ if (previous) {
247551
+ error("duplicate-route", `路由 ${key} 重复(首次声明于模块 ${previous.module.name} 的 ${previous.controller.className})`, controller.file);
247552
+ } else {
247553
+ routesByKey.set(key, { module, controller });
247554
+ }
247555
+ if (route.command && !module.commands.some((command) => command.className === route.command)) {
247556
+ error("route-command-unresolved", `路由 ${key} 绑定的 command 类 ${route.command} 未在模块 ${module.name} 声明`, controller.file);
247557
+ }
247558
+ }
247559
+ }
247560
+ }
247444
247561
  for (const module of graph.modules) {
247445
247562
  const seen = new Map;
247446
247563
  for (const provider of module.providers) {
@@ -247472,13 +247589,18 @@ function validateGraph(graph, strict = false) {
247472
247589
  }
247473
247590
  for (const command of module.commands) {
247474
247591
  if (!command.permission) {
247475
- warn2("command-missing-permission", `模块 ${module.name} 的 command ${command.name} (${command.className}) 未声明 permission`, module.file, module.line);
247592
+ error("command-missing-permission", `模块 ${module.name} 的 command ${command.name} (${command.className}) 未声明 permission`, module.file, module.line);
247476
247593
  }
247477
247594
  }
247478
247595
  }
247479
247596
  diagnostics.push(...detectCycles(graph, resolveDep));
247480
247597
  return diagnostics;
247481
247598
  }
247599
+ function joinRoutePaths(prefix, path) {
247600
+ const joined = `${prefix}/${path}`.replace(/\/{2,}/g, "/");
247601
+ const normalized = joined.length > 1 ? joined.replace(/\/+$/, "") : joined;
247602
+ return normalized.replace(/:[^/]+/g, ":param");
247603
+ }
247482
247604
  function detectCycles(graph, resolveDep) {
247483
247605
  const diagnostics = [];
247484
247606
  const nodeId = (ref) => `${ref.module.name}:${ref.provider.token}`;
@@ -247921,6 +248043,18 @@ JOIN pg_language l ON l.oid = p.prolang
247921
248043
  WHERE n.nspname = ANY($1)
247922
248044
  ORDER BY n.nspname, p.proname
247923
248045
  `;
248046
+ var TRIGGERS_SQL = `
248047
+ SELECT n.nspname AS schema,
248048
+ c.relname AS table,
248049
+ t.tgname AS name,
248050
+ t.tgenabled <> 'D' AS enabled
248051
+ FROM pg_trigger t
248052
+ JOIN pg_class c ON c.oid = t.tgrelid
248053
+ JOIN pg_namespace n ON n.oid = c.relnamespace
248054
+ WHERE NOT t.tgisinternal
248055
+ AND n.nspname = ANY($1)
248056
+ ORDER BY n.nspname, c.relname, t.tgname
248057
+ `;
247924
248058
  var GRANTS_SQL = `
247925
248059
  SELECT table_schema AS object_schema,
247926
248060
  table_name AS object_name,
@@ -247953,10 +248087,11 @@ function extractSearchPath(config) {
247953
248087
  }
247954
248088
  async function readCatalog(executor, schemas = ["public"]) {
247955
248089
  const params = [schemas];
247956
- const [tableRows, policyRows, functionRows, grantRows] = await Promise.all([
248090
+ const [tableRows, policyRows, functionRows, triggerRows, grantRows] = await Promise.all([
247957
248091
  executor.query(TABLES_SQL, params),
247958
248092
  executor.query(POLICIES_SQL, params),
247959
248093
  executor.query(FUNCTIONS_SQL, params),
248094
+ executor.query(TRIGGERS_SQL, params),
247960
248095
  executor.query(GRANTS_SQL, params)
247961
248096
  ]);
247962
248097
  return {
@@ -247982,6 +248117,12 @@ async function readCatalog(executor, schemas = ["public"]) {
247982
248117
  searchPath: extractSearchPath(row.config),
247983
248118
  language: row.language
247984
248119
  })),
248120
+ triggers: triggerRows.map((row) => ({
248121
+ schema: row.schema,
248122
+ table: row.table,
248123
+ name: row.name,
248124
+ enabled: row.enabled
248125
+ })),
247985
248126
  grants: grantRows.map((row) => ({
247986
248127
  objectSchema: row.object_schema,
247987
248128
  objectName: row.object_name,
@@ -248035,6 +248176,20 @@ function reconcileModule(module, catalog) {
248035
248176
  push("error", "definer-without-search-path", fn.name, `security definer 函数 ${fn.name} 未设置固定 search_path(当前: ${cf.searchPath ?? "未设置"})`);
248036
248177
  }
248037
248178
  }
248179
+ for (const trigger of module.triggers) {
248180
+ const [schema, table] = splitQualifiedName(trigger.table);
248181
+ const found = catalog.triggers.some((ct) => ct.schema === schema && ct.table === table && ct.name === trigger.name);
248182
+ if (!found) {
248183
+ push("error", "missing-trigger", `${trigger.table}.${trigger.name}`, `声明的触发器 ${trigger.name} 在表 ${trigger.table} 的 catalog 中不存在`);
248184
+ }
248185
+ }
248186
+ const declaredTriggerKeys = new Set(module.triggers.map((t) => `${t.table}::${t.name}`));
248187
+ for (const ct of catalog.triggers) {
248188
+ const qualified = `${ct.schema}.${ct.table}`;
248189
+ if (ownedTables.has(qualified) && !declaredTriggerKeys.has(`${qualified}::${ct.name}`)) {
248190
+ push("warn", "undeclared-trigger", `${qualified}.${ct.name}`, `归属表 ${qualified} 上存在未声明的触发器 ${ct.name}${ct.enabled ? "" : "(已禁用)"},可能发生漂移`);
248191
+ }
248192
+ }
248038
248193
  for (const table of module.tables) {
248039
248194
  const [schema, name] = splitQualifiedName(table);
248040
248195
  const ct = catalog.tables.find((t) => t.schema === schema && t.name === name);
@@ -248066,6 +248221,8 @@ var SET_SEARCH_PATH_RE = /\bset\s+search_path\b/i;
248066
248221
  var GRANT_TO_PUBLIC_RE = /\bgrant\b[^;]*\bto\s+public\b/i;
248067
248222
  var DROP_WITHOUT_IF_EXISTS_RE = /\bdrop\s+(?:table|column)\s+(?!if\s+exists\b)/i;
248068
248223
  var ENABLE_RLS_RE = /\benable\s+row\s+level\s+security\b/i;
248224
+ var CREATE_POLICY_RE = /\bcreate\s+policy\b/i;
248225
+ var DROP_POLICY_IF_EXISTS_RE = /\bdrop\s+policy\s+if\s+exists\b/i;
248069
248226
  function lineOf(sql, index) {
248070
248227
  let line = 1;
248071
248228
  for (let i = 0;i < index; i += 1) {
@@ -248106,6 +248263,19 @@ function lintSql(sql, file) {
248106
248263
  line: lineOf(sql, drop.index)
248107
248264
  });
248108
248265
  }
248266
+ const createPolicy = CREATE_POLICY_RE.exec(sql);
248267
+ if (createPolicy) {
248268
+ const dropPolicy = DROP_POLICY_IF_EXISTS_RE.exec(sql);
248269
+ if (!dropPolicy || dropPolicy.index > createPolicy.index) {
248270
+ issues.push({
248271
+ severity: "warn",
248272
+ code: "non-idempotent-policy",
248273
+ message: "create policy 前缺少 drop policy if exists,策略不可重复执行",
248274
+ file,
248275
+ line: lineOf(sql, createPolicy.index)
248276
+ });
248277
+ }
248278
+ }
248109
248279
  return issues;
248110
248280
  }
248111
248281
  async function lintModule(module, readFile2) {
@@ -250641,7 +250811,7 @@ function registerDeployTools(server2, http, options = {}) {
250641
250811
  // package.json
250642
250812
  var package_default = {
250643
250813
  name: "@supacloud/cli",
250644
- version: "0.40.0",
250814
+ version: "0.41.0",
250645
250815
  description: "Project-scoped CLI for SupaCloud users",
250646
250816
  type: "module",
250647
250817
  main: "./dist/index.js",
@@ -250673,8 +250843,8 @@ var package_default = {
250673
250843
  },
250674
250844
  dependencies: {
250675
250845
  "@sinclair/typebox": "^0.34.52",
250676
- "@supacloud/compiler": "^0.1.0",
250677
- "@supacloud/db": "^0.1.0",
250846
+ "@supacloud/compiler": "^0.2.0",
250847
+ "@supacloud/db": "^0.2.0",
250678
250848
  fflate: "^0.8.3"
250679
250849
  },
250680
250850
  devDependencies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@sinclair/typebox": "^0.34.52",
35
- "@supacloud/compiler": "^0.1.0",
36
- "@supacloud/db": "^0.1.0",
35
+ "@supacloud/compiler": "^0.2.0",
36
+ "@supacloud/db": "^0.2.0",
37
37
  "fflate": "^0.8.3"
38
38
  },
39
39
  "devDependencies": {