@supacloud/cli 0.62.1 → 0.64.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 (3) hide show
  1. package/README.md +12 -3
  2. package/dist/index.js +881 -777
  3. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -206727,7 +206727,7 @@ var ACTION_POLICY = {
206727
206727
  },
206728
206728
  ai: { local: ["show_skill", "install_skill"] },
206729
206729
  app: {
206730
- local: ["init", "generate", "dev", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
206730
+ local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
206731
206731
  read: ["status"],
206732
206732
  write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire"]
206733
206733
  },
@@ -217356,6 +217356,7 @@ function confirmedFunctionDeletion(payload, expectation) {
217356
217356
  var execFileAsync = promisify(execFile);
217357
217357
  var SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
217358
217358
  var FORBIDDEN_BUNDLE_SEGMENTS = new Set(["node_modules", ".git"]);
217359
+ var EDGE_FUNCTION_DEPLOY_TIMEOUT_MS = 5 * 60000;
217359
217360
  var FUNCTION_FRAMEWORKS2 = ["fetch", "elysia", "hono", "sveltekit-function"];
217360
217361
  function scaffoldFiles(framework, slug) {
217361
217362
  if (framework === "elysia") {
@@ -218217,7 +218218,7 @@ ${deployCheck.err}`;
218217
218218
  expected_active_version: expectedActiveVersion,
218218
218219
  expected_activation_id: expectedActivationId,
218219
218220
  ...functionConfig()
218220
- });
218221
+ }, { timeoutMs: EDGE_FUNCTION_DEPLOY_TIMEOUT_MS });
218221
218222
  return functionMutationResponse({
218222
218223
  operation: "edge_functions.deploy",
218223
218224
  projectRef: ref,
@@ -218236,7 +218237,7 @@ ${deployCheck.err}`;
218236
218237
  expected_active_version: expectedActiveVersion,
218237
218238
  expected_activation_id: expectedActivationId,
218238
218239
  ...functionConfig()
218239
- });
218240
+ }, { timeoutMs: EDGE_FUNCTION_DEPLOY_TIMEOUT_MS });
218240
218241
  return functionMutationResponse({
218241
218242
  operation: "edge_functions.deploy_bundle",
218242
218243
  projectRef: ref,
@@ -221518,9 +221519,9 @@ function registerAiTools(server2) {
221518
221519
  }
221519
221520
 
221520
221521
  // src/shared/tools/app-tools.ts
221521
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
221522
- import { mkdir as mkdir6, readFile as readFile8, readdir as readdir5, writeFile as writeFile5 } from "node:fs/promises";
221523
- import { basename as basename8, dirname as dirname12, join as join15, relative as relative12, resolve as resolve17 } from "node:path";
221522
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
221523
+ import { mkdir as mkdir6, readFile as readFile9, readdir as readdir6, writeFile as writeFile5 } from "node:fs/promises";
221524
+ import { basename as basename8, dirname as dirname13, join as join17, relative as relative13, resolve as resolve21 } from "node:path";
221524
221525
 
221525
221526
  // node_modules/@supacloud/compiler/dist/index.js
221526
221527
  import { dirname as dirname22, relative as relative6, sep as sep4 } from "node:path";
@@ -229355,7 +229356,7 @@ function renderClient(graph, options) {
229355
229356
  "",
229356
229357
  "export interface ApiClientConfig {",
229357
229358
  " baseUrl?: string;",
229358
- " fetch?: typeof fetch;",
229359
+ " fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;",
229359
229360
  " headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);",
229360
229361
  " interceptors?: HttpInterceptorFn[];",
229361
229362
  " /** Normalize response objects like Elysia's default schema validator. */",
@@ -246659,6 +246660,46 @@ function buildToolDefinitions(manifest) {
246659
246660
  }
246660
246661
  return { openai, mcp };
246661
246662
  }
246663
+ // src/shared/tools/starter-metadata.json
246664
+ var starter_metadata_default = {
246665
+ schemaVersion: 1,
246666
+ generatedBy: "supacloud/starter-metadata-v1",
246667
+ packages: {
246668
+ compiler: {
246669
+ version: "0.31.1"
246670
+ },
246671
+ app: {
246672
+ version: "0.22.0",
246673
+ dependencies: {
246674
+ rxjs: "7.8.2"
246675
+ }
246676
+ },
246677
+ elysia: {
246678
+ version: "0.23.1"
246679
+ },
246680
+ commands: {
246681
+ version: "0.9.0"
246682
+ },
246683
+ contracts: {
246684
+ version: "0.8.0"
246685
+ },
246686
+ db: {
246687
+ version: "0.13.0",
246688
+ peerDependencies: {
246689
+ "drizzle-orm": "1.0.0-rc.4"
246690
+ },
246691
+ devDependencies: {
246692
+ "drizzle-kit": "1.0.0-rc.4"
246693
+ }
246694
+ },
246695
+ sdk: {
246696
+ version: "0.36.1",
246697
+ peerDependencies: {
246698
+ "@supabase/supabase-js": "^2.115.0"
246699
+ }
246700
+ }
246701
+ }
246702
+ };
246662
246703
 
246663
246704
  // src/shared/tools/app-starter-reactive.ts
246664
246705
  var STARTER_REACTIVE_GUIDE = `# Reactive development / 响应式开发
@@ -246884,259 +246925,9 @@ test("partial and untagged inherited remote profiles cannot mix with selected fi
246884
246925
  expect(() => loadEnvironment("staging", root, {})).toThrow("Each remote file profile");
246885
246926
  });
246886
246927
  `;
246887
- // ../compiler/package.json
246888
- var package_default = {
246889
- name: "@supacloud/compiler",
246890
- version: "0.31.0",
246891
- description: "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
246892
- type: "module",
246893
- main: "./dist/index.js",
246894
- module: "./dist/index.js",
246895
- types: "./dist/index.d.ts",
246896
- bin: {
246897
- "supacloud-compiler": "./dist/cli.js"
246898
- },
246899
- exports: {
246900
- ".": {
246901
- types: "./dist/index.d.ts",
246902
- import: "./dist/index.js",
246903
- default: "./dist/index.js"
246904
- },
246905
- "./diagnostics": {
246906
- types: "./dist/diagnostics.d.ts",
246907
- import: "./dist/diagnostics.js",
246908
- default: "./dist/diagnostics.js"
246909
- }
246910
- },
246911
- files: [
246912
- "NOTICE",
246913
- "LICENSE-APACHE-2.0.txt",
246914
- "dist",
246915
- "README.md",
246916
- "DELIVERY.md"
246917
- ],
246918
- scripts: {
246919
- build: "bun run clean && bun run build:js && bun run build:types",
246920
- "build:js": "bun build src/index.ts src/diagnostics.ts src/cli.ts src/delivery-bundle-worker.ts --outdir dist --target node --conditions=supacloud-source --external @typescript/typescript6 --external graphql --external '@graphql-codegen/*'",
246921
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
246922
- clean: "rm -rf dist",
246923
- prepublishOnly: "bun run build && bun run test:package",
246924
- test: "bun test",
246925
- "test:package": 'SUPACLOUD_COMPILER_TEST_CLI="$PWD/dist/cli.js" bun test src/graphql-package.test.ts',
246926
- benchmark: "bun run src/benchmark.ts",
246927
- typecheck: "tsc -p tsconfig.json --noEmit",
246928
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit"
246929
- },
246930
- keywords: [
246931
- "supacloud",
246932
- "compiler",
246933
- "di",
246934
- "codegen",
246935
- "elysia"
246936
- ],
246937
- license: "Apache-2.0",
246938
- repository: {
246939
- type: "git",
246940
- url: "https://github.com/vibeunion/supacloud.git",
246941
- directory: "packages/compiler"
246942
- },
246943
- dependencies: {
246944
- "@supacloud/delivery": "0.4.0",
246945
- typebox: "1.3.34",
246946
- "@graphql-codegen/core": "^6.2.0",
246947
- "@graphql-codegen/typed-document-node": "^7.1.0",
246948
- "@graphql-codegen/typescript-operations": "^6.1.6",
246949
- "@graphql-typed-document-node/core": "^3.2.0",
246950
- "@typescript/typescript6": "^6.0.2",
246951
- graphql: "^16"
246952
- },
246953
- devDependencies: {
246954
- "@supabase/supabase-js": "2.115.0",
246955
- "drizzle-orm": "1.0.0-rc.4",
246956
- "@types/bun": "^1.4.2",
246957
- typescript: "^7.0.2"
246958
- }
246959
- };
246960
- // ../app/package.json
246961
- var package_default2 = {
246962
- name: "@supacloud/app",
246963
- version: "0.21.1",
246964
- description: "Angular-style application metadata for SupaCloud: modules, DI tokens, providers, scopes, controllers and commands",
246965
- type: "module",
246966
- main: "./dist/index.js",
246967
- module: "./dist/index.js",
246968
- types: "./dist/index.d.ts",
246969
- exports: {
246970
- "./contracts": {
246971
- types: "./dist/contract_client.d.ts",
246972
- import: "./dist/contract_client.js",
246973
- default: "./dist/contract_client.js"
246974
- },
246975
- "./browser": {
246976
- types: "./dist/browser.d.ts",
246977
- import: "./dist/browser.js",
246978
- default: "./dist/browser.js"
246979
- },
246980
- ".": {
246981
- types: "./dist/index.d.ts",
246982
- import: "./dist/index.js",
246983
- default: "./dist/index.js"
246984
- },
246985
- "./execution": {
246986
- types: "./dist/execution.d.ts",
246987
- import: "./dist/execution.js",
246988
- default: "./dist/execution.js"
246989
- },
246990
- "./reactive": {
246991
- types: "./dist/reactive.d.ts",
246992
- import: "./dist/reactive.js",
246993
- default: "./dist/reactive.js"
246994
- },
246995
- "./rxjs": {
246996
- types: "./dist/rxjs.d.ts",
246997
- import: "./dist/rxjs.js",
246998
- default: "./dist/rxjs.js"
246999
- },
247000
- "./angular": {
247001
- types: "./dist/angular.d.ts",
247002
- import: "./dist/angular.js",
247003
- default: "./dist/angular.js"
247004
- },
247005
- "./http": {
247006
- types: "./dist/http.d.ts",
247007
- import: "./dist/http.js",
247008
- default: "./dist/http.js"
247009
- },
247010
- "./runtime": {
247011
- types: "./dist/runtime.d.ts",
247012
- import: "./dist/runtime.js",
247013
- default: "./dist/runtime.js"
247014
- }
247015
- },
247016
- files: [
247017
- "NOTICE",
247018
- "LICENSE-APACHE-2.0.txt",
247019
- "dist",
247020
- "README.md",
247021
- "EXECUTION.md",
247022
- "REACTIVE.md",
247023
- "ANGULAR.md"
247024
- ],
247025
- scripts: {
247026
- build: "bun run clean && bun run build:js && bun run build:types",
247027
- "build:js": "bun build src/index.ts src/execution.ts --outdir dist --target node --external @supacloud/contracts --external '@angular/*' --external rxjs --format esm && bun build src/contract_client.ts src/browser.ts --outdir dist --target browser --external @supacloud/contracts --format esm && bun build src/reactive.ts --outdir dist --target browser --external rxjs --format esm && bun build src/angular.ts src/rxjs.ts --outdir dist --target browser --external '@angular/*' --external rxjs --format esm && bun build src/http.ts --outdir dist --target node --external @supacloud/app --format esm && bun build src/runtime.ts --outdir dist --target browser --format esm",
247028
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
247029
- clean: "rm -rf dist",
247030
- "check:package": "node ../../.github/scripts/esm-package-policy.mjs --pack .",
247031
- prepublishOnly: "bun run build && bun run check:package",
247032
- test: "bun test",
247033
- typecheck: "tsc -p tsconfig.json --noEmit",
247034
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit"
247035
- },
247036
- keywords: [
247037
- "supacloud",
247038
- "di",
247039
- "dependency-injection",
247040
- "module",
247041
- "elysia"
247042
- ],
247043
- license: "Apache-2.0",
247044
- repository: {
247045
- type: "git",
247046
- url: "https://github.com/vibeunion/supacloud.git",
247047
- directory: "packages/app"
247048
- },
247049
- dependencies: {
247050
- "@angular/core": "^22.1.5",
247051
- "@supacloud/contracts": "0.8.0",
247052
- rxjs: "7.8.2",
247053
- typebox: "1.3.34"
247054
- },
247055
- devDependencies: {
247056
- "@types/bun": "^1.4.2",
247057
- "@types/node": "^26.5.0",
247058
- typescript: "^7.0.2",
247059
- "@angular/compiler": "22.1.5"
247060
- }
247061
- };
247062
- // ../elysia/package.json
247063
- var package_default3 = {
247064
- name: "@supacloud/elysia",
247065
- version: "0.23.1",
247066
- description: "Elysia runtime adapter for SupaCloud compiled modules: application/request scopes, route registration and validation",
247067
- type: "module",
247068
- main: "./dist/index.js",
247069
- module: "./dist/index.js",
247070
- types: "./dist/index.d.ts",
247071
- exports: {
247072
- ".": {
247073
- types: "./dist/index.d.ts",
247074
- import: "./dist/index.js",
247075
- default: "./dist/index.js"
247076
- }
247077
- },
247078
- files: [
247079
- "NOTICE",
247080
- "LICENSE-APACHE-2.0.txt",
247081
- "dist",
247082
- "README.md",
247083
- "compatibility.json"
247084
- ],
247085
- scripts: {
247086
- "generate:example": "bun src/generate-webhook-example.ts",
247087
- build: "bun run clean && bun run build:js && bun run build:types",
247088
- "build:js": "bun build src/index.ts --outdir dist --target node --external elysia --external jose --external @supacloud/app --external @supacloud/db --external @supacloud/contracts",
247089
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
247090
- clean: "rm -rf dist",
247091
- prepublishOnly: "bun run build",
247092
- test: "bun test",
247093
- "test:conformance": "bun test src/conformance.test.ts src/application-execution.test.ts ../app/src/execution.test.ts ../app/src/execution_bundle.test.ts",
247094
- "test:runtime-safety": "SUPACLOUD_REQUIRE_RUNTIME_SAFETY=1 bun test src/runtime-safety.test.ts",
247095
- "test:contract-upgrade": "bun test src/contract-upgrade.test.ts",
247096
- "bench:http-policy": "bun bench/http-policy.ts",
247097
- typecheck: "tsc -p tsconfig.json --noEmit",
247098
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit"
247099
- },
247100
- keywords: [
247101
- "supacloud",
247102
- "elysia",
247103
- "adapter",
247104
- "dependency-injection",
247105
- "module"
247106
- ],
247107
- license: "Apache-2.0",
247108
- repository: {
247109
- type: "git",
247110
- url: "https://github.com/vibeunion/supacloud.git",
247111
- directory: "packages/elysia"
247112
- },
247113
- dependencies: {
247114
- "@supacloud/app": "file:../app",
247115
- "@supacloud/contracts": "file:../contracts",
247116
- "exact-mirror": "1.2.6",
247117
- jose: "^6.2.11",
247118
- typebox: "1.3.34"
247119
- },
247120
- overrides: {
247121
- "@supacloud/delivery": "file:../delivery",
247122
- "@supacloud/contracts": "file:../contracts",
247123
- "@supacloud/commands": "file:../commands"
247124
- },
247125
- peerDependencies: {
247126
- elysia: "2.0.0-beta.19"
247127
- },
247128
- devDependencies: {
247129
- "@supacloud/compiler": "file:../compiler",
247130
- "@supacloud/delivery": "file:../delivery",
247131
- "@supacloud/commands": "file:../commands",
247132
- "@supacloud/db": "file:../db",
247133
- "@types/bun": "^1.4.2",
247134
- elysia: "2.0.0-beta.19",
247135
- typescript: "^7.0.2"
247136
- }
247137
- };
247138
246928
 
247139
246929
  // src/shared/tools/app-starter-templates.ts
246930
+ var { compiler: compilerMetadata, app: appMetadata, elysia: elysiaMetadata } = starter_metadata_default.packages;
247140
246931
  var json = (value) => `${JSON.stringify(value, null, 2)}
247141
246932
  `;
247142
246933
  var DEV_SCRIPT = `import { watchProject, compileOptionsFromConfig, loadSupacloudConfig } from "@supacloud/compiler";
@@ -247212,7 +247003,8 @@ export function createCompiledModules(): never {
247212
247003
  throw new Error("Run bun run compile before starting the application");
247213
247004
  }
247214
247005
  `;
247215
- function baseFiles(name) {
247006
+ function baseFiles(name, sdkDependencies) {
247007
+ const minimal = sdkDependencies !== undefined;
247216
247008
  return {
247217
247009
  "package.json": json({
247218
247010
  name,
@@ -247235,13 +247027,13 @@ function baseFiles(name) {
247235
247027
  "env:production": "bun --no-env-file scripts/environment.ts production"
247236
247028
  },
247237
247029
  dependencies: {
247238
- "@supacloud/app": `^${package_default2.version}`,
247239
- "@supacloud/elysia": `^${package_default3.version}`,
247030
+ "@supacloud/app": `^${appMetadata.version}`,
247031
+ "@supacloud/elysia": `^${elysiaMetadata.version}`,
247240
247032
  elysia: "2.0.0-beta.19",
247241
- rxjs: package_default2.dependencies.rxjs
247033
+ ...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
247242
247034
  },
247243
247035
  devDependencies: {
247244
- "@supacloud/compiler": `^${package_default.version}`,
247036
+ "@supacloud/compiler": `^${compilerMetadata.version}`,
247245
247037
  "@types/bun": "^1.4.2",
247246
247038
  typescript: "^7.0.2"
247247
247039
  }
@@ -247289,9 +247081,11 @@ export default defineSupacloudConfig({
247289
247081
  `,
247290
247082
  "src/application.ts": APPLICATION_SOURCE(name),
247291
247083
  "generated/application.ts": GENERATED_BOOTSTRAP,
247292
- "REACTIVE.md": STARTER_REACTIVE_GUIDE,
247293
- "AGENTS.md": STARTER_REACTIVE_AGENTS,
247294
- "tests/reactive.test.ts": STARTER_REACTIVE_TEST,
247084
+ ...minimal ? {} : {
247085
+ "REACTIVE.md": STARTER_REACTIVE_GUIDE,
247086
+ "AGENTS.md": STARTER_REACTIVE_AGENTS,
247087
+ "tests/reactive.test.ts": STARTER_REACTIVE_TEST
247088
+ },
247295
247089
  "scripts/environment.ts": STARTER_ENVIRONMENT,
247296
247090
  "tests/environment.test.ts": STARTER_ENVIRONMENT_TEST,
247297
247091
  "scripts/dev.ts": DEV_SCRIPT,
@@ -247487,7 +247281,111 @@ supacloud doctor --root .
247487
247281
  `
247488
247282
  };
247489
247283
  }
247490
- function appTemplateFiles(name, template) {
247284
+ function minimalTemplate(name, sdkDependencies) {
247285
+ return {
247286
+ ...baseFiles(name, sdkDependencies),
247287
+ "src/features/health/health.ts": `import { Controller, Get, Module } from "@supacloud/app/core";
247288
+ import { t } from "elysia";
247289
+
247290
+ export const HealthResult = t.Object({ ok: t.Boolean() });
247291
+
247292
+ @Controller("/health")
247293
+ export class HealthController {
247294
+ @Get("/", { responses: { 200: HealthResult } })
247295
+ health(): { ok: boolean } { return { ok: true }; }
247296
+ }
247297
+
247298
+ @Module({ name: "health", tags: ["type:feature"], controllers: [HealthController] })
247299
+ export class HealthModule {}
247300
+ `,
247301
+ "src/features/health/health.test.ts": `import { expect, test } from "bun:test";
247302
+ import { HealthController } from "./health";
247303
+
247304
+ test("health is a public, side-effect-free operation", () => {
247305
+ expect(new HealthController().health()).toEqual({ ok: true });
247306
+ });
247307
+ `,
247308
+ "src/app.module.ts": `import { Module } from "@supacloud/app/core";
247309
+ import { HealthModule } from "./features/health/health";
247310
+
247311
+ @Module({ name: "application-root", tags: ["type:app"], imports: [HealthModule] })
247312
+ export class AppModule {}
247313
+ `,
247314
+ "scripts/sandbox.ts": `import { createMemorySandbox } from "@supacloud/elysia";
247315
+ import { createCompiledModules } from "../generated/application";
247316
+
247317
+ export function createDemo() {
247318
+ if (process.env.APP_ENV !== "development" && process.env.APP_ENV !== "test") {
247319
+ throw new Error("Local adapters are restricted to development and test");
247320
+ }
247321
+ return createMemorySandbox({ modules: createCompiledModules() });
247322
+ }
247323
+ `,
247324
+ "AGENTS.md": `# Application changes
247325
+
247326
+ - Business code and its tests belong in src/features/<feature>.
247327
+ - Use @supacloud/app/core for metadata; keep UI in the selected frontend framework.
247328
+ - Reuse @supacloud/js and the existing Supabase session for platform access.
247329
+ - Ordinary reads use the RLS-protected Supabase client. GraphQL is opt-in.
247330
+ - Cross-table business writes require trusted authorization, transactions,
247331
+ idempotency and audit. Never put service-role or Management tokens in a browser.
247332
+ - Do not edit generated/. Do not rewrite historical migrations.
247333
+ - Run the directly related single test file and git diff --check for daily changes.
247334
+ - Use app verify-plan --target <module> to inspect the focused verification plan.
247335
+ - Do not introduce another workflow engine, DI container or session store.
247336
+ `,
247337
+ "README.md": `# ${name}
247338
+
247339
+ ## Local development
247340
+
247341
+ \`\`\`sh
247342
+ bun install
247343
+ bun run compile
247344
+ bun test src/features/health/health.test.ts
247345
+ bun run dev
247346
+ \`\`\`
247347
+
247348
+ The local server binds to 127.0.0.1:3000 and exposes GET /health.
247349
+ The dev script compiles, watches and restarts after successful compilation.
247350
+ Memory adapters are local-only; this is not production identity or persistence.
247351
+
247352
+ ## Add a feature
247353
+
247354
+ Keep code, contracts and tests in src/features/<feature>; register modules in
247355
+ src/app.module.ts. Use @supacloud/app/core, not a frontend compatibility API.
247356
+ Use app generate --kind resource for an authorized read-port skeleton.
247357
+ It fails closed until the application supplies its data adapter.
247358
+
247359
+ ## Frontend and database
247360
+
247361
+ @supacloud/js wraps your existing @supabase/supabase-js client. Reuse its session,
247362
+ Storage and RLS-protected PostgREST reads. Generated business clients can use
247363
+ createAuthenticatedFetch from @supacloud/js/contracts with that same session.
247364
+ Use fixed trusted HTTPS endpoints and never retry an uncertain write.
247365
+
247366
+ GraphQL is optional; this starter has no synthetic GraphQL schema.
247367
+ For a complete persistent command/attachment example, create a separate reference
247368
+ project with app init --template command. The http and edge recipes also remain
247369
+ explicit options; do not install every recipe into this application.
247370
+
247371
+ ## Integration and delivery
247372
+
247373
+ createApp requires application-owned trusted identity and durable adapters.
247374
+ Database migrations and seed data require an explicitly selected development
247375
+ database and a reviewed application script; the CLI never infers a production target.
247376
+ Commit generated/ after compilation. At release, check generated drift before
247377
+ regeneration, then validate types, database permissions and authenticated behavior.
247378
+ `
247379
+ };
247380
+ }
247381
+ function appTemplateFiles(name, template, sdkDependencies) {
247382
+ if (template === "minimal") {
247383
+ if (!sdkDependencies)
247384
+ throw new Error("Minimal template requires the initializer's SDK dependency versions");
247385
+ return minimalTemplate(name, sdkDependencies);
247386
+ }
247387
+ if (template !== "http" && template !== "edge")
247388
+ throw new Error("Use appStarterFiles for the command recipe");
247491
247389
  const files = template === "http" ? httpTemplate(name) : edgeTemplate(name);
247492
247390
  const feature = template === "http" ? "OrdersFeature" : "SyncFeature";
247493
247391
  const source = template === "http" ? "./orders/orders" : "./sync/sync";
@@ -248521,329 +248419,135 @@ export async function createDeliveryApplication(modules: CompiledModule[], lifec
248521
248419
  }
248522
248420
  }
248523
248421
  `;
248524
- // ../commands/package.json
248525
- var package_default4 = {
248526
- name: "@supacloud/commands",
248527
- version: "0.9.0",
248528
- description: "Storage-independent durable command execution and recovery",
248529
- type: "module",
248530
- main: "./dist/index.js",
248531
- types: "./dist/index.d.ts",
248532
- exports: {
248533
- "./typebox": {
248534
- types: "./dist/typebox.d.ts",
248535
- import: "./dist/typebox.js"
248536
- },
248537
- ".": {
248538
- types: "./dist/index.d.ts",
248539
- import: "./dist/index.js"
248540
- }
248541
- },
248542
- files: [
248543
- "NOTICE",
248544
- "LICENSE-APACHE-2.0.txt",
248545
- "dist",
248546
- "README.md"
248547
- ],
248548
- scripts: {
248549
- build: "bun run build:js && bun run build:types",
248550
- "build:js": "bun build src/index.ts src/typebox.ts --outdir dist --target node --external @supacloud/contracts --external @sinclair/typebox",
248551
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
248552
- typecheck: "tsc -p tsconfig.json --noEmit",
248553
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
248554
- test: "bun test",
248555
- prepublishOnly: "bun run build"
248556
- },
248557
- dependencies: {
248558
- "@supacloud/contracts": "file:../contracts"
248559
- },
248560
- devDependencies: {
248561
- "@sinclair/typebox": "0.34.52",
248562
- "@types/bun": "1.4.2",
248563
- typescript: "7.0.2"
248564
- },
248565
- peerDependencies: {
248566
- "@sinclair/typebox": "0.34.52"
248567
- },
248568
- peerDependenciesMeta: {
248569
- "@sinclair/typebox": {
248570
- optional: true
248571
- }
248572
- },
248573
- license: "Apache-2.0",
248574
- repository: {
248575
- type: "git",
248576
- url: "https://github.com/vibeunion/supacloud.git",
248577
- directory: "packages/commands"
248578
- }
248579
- };
248580
- // ../contracts/package.json
248581
- var package_default5 = {
248582
- name: "@supacloud/contracts",
248583
- version: "0.8.0",
248584
- description: "SupaCloud command protocol with optional client and browser entrypoints",
248585
- type: "module",
248586
- main: "./dist/index.cjs",
248587
- types: "./dist/index.d.mts",
248588
- exports: {
248589
- ".": {
248590
- import: {
248591
- types: "./dist/index.d.mts",
248592
- default: "./dist/index.mjs"
248593
- },
248594
- require: {
248595
- types: "./dist/index.d.cts",
248596
- default: "./dist/index.cjs"
248597
- }
248598
- },
248599
- "./client": {
248600
- import: {
248601
- types: "./dist/client.d.mts",
248602
- default: "./dist/client.mjs"
248603
- },
248604
- require: {
248605
- types: "./dist/client.d.cts",
248606
- default: "./dist/client.cjs"
248607
- }
248608
- },
248609
- "./browser": {
248610
- import: {
248611
- types: "./dist/browser.d.mts",
248612
- default: "./dist/browser.mjs"
248613
- },
248614
- require: {
248615
- types: "./dist/browser.d.cts",
248616
- default: "./dist/browser.cjs"
248617
- }
248618
- }
248619
- },
248620
- files: [
248621
- "NOTICE",
248622
- "LICENSE-APACHE-2.0.txt",
248623
- "dist",
248624
- "README.md"
248625
- ],
248626
- scripts: {
248627
- build: "bun run clean && bun run build:js && bun run build:types",
248628
- "build:js": "bun run build:js:esm && bun run build:js:cjs",
248629
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/copy-dual-declarations.mjs dist",
248630
- typecheck: "tsc -p tsconfig.json --noEmit",
248631
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
248632
- test: "bun test",
248633
- prepublishOnly: "bun run build",
248634
- "build:js:esm": 'bun build src/index.ts src/client.ts src/browser.ts --outdir dist --format esm --entry-naming "[name].mjs" --chunk-naming "[name]-[hash].mjs" --target browser --splitting',
248635
- "build:js:cjs": 'bun build src/index.ts src/client.ts src/browser.ts --outdir dist --format cjs --entry-naming "[name].cjs" --chunk-naming "[name]-[hash].cjs" --target browser',
248636
- clean: "rm -rf dist"
248637
- },
248638
- devDependencies: {
248639
- "@types/bun": "1.4.2",
248640
- typescript: "7.0.2"
248641
- },
248642
- license: "Apache-2.0",
248643
- repository: {
248644
- type: "git",
248645
- url: "https://github.com/vibeunion/supacloud.git",
248646
- directory: "packages/contracts"
248647
- },
248648
- module: "./dist/index.mjs"
248649
- };
248650
- // ../db/package.json
248651
- var package_default6 = {
248652
- name: "@supacloud/db",
248653
- version: "0.11.0",
248654
- description: "Database governance layer for SupaCloud: RLS policies, RPC functions and grants as first-class resources, with manifest/catalog reconciliation",
248655
- type: "module",
248656
- main: "./dist/index.js",
248657
- module: "./dist/index.js",
248658
- types: "./dist/index.d.ts",
248659
- bin: {
248660
- "supacloud-sql-impact": "./dist/sql-impact-cli.js"
248661
- },
248662
- exports: {
248663
- "./sql-analysis": {
248664
- types: "./dist/sql-analysis.d.ts",
248665
- import: "./dist/sql-analysis.js"
248666
- },
248667
- "./drizzle": {
248668
- types: "./dist/drizzle.d.ts",
248669
- import: "./dist/drizzle.js"
248670
- },
248671
- "./drizzle-bun": {
248672
- types: "./dist/drizzle-bun.d.ts",
248673
- import: "./dist/drizzle-bun.js"
248674
- },
248675
- "./bun": {
248676
- types: "./dist/command-bun.d.ts",
248677
- import: "./dist/command-bun.js"
248678
- },
248679
- ".": {
248680
- types: "./dist/index.d.ts",
248681
- import: "./dist/index.js",
248682
- default: "./dist/index.js"
248683
- }
248684
- },
248685
- files: [
248686
- "NOTICE",
248687
- "LICENSE-APACHE-2.0.txt",
248688
- "dist",
248689
- "README.md",
248690
- "MIGRATION_BINDINGS.md"
248691
- ],
248692
- scripts: {
248693
- build: "bun run clean && bun run build:js && bun run build:types",
248694
- "build:js": "bun build src/index.ts src/command-bun.ts src/drizzle.ts src/drizzle-bun.ts src/sql-analysis.ts src/sql-impact-cli.ts --outdir dist --target node --external bun --external @supacloud/contracts --external drizzle-orm --external libpg-query",
248695
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
248696
- clean: "rm -rf dist",
248697
- prepublishOnly: "bun run build",
248698
- test: "bun test",
248699
- typecheck: "tsc -p tsconfig.json --noEmit",
248700
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
248701
- "typecheck:drizzle": "tsc -p tsconfig.drizzle.json --noEmit"
248702
- },
248703
- keywords: [
248704
- "supacloud",
248705
- "database",
248706
- "governance",
248707
- "rls",
248708
- "postgresql"
248709
- ],
248710
- license: "Apache-2.0",
248711
- repository: {
248712
- type: "git",
248713
- url: "https://github.com/vibeunion/supacloud.git",
248714
- directory: "packages/db"
248715
- },
248716
- devDependencies: {
248717
- "@sinclair/typebox": "0.34.52",
248718
- "@typescript/typescript6": "^6.0.2",
248719
- "drizzle-orm": "1.0.0-rc.4",
248720
- "@types/bun": "^1.4.2",
248721
- typescript: "^7.0.2"
248722
- },
248723
- dependencies: {
248724
- "libpg-query": "18.1.4",
248725
- "@supacloud/contracts": "file:../contracts"
248726
- },
248727
- peerDependencies: {
248728
- "drizzle-orm": "1.0.0-rc.4"
248729
- },
248730
- peerDependenciesMeta: {
248731
- "drizzle-orm": {
248732
- optional: true
248733
- }
248734
- },
248735
- overrides: {
248736
- "@supacloud/contracts": "file:../contracts"
248737
- }
248738
- };
248739
- // ../supacloud-js/package.json
248740
- var package_default7 = {
248741
- name: "@supacloud/js",
248742
- version: "0.36.0",
248743
- description: "Platform SDK for SupaCloud, built on top of supabase-js",
248744
- type: "module",
248745
- main: "./dist/index.cjs",
248746
- module: "./dist/index.mjs",
248747
- types: "./dist/index.d.mts",
248748
- exports: {
248749
- ".": {
248750
- import: {
248751
- types: "./dist/index.d.mts",
248752
- default: "./dist/index.mjs"
248753
- },
248754
- require: {
248755
- types: "./dist/index.d.cts",
248756
- default: "./dist/index.cjs"
248757
- }
248758
- },
248759
- "./task-events": {
248760
- import: {
248761
- types: "./dist/task-events.d.mts",
248762
- default: "./dist/task-events.mjs"
248763
- },
248764
- require: {
248765
- types: "./dist/task-events.d.cts",
248766
- default: "./dist/task-events.cjs"
248767
- }
248768
- },
248769
- "./contracts": {
248770
- import: {
248771
- types: "./dist/contracts.d.mts",
248772
- default: "./dist/contracts.mjs"
248773
- },
248774
- require: {
248775
- types: "./dist/contracts.d.cts",
248776
- default: "./dist/contracts.cjs"
248777
- }
248778
- },
248779
- "./reactive": {
248780
- import: {
248781
- types: "./dist/reactive.d.mts",
248782
- default: "./dist/reactive.mjs"
248783
- },
248784
- require: {
248785
- types: "./dist/reactive.d.cts",
248786
- default: "./dist/reactive.cjs"
248787
- }
248788
- }
248789
- },
248790
- files: [
248791
- "NOTICE",
248792
- "LICENSE-APACHE-2.0.txt",
248793
- "dist",
248794
- "README.md",
248795
- "COMPATIBILITY.md",
248796
- "REACTIVE.md"
248797
- ],
248798
- scripts: {
248799
- test: "bun test",
248800
- build: "bun run clean && bun run build:js && bun run build:types",
248801
- "build:js": "bun run build:js:esm && bun run build:js:cjs",
248802
- "build:types": "tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/copy-dual-declarations.mjs dist",
248803
- clean: "rm -rf dist",
248804
- prepublishOnly: "bun run build && bun run check:package",
248805
- "typecheck:consumer": "tsc -p tsconfig.consumer.json --noEmit",
248806
- typecheck: "tsc -p tsconfig.json --noEmit",
248807
- "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
248808
- "check:package": "node ../../.github/scripts/esm-package-policy.mjs --pack .",
248809
- "build:js:esm": 'bun build src/index.ts src/task-events.ts --outdir dist --format esm --entry-naming "[name].mjs" --chunk-naming "[name]-[hash].mjs" --target node --external @supabase/supabase-js --external @supacloud/contracts && bun build src/contracts.ts --outdir dist --format esm --entry-naming "[name].mjs" --chunk-naming "[name]-[hash].mjs" --target browser --external @supacloud/contracts && bun build src/reactive.ts --outdir dist --format esm --entry-naming "[name].mjs" --chunk-naming "[name]-[hash].mjs" --target browser --external rxjs',
248810
- "build:js:cjs": 'bun build src/index.ts src/task-events.ts --outdir dist --format cjs --entry-naming "[name].cjs" --chunk-naming "[name]-[hash].cjs" --target node --external @supabase/supabase-js --external @supacloud/contracts && bun build src/contracts.ts --outdir dist --format cjs --entry-naming "[name].cjs" --chunk-naming "[name]-[hash].cjs" --target browser --external @supacloud/contracts && bun build src/reactive.ts --outdir dist --format cjs --entry-naming "[name].cjs" --chunk-naming "[name]-[hash].cjs" --target browser --external rxjs'
248811
- },
248812
- keywords: [
248813
- "supacloud",
248814
- "supabase",
248815
- "sdk",
248816
- "background-tasks",
248817
- "realtime"
248818
- ],
248819
- license: "Apache-2.0",
248820
- repository: {
248821
- type: "git",
248822
- url: "https://github.com/vibeunion/supacloud.git",
248823
- directory: "packages/supacloud-js"
248824
- },
248825
- peerDependencies: {
248826
- "@supabase/supabase-js": "^2.115.0"
248827
- },
248828
- dependencies: {
248829
- "@supacloud/contracts": "file:../contracts",
248830
- rxjs: "7.8.2"
248831
- },
248832
- devDependencies: {
248833
- "@types/bun": "^1.4.2",
248834
- "@supabase/supabase-js": "^2.115.0",
248835
- typescript: "^7.0.2"
248836
- },
248837
- engines: {
248838
- node: ">=22.12.0"
248839
- }
248422
+
248423
+ // src/shared/tools/app-starter-database.ts
248424
+ var STARTER_DATABASE_CONFIG = {
248425
+ version: 1,
248426
+ schema: ["db/schema.ts"],
248427
+ functions: "db/functions",
248428
+ migrations: "migrations",
248429
+ contracts: "db/contracts",
248430
+ audit: "output/database-audit",
248431
+ consumers: ["src", "scripts", "tests"],
248432
+ auditConsumers: [],
248433
+ role: "starter_review_http"
248840
248434
  };
248435
+ var STARTER_DATABASE_SCHEMA = `import { sql } from "drizzle-orm";
248436
+ import { boolean, check, integer, jsonb, pgTable, primaryKey, text, uuid } from "drizzle-orm/pg-core";
248437
+
248438
+ // Structure for the reviewed starter migrations. Grants/policies stay in those migrations.
248439
+ export const application = pgTable.withRLS("starter_application", {
248440
+ singleton: boolean().default(true).primaryKey(),
248441
+ projectId: text("project_id").notNull(),
248442
+ tenantId: text("tenant_id").notNull(),
248443
+ }, table => [
248444
+ check("starter_application_singleton_check", sql\`\${table.singleton}\`),
248445
+ check("starter_application_project_id_check", sql\`length(\${table.projectId}) > 0\`),
248446
+ check("starter_application_tenant_id_check", sql\`length(\${table.tenantId}) > 0\`),
248447
+ ]);
248448
+
248449
+ export const reviews = pgTable.withRLS("starter_reviews", {
248450
+ id: text().primaryKey(),
248451
+ ownerId: text("owner_id").notNull(),
248452
+ state: text({ enum: ["draft", "approved"] }).notNull(),
248453
+ version: integer().notNull(),
248454
+ }, table => [
248455
+ check("starter_reviews_state_check", sql\`\${table.state} IN ('draft', 'approved')\`),
248456
+ check("starter_reviews_version_check", sql\`\${table.version} > 0\`),
248457
+ ]);
248458
+
248459
+ export const members = pgTable.withRLS("starter_members", {
248460
+ subject: text().primaryKey(),
248461
+ enabled: boolean().default(true).notNull(),
248462
+ canApprove: boolean("can_approve").default(false).notNull(),
248463
+ storageSubject: uuid("storage_subject").unique(),
248464
+ });
248465
+
248466
+ export const attachments = pgTable.withRLS("starter_attachments", {
248467
+ reviewId: text("review_id").primaryKey().references(() => reviews.id),
248468
+ artifactId: uuid("artifact_id").notNull().unique(),
248469
+ ownerId: text("owner_id").notNull(),
248470
+ runId: uuid("run_id").notNull().unique(),
248471
+ objectPath: text("object_path").notNull().unique(),
248472
+ });
248473
+
248474
+ export const attachmentResults = pgTable.withRLS("starter_attachment_results", {
248475
+ reviewId: text("review_id").notNull().references(() => reviews.id),
248476
+ version: integer().notNull(),
248477
+ artifactId: uuid("artifact_id").notNull(),
248478
+ result: jsonb().$type<unknown>().notNull(),
248479
+ }, table => [
248480
+ primaryKey({ columns: [table.reviewId, table.version, table.artifactId] }),
248481
+ check("starter_attachment_results_version_check", sql\`\${table.version} >= 2\`),
248482
+ ]);
248483
+
248484
+ export type ReviewRow = typeof reviews.$inferSelect;
248485
+ export type NewReview = typeof reviews.$inferInsert;
248486
+ `;
248487
+ var STARTER_DATABASE_GUIDE = `
248488
+ ## Database Source Workflow
248489
+
248490
+ Drizzle owns table declarations and inferred row types in db/schema.ts.
248491
+ Use the existing @supacloud/db/drizzle-bun adapters for typed queries and governed
248492
+ transactions; never replace permission, idempotency or audit checks with plain CRUD.
248493
+ Opaque JSON still needs runtime decoding.
248494
+
248495
+ After installing dependencies, run \`bun run db:generate\` once and commit db/contracts/.
248496
+ The default \`bun run check\` and build fail on stale database contracts before any
248497
+ compiler regeneration. After an intentional source change, run db:generate again,
248498
+ review the diff, then run check. Ordinary checks are offline.
248499
+
248500
+ - Maintain one schema-qualified function with explicit grants per db/functions/*.sql.
248501
+ - Preserve migrations/ and its existing executor/ledger. Add forward migrations only.
248502
+ - \`bun run db:diff\` generates Drizzle drafts under db/migration-candidates/, never
248503
+ into the deployment directory. The starter has existing SQL history, so establish
248504
+ and review a matching Drizzle baseline before promoting any draft; the first
248505
+ draft is not automatically an incremental production migration.
248506
+ - \`bun run db:pull\` requires an explicitly selected DATABASE_URL and writes only
248507
+ output/database-audit/drizzle-candidate/. Review candidates; never overwrite source blindly.
248508
+ - Full SQL dumps belong under the ignored output/database-audit/ directory. They are
248509
+ audit/recovery artifacts, never ordinary type, application or test inputs.
248510
+ - \`bun run db:assess\` explains adoption/findings without writing or connecting.
248511
+ - GraphQL/PostgREST exports remain legitimate protocol contracts. This check is not
248512
+ proof of deployed schema, effective role permissions or complete RLS equivalence.
248513
+
248514
+ Do not introduce any/@ts-ignore to bypass a contract. Use Drizzle-inferred types,
248515
+ preserve bigint/numeric precision and decode unknown results at the boundary.
248516
+ `;
248517
+ function starterDatabaseFiles() {
248518
+ return {
248519
+ "database.sources.json": JSON.stringify(STARTER_DATABASE_CONFIG, null, 2) + `
248520
+ `,
248521
+ "db/schema.ts": STARTER_DATABASE_SCHEMA,
248522
+ "db/functions/README.md": `Maintain one schema-qualified function and its explicit EXECUTE ACL per SQL file. No RPC functions are needed by the initial transactional starter.
248523
+ `,
248524
+ "drizzle.config.ts": `import { defineConfig } from "drizzle-kit";
248525
+ export default defineConfig({
248526
+ dialect: "postgresql",
248527
+ schema: "./db/schema.ts",
248528
+ out: "./db/migration-candidates",
248529
+ });
248530
+ `,
248531
+ "drizzle.pull.config.ts": `import { defineConfig } from "drizzle-kit";
248532
+ const url = process.env.DATABASE_URL;
248533
+ if (!url) throw new Error("Select an explicit DATABASE_URL for introspection");
248534
+ export default defineConfig({
248535
+ dialect: "postgresql",
248536
+ schema: "./db/schema.ts",
248537
+ out: "./output/database-audit/drizzle-candidate",
248538
+ dbCredentials: { url },
248539
+ });
248540
+ `
248541
+ };
248542
+ }
248841
248543
 
248842
248544
  // src/shared/tools/app-starter.ts
248545
+ var { compiler: compilerMetadata2, app: appMetadata2, elysia: elysiaMetadata2, commands: commandsMetadata, contracts: contractsMetadata, db: dbMetadata, sdk: sdkMetadata } = starter_metadata_default.packages;
248843
248546
  var json2 = (value) => `${JSON.stringify(value, null, 2)}
248844
248547
  `;
248845
248548
  function appStarterFiles(name) {
248846
248549
  return {
248550
+ ...starterDatabaseFiles(),
248847
248551
  "package.json": json2({
248848
248552
  name,
248849
248553
  version: "0.0.0",
@@ -248854,28 +248558,36 @@ function appStarterFiles(name) {
248854
248558
  compile: "bun --no-env-file node_modules/@supacloud/compiler/dist/cli.js compile",
248855
248559
  "check:generated": "bun --no-env-file node_modules/@supacloud/compiler/dist/cli.js check",
248856
248560
  typecheck: "tsc --noEmit",
248857
- check: "bun run compile && bun run check:generated && bun run typecheck && bun run test",
248561
+ "db:generate": "supacloud-db generate",
248562
+ "db:check": "supacloud-db check",
248563
+ "db:assess": "supacloud-db assess",
248564
+ "db:diff": "drizzle-kit generate --config drizzle.config.ts",
248565
+ "db:pull": "drizzle-kit pull --config drizzle.pull.config.ts",
248566
+ check: "bun run db:check && bun run compile && bun run check:generated && bun run typecheck && bun run test",
248858
248567
  test: "bun run compile && bun --no-env-file scripts/environment.ts test bun test",
248859
248568
  dev: "bun --no-env-file scripts/environment.ts development bun scripts/dev.ts",
248860
- build: "bun run compile && bun run typecheck && bun build src/application.ts --target bun --minify --outdir dist",
248569
+ "dev:integration": "bun --no-env-file scripts/environment.ts development bun scripts/integration.ts",
248570
+ build: "bun run db:check && bun run compile && bun run typecheck && bun build src/application.ts --target bun --minify --outdir dist",
248861
248571
  "env:development": "bun --no-env-file scripts/environment.ts development",
248862
248572
  "env:test": "bun --no-env-file scripts/environment.ts test",
248863
248573
  "env:staging": "bun --no-env-file scripts/environment.ts staging",
248864
248574
  "env:production": "bun --no-env-file scripts/environment.ts production"
248865
248575
  },
248866
248576
  dependencies: {
248867
- "@supacloud/app": `^${package_default2.version}`,
248868
- "@supacloud/elysia": `^${package_default3.version}`,
248869
- "@supacloud/commands": `^${package_default4.version}`,
248870
- "@supacloud/contracts": `^${package_default5.version}`,
248871
- "@supacloud/db": `^${package_default6.version}`,
248872
- "@supacloud/js": `^${package_default7.version}`,
248873
- "@supabase/supabase-js": package_default7.peerDependencies["@supabase/supabase-js"],
248577
+ "@supacloud/app": `^${appMetadata2.version}`,
248578
+ "@supacloud/elysia": `^${elysiaMetadata2.version}`,
248579
+ "@supacloud/commands": `^${commandsMetadata.version}`,
248580
+ "@supacloud/contracts": `^${contractsMetadata.version}`,
248581
+ "@supacloud/db": `^${dbMetadata.version}`,
248582
+ "drizzle-orm": dbMetadata.peerDependencies["drizzle-orm"],
248583
+ "@supacloud/js": `^${sdkMetadata.version}`,
248584
+ "@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
248874
248585
  elysia: "2.0.0-beta.19",
248875
- rxjs: package_default2.dependencies.rxjs
248586
+ rxjs: appMetadata2.dependencies.rxjs
248876
248587
  },
248877
248588
  devDependencies: {
248878
- "@supacloud/compiler": `^${package_default.version}`,
248589
+ "@supacloud/compiler": `^${compilerMetadata2.version}`,
248590
+ "drizzle-kit": dbMetadata.devDependencies["drizzle-kit"],
248879
248591
  "@types/bun": "^1.4.2",
248880
248592
  typescript: "^7.0.2"
248881
248593
  }
@@ -248891,13 +248603,15 @@ function appStarterFiles(name) {
248891
248603
  noEmit: true,
248892
248604
  types: ["bun"]
248893
248605
  },
248894
- include: ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts", "generated/**/*.ts", "supacloud.config.ts"]
248606
+ include: ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts", "generated/**/*.ts", "db/schema.ts", "db/contracts/**/*.ts", "supacloud.config.ts", "drizzle*.config.ts"]
248895
248607
  }),
248896
248608
  "bunfig.toml": `env = false
248897
248609
  `,
248898
248610
  ".gitignore": [
248899
248611
  "node_modules/",
248900
248612
  "dist/",
248613
+ "output/database-audit/",
248614
+ "db/migration-candidates/",
248901
248615
  ".env",
248902
248616
  ".env.*",
248903
248617
  "!.env.*.example",
@@ -248986,7 +248700,7 @@ test("generated query client preserves its read contract without a live database
248986
248700
  });
248987
248701
  `,
248988
248702
  "REACTIVE.md": STARTER_REACTIVE_GUIDE,
248989
- "AGENTS.md": STARTER_REACTIVE_AGENTS,
248703
+ "AGENTS.md": STARTER_REACTIVE_AGENTS + STARTER_DATABASE_GUIDE,
248990
248704
  "tests/reactive.test.ts": STARTER_REACTIVE_TEST,
248991
248705
  "scripts/environment.ts": STARTER_ENVIRONMENT,
248992
248706
  "tests/environment.test.ts": STARTER_ENVIRONMENT_TEST,
@@ -249005,7 +248719,8 @@ const watcher = watchProject({
249005
248719
  restarts = restarts.then(async () => {
249006
248720
  if (server) { server.kill(); await server.exited; }
249007
248721
  if (closing) return;
249008
- server = Bun.spawn([process.execPath, "--no-env-file", "scripts/serve.ts"], {
248722
+ const entry = process.env.SUPACLOUD_DEV_PROFILE === "integration" ? "scripts/serve-integration.ts" : "scripts/serve.ts";
248723
+ server = Bun.spawn([process.execPath, "--no-env-file", entry], {
249009
248724
  stdin: "inherit", stdout: "inherit", stderr: "inherit", env: process.env,
249010
248725
  });
249011
248726
  }).catch((error) => { console.error(error); process.exitCode = 1; });
@@ -249021,6 +248736,48 @@ const close = async () => {
249021
248736
  process.once("SIGINT", close);
249022
248737
  process.once("SIGTERM", close);
249023
248738
  await watcher.ready;
248739
+ `,
248740
+ "scripts/integration.ts": `if (process.env.APP_ENV !== "development") throw new Error("Integration is development-only");
248741
+ process.env.SUPACLOUD_DEV_PROFILE = "integration";
248742
+ await import("./dev");
248743
+ export {};
248744
+ `,
248745
+ "scripts/serve-integration.ts": `import { createCompiledModules } from "../generated/application";
248746
+ import { createDeliveryApplication } from "../src/delivery-host";
248747
+
248748
+ if (process.env.APP_ENV !== "development") throw new Error("Integration is development-only");
248749
+ const databaseUrl = process.env.SUPACLOUD_DEV_DATABASE_URL;
248750
+ try {
248751
+ if (!databaseUrl) throw new Error();
248752
+ const url = new URL(databaseUrl);
248753
+ if (!["postgres:", "postgresql:"].includes(url.protocol)
248754
+ || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)
248755
+ || url.pathname.length <= 1 || url.search || url.hash) throw new Error();
248756
+ } catch {
248757
+ throw new Error("Integration requires an explicit loopback PostgreSQL database");
248758
+ }
248759
+ if (process.env.DATABASE_SOCKET_PATH
248760
+ || (process.env.DATABASE_URL && process.env.DATABASE_URL !== databaseUrl)) {
248761
+ throw new Error("Conflicting integration database settings");
248762
+ }
248763
+ process.env.DATABASE_URL = databaseUrl;
248764
+ const port = Number(process.env.PORT ?? "3000");
248765
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("Invalid PORT");
248766
+ const abort = new AbortController();
248767
+ const stopStartup = () => abort.abort();
248768
+ process.once("SIGINT", stopStartup);
248769
+ process.once("SIGTERM", stopStartup);
248770
+ const application = await createDeliveryApplication(createCompiledModules(), { signal: abort.signal });
248771
+ if (abort.signal.aborted) { await application.close(); throw new Error("Integration startup cancelled"); }
248772
+ const server = Bun.serve({ hostname: "127.0.0.1", port, fetch: application.fetch });
248773
+ const close = async () => {
248774
+ abort.abort();
248775
+ server.stop(true);
248776
+ await application.close();
248777
+ };
248778
+ process.once("SIGINT", close);
248779
+ process.once("SIGTERM", close);
248780
+ console.log("Local integration: " + server.url);
249024
248781
  `,
249025
248782
  "scripts/serve.ts": `import { createDemo } from "./sandbox";
249026
248783
 
@@ -249088,6 +248845,7 @@ import { ApplicationError, assertFeatureTransition } from "@supacloud/elysia";
249088
248845
  import { t } from "elysia";
249089
248846
  import { VerifyReviewAttachment } from "./attachment";
249090
248847
  import { ReviewUploads, ReviewUploadsController, type ReviewUploadPort } from "./uploads";
248848
+ import type { ReviewRow } from "../../db/schema";
249091
248849
 
249092
248850
  export const reviewSpec = defineFeatureSpec({
249093
248851
  name: "review",
@@ -249102,7 +248860,7 @@ export const reviewSpec = defineFeatureSpec({
249102
248860
  },
249103
248861
  });
249104
248862
 
249105
- interface Review { state: string; version: number }
248863
+ type Review = Pick<ReviewRow, "state" | "version">;
249106
248864
  // Production implementations must bind both operations to the current transaction.
249107
248865
  export interface ReviewStore extends Partial<ReviewUploadPort> {
249108
248866
  get(table: string, key: string): unknown;
@@ -249125,7 +248883,7 @@ export const requireRequest: Aspect = (context, next) => {
249125
248883
 
249126
248884
  function readReview(value: unknown): Review {
249127
248885
  if (typeof value !== "object" || value === null ||
249128
- !("state" in value) || typeof value.state !== "string" ||
248886
+ !("state" in value) || (value.state !== "draft" && value.state !== "approved") ||
249129
248887
  !("version" in value) || typeof value.version !== "number") {
249130
248888
  throw new ApplicationError("Review not found", { status: 404, code: "REVIEW_NOT_FOUND" });
249131
248889
  }
@@ -249252,6 +249010,7 @@ test("the production composition root rejects missing governance adapters", () =
249252
249010
 
249253
249011
  \`\`\`sh
249254
249012
  bun install
249013
+ bun run db:generate
249255
249014
  bun run check
249256
249015
  bun run dev
249257
249016
  \`\`\`
@@ -249273,6 +249032,28 @@ compiler-checked command/route/governance bindings, an explicit AOP function,
249273
249032
  HTTP schemas, permission denial, transaction rollback, idempotency and audit.
249274
249033
  It is not a complete Maker-Checker workflow or a production authorization policy.
249275
249034
 
249035
+ ## Frontend SDK Boundary
249036
+
249037
+ Use @supacloud/js as the platform SDK around your existing user-scoped
249038
+ @supabase/supabase-js client. Pass that same client to existing UI providers;
249039
+ supacloud.supabase exposes it unchanged. Keep UI routing, forms and query
249040
+ caches in your frontend framework or svadmin, not in a new DI container.
249041
+
249042
+ For generated/client.ts, supply createAuthenticatedFetch from
249043
+ @supacloud/js/contracts as its fetch option. Resolve the current user token
249044
+ from the same Supabase session for each request; do not capture a token at
249045
+ startup or introduce a second session store. Use a fixed trusted HTTPS API
249046
+ origin. The authenticated transport rejects missing credentials and redirects
249047
+ and does not replay a write after 401. Its underlying fetch must also be
249048
+ single-attempt.
249049
+
249050
+ Reuse the same session resolver for generated/graphql.ts when adopting queries.
249051
+ The local memory demo is not this authenticated production integration.
249052
+ Never use supacloud.commands or supacloud.workflows from a browser action:
249053
+ those RPCs require service-role credentials. Browser business commands use
249054
+ application endpoints, optionally composed through @supacloud/js/contracts.
249055
+ Management API tokens and service-role keys remain on trusted servers.
249056
+
249276
249057
  ## Typed Queries
249277
249058
 
249278
249059
  This starter uses Database First as its only GraphQL server-schema model.
@@ -249541,6 +249322,8 @@ policy. Invalid transaction/idempotency modes fail compilation instead of
249541
249322
  silently disabling governance. onExecution receives metadata-only trace events;
249542
249323
  durable audit still belongs to the command governance adapter.
249543
249324
 
249325
+ ${STARTER_DATABASE_GUIDE}
249326
+
249544
249327
  ## CI
249545
249328
 
249546
249329
  After the first compilation, commit generated/. In CI run
@@ -249564,8 +249347,11 @@ async function initializeAppProject(options) {
249564
249347
  if ((await readdir4(root)).some((entry) => entry !== ".git")) {
249565
249348
  throw new Error("app init requires an empty directory (an existing .git directory is allowed)");
249566
249349
  }
249567
- const template = options.template ?? "command";
249568
- const files = template === "command" ? appStarterFiles(name) : appTemplateFiles(name, template);
249350
+ const template = options.template ?? "minimal";
249351
+ const files = template === "command" ? appStarterFiles(name) : appTemplateFiles(name, template, {
249352
+ "@supacloud/js": `^${sdkMetadata.version}`,
249353
+ "@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"]
249354
+ });
249569
249355
  for (const [relativePath, content] of Object.entries(files)) {
249570
249356
  const path = join13(root, relativePath);
249571
249357
  await mkdir4(dirname10(path), { recursive: true });
@@ -249574,26 +249360,83 @@ async function initializeAppProject(options) {
249574
249360
  return { root, name, files: Object.keys(files) };
249575
249361
  }
249576
249362
 
249363
+ // src/shared/tools/app-database-check.ts
249364
+ import { spawnSync } from "node:child_process";
249365
+ import { existsSync as existsSync11 } from "node:fs";
249366
+ import { join as join14, resolve as resolve16 } from "node:path";
249367
+ var CHECK_ENTRY = `
249368
+ import { createRequire } from "node:module";
249369
+ import { join } from "node:path";
249370
+ import { pathToFileURL } from "node:url";
249371
+ const root = process.argv[1];
249372
+ const cli = createRequire(join(root, "package.json")).resolve("@supacloud/db/source-contracts-cli");
249373
+ process.argv = [process.execPath, cli, "check", "--root", root];
249374
+ await import(pathToFileURL(cli).href);
249375
+ `;
249376
+ function checkAppDatabaseSources(projectRoot) {
249377
+ const root = resolve16(projectRoot);
249378
+ if (!existsSync11(join14(root, "database.sources.json")))
249379
+ return;
249380
+ const name = "database-source-contracts";
249381
+ try {
249382
+ const result = spawnSync(process.versions.bun ? "bun" : process.execPath, [
249383
+ ...process.versions.bun ? ["--no-env-file"] : ["--input-type=module"],
249384
+ "--eval",
249385
+ CHECK_ENTRY,
249386
+ "--",
249387
+ root
249388
+ ], { cwd: root, encoding: "utf8", timeout: 30000, maxBuffer: 4 * 1024 * 1024 });
249389
+ if (result.error)
249390
+ throw result.error;
249391
+ const report = JSON.parse(result.stdout);
249392
+ if (!report || typeof report !== "object" || !("scope" in report) || report.scope !== "local-source-contracts" || !("ok" in report) || typeof report.ok !== "boolean" || !("findings" in report) || !Array.isArray(report.findings)) {
249393
+ throw new Error("Invalid database source check response");
249394
+ }
249395
+ const messages = [];
249396
+ for (const finding of report.findings) {
249397
+ if (!finding || typeof finding !== "object" || !("file" in finding) || typeof finding.file !== "string" || !("message" in finding) || typeof finding.message !== "string")
249398
+ throw new Error("Invalid source finding");
249399
+ messages.push(`${finding.file}: ${finding.message}`);
249400
+ }
249401
+ return {
249402
+ name,
249403
+ ok: result.status === 0 && report.ok && !messages.length,
249404
+ detail: messages.join(`
249405
+ `) || (result.status === 0 && report.ok ? "Offline database contracts and boundaries are current" : "Database source check failed")
249406
+ };
249407
+ } catch {
249408
+ return { name, ok: false, detail: "Cannot run the project database checker. Install the project runtime and @supacloud/db with source-contracts support, then run `supacloud-db check --root .` for diagnostics." };
249409
+ }
249410
+ }
249411
+
249577
249412
  // src/shared/tools/app-resource.ts
249578
249413
  function resourceScaffold(name, className) {
249579
249414
  return {
249580
249415
  [`${name}.model.ts`]: `import { t } from "elysia";
249581
- import type { RouteHandlerOutput } from "@supacloud/app";
249416
+ import { InjectionToken, type RouteHandlerOutput } from "@supacloud/app";
249582
249417
 
249583
249418
  // HTTP contracts are the single source of field types. Services import types only.
249584
249419
  export const ${className}Params = t.Object({ id: t.String({ minLength: 1, maxLength: 128, pattern: "^[A-Za-z0-9_-]+$" }) });
249585
249420
  export const ${className}Response = t.Object({ id: t.String() });
249586
249421
  export type ${className}Result = RouteHandlerOutput<{ responses: { 200: typeof ${className}Response } }>;
249422
+
249423
+ // Bind a request-scoped adapter using the verified user and RLS. It must reject
249424
+ // denied/not-found reads; never bind a browser-supplied identity or service-role fallback.
249425
+ export interface ${className}ReadPort {
249426
+ readAuthorized(id: string): Promise<${className}Result>;
249427
+ }
249428
+ export const ${className}Reader = new InjectionToken<${className}ReadPort>("${name}.reader", { scope: "request" });
249587
249429
  `,
249588
- [`${name}.service.ts`]: `import { Injectable } from "@supacloud/app";
249589
- import type { ${className}Result } from "./${name}.model";
249430
+ [`${name}.service.ts`]: `import { Inject, Injectable, Optional } from "@supacloud/app";
249431
+ import { ${className}Reader, type ${className}ReadPort, type ${className}Result } from "./${name}.model";
249590
249432
 
249591
- @Injectable()
249433
+ @Injectable({ scope: "request" })
249592
249434
  export class ${className}Service {
249593
- async find(_id: string): Promise<${className}Result> {
249594
- // Supply your project's authorized read port before exposing this route.
249595
- // Writes must use a governed Command; this scaffold never fakes persistence.
249596
- throw new Error("Implement ${className}Service.find before exposing this resource");
249435
+ constructor(@Inject(${className}Reader) @Optional() readonly reader?: ${className}ReadPort) {}
249436
+
249437
+ async find(id: string): Promise<${className}Result> {
249438
+ if (!this.reader) throw new Error("Bind ${className}Reader before exposing this resource");
249439
+ return this.reader.readAuthorized(id);
249597
249440
  }
249598
249441
  }
249599
249442
  `,
@@ -249603,7 +249446,7 @@ import { ${className}Service } from "./${name}.service";
249603
249446
 
249604
249447
  @Controller("/${name}")
249605
249448
  export class ${className}Controller {
249606
- constructor(@Inject(${className}Service) readonly service: ${className}Service) {}
249449
+ constructor(@Inject(${className}Service) readonly service: Pick<${className}Service, "find">) {}
249607
249450
 
249608
249451
  @Get("/:id", { params: ${className}Params, responses: { 200: ${className}Response } })
249609
249452
  find(@Param("id") id: string): Promise<${className}Result> {
@@ -249628,7 +249471,18 @@ import { ${className}Service } from "./${name}.service";
249628
249471
 
249629
249472
  test("${name} does not claim a read adapter has been implemented", async () => {
249630
249473
  await expect(new ${className}Service().find("example"))
249631
- .rejects.toThrow("Implement ${className}Service.find");
249474
+ .rejects.toThrow("Bind ${className}Reader");
249475
+ });
249476
+
249477
+ test("${name} returns only the supplied authorized read result", async () => {
249478
+ const service = new ${className}Service({ readAuthorized: async id => ({ id }) });
249479
+ expect(await service.find("example")).toEqual({ id: "example" });
249480
+ });
249481
+
249482
+ test("${name} preserves permission denial without a privileged fallback", async () => {
249483
+ const denied = new Error("denied");
249484
+ const service = new ${className}Service({ readAuthorized: async () => { throw denied; } });
249485
+ await expect(service.find("example")).rejects.toBe(denied);
249632
249486
  });
249633
249487
  `,
249634
249488
  [`${name}.controller.test.ts`]: `import { expect, test } from "bun:test";
@@ -249649,10 +249503,220 @@ test("${name} controller preserves an asynchronous read failure", async () => {
249649
249503
  };
249650
249504
  }
249651
249505
 
249506
+ // src/shared/tools/app-local-dev.ts
249507
+ import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
249508
+ import { readFile as readFile4 } from "node:fs/promises";
249509
+ import { join as join15, resolve as resolve17 } from "node:path";
249510
+ function localDatabaseUrl(value) {
249511
+ try {
249512
+ if (!value || /[\x00-\x20\x7f]/.test(value))
249513
+ throw new Error;
249514
+ const url = new URL(value);
249515
+ if (!["postgres:", "postgresql:"].includes(url.protocol) || !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) || url.pathname.length <= 1 || url.search || url.hash)
249516
+ throw new Error;
249517
+ return value;
249518
+ } catch {
249519
+ throw new Error("Integration requires an explicit loopback PostgreSQL URL with a database name and no query or fragment");
249520
+ }
249521
+ }
249522
+ async function probeDatabase(url, signal) {
249523
+ const source = `const database = new Bun.SQL(process.env.SUPACLOUD_DEV_DATABASE_URL, {
249524
+ max: 1, connectionTimeout: 5,
249525
+ });
249526
+ try {
249527
+ const rows = await database\`SELECT current_database() AS name,
249528
+ r.rolsuper AS superuser, r.rolbypassrls AS bypass_rls
249529
+ FROM pg_roles r WHERE r.rolname = current_user\`;
249530
+ console.log(JSON.stringify(rows));
249531
+ } finally {
249532
+ await database.close({ timeout: 1 });
249533
+ }`;
249534
+ try {
249535
+ const stdout = await new Promise((resolveOutput, reject) => {
249536
+ execFile2(process.versions.bun ? process.execPath : "bun", ["--no-env-file", "-e", source], {
249537
+ env: { ...process.env, SUPACLOUD_DEV_DATABASE_URL: url },
249538
+ timeout: 12000,
249539
+ killSignal: "SIGKILL",
249540
+ maxBuffer: 4096,
249541
+ encoding: "utf8",
249542
+ signal
249543
+ }, (error, output) => error ? reject(error) : resolveOutput(output));
249544
+ });
249545
+ const rows = JSON.parse(stdout);
249546
+ const expected = decodeURIComponent(new URL(url).pathname.slice(1));
249547
+ if (!Array.isArray(rows) || rows.length !== 1)
249548
+ throw new Error;
249549
+ const row = rows[0];
249550
+ if (!row || typeof row !== "object" || !("name" in row) || row.name !== expected || !("superuser" in row) || row.superuser !== false || !("bypass_rls" in row) || row.bypass_rls !== false)
249551
+ throw new Error;
249552
+ } catch {
249553
+ throw new Error("Integration database verification failed; check connectivity, database identity and non-privileged application role");
249554
+ }
249555
+ }
249556
+ async function runLocalDevelopment(options) {
249557
+ const root = resolve17(options.root);
249558
+ const profile = options.profile ?? "fast";
249559
+ if (profile !== "fast" && profile !== "integration")
249560
+ throw new Error("Unknown development profile");
249561
+ if (["production", "staging"].includes(process.env.APP_ENV ?? "") || process.env.SUPACLOUD_ENV === "production") {
249562
+ throw new Error("Local development refuses production or staging environment selectors");
249563
+ }
249564
+ const remoteKeys = ["SUPACLOUD_API_URL", "SUPACLOUD_API_TOKEN", "SUPACLOUD_PROJECT_REF"];
249565
+ if (remoteKeys.some((key) => process.env[key] !== undefined) && (process.env.SUPACLOUD_ENV !== "test" || remoteKeys.some((key) => !process.env[key]?.trim()))) {
249566
+ throw new Error("Inherited remote context must be complete and explicitly tagged with SUPACLOUD_ENV=test");
249567
+ }
249568
+ const script = profile === "integration" ? "dev:integration" : "dev";
249569
+ const manifest = JSON.parse(await readFile4(join15(root, "package.json"), "utf8"));
249570
+ const scripts = manifest && typeof manifest === "object" && "scripts" in manifest ? manifest.scripts : undefined;
249571
+ const command = scripts && typeof scripts === "object" ? Reflect.get(scripts, script) : undefined;
249572
+ if (typeof command !== "string" || !command.trim()) {
249573
+ throw new Error(`Project must declare ${script}; use app watch for compile-only inspection`);
249574
+ }
249575
+ let databaseUrl;
249576
+ if (profile === "integration") {
249577
+ databaseUrl = localDatabaseUrl(options.databaseUrl ?? process.env.SUPACLOUD_DEV_DATABASE_URL);
249578
+ } else if (options.databaseUrl !== undefined) {
249579
+ throw new Error("A database URL requires the integration profile");
249580
+ }
249581
+ if (options.signal?.aborted)
249582
+ return { profile, script, exitCode: 0, state: "stopped" };
249583
+ if (databaseUrl)
249584
+ await probeDatabase(databaseUrl, options.signal);
249585
+ if (options.signal?.aborted)
249586
+ return { profile, script, exitCode: 0, state: "stopped" };
249587
+ const env = {
249588
+ ...process.env,
249589
+ APP_ENV: "development",
249590
+ SUPACLOUD_ENV: "test",
249591
+ SUPACLOUD_DEV_PROFILE: profile
249592
+ };
249593
+ delete env.SUPACLOUD_DEV_DATABASE_URL;
249594
+ if (databaseUrl)
249595
+ env.SUPACLOUD_DEV_DATABASE_URL = databaseUrl;
249596
+ const executable = process.versions.bun ? process.execPath : "bun";
249597
+ const child = spawn3(executable, ["--no-env-file", "run", script], {
249598
+ cwd: root,
249599
+ env,
249600
+ detached: process.platform !== "win32",
249601
+ stdio: ["ignore", "pipe", "pipe"]
249602
+ });
249603
+ let timer;
249604
+ let outputError;
249605
+ let stopping = false;
249606
+ const kill = (signal) => {
249607
+ try {
249608
+ if (process.platform !== "win32" && child.pid)
249609
+ process.kill(-child.pid, signal);
249610
+ else
249611
+ child.kill(signal);
249612
+ } catch (error) {
249613
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH")
249614
+ throw error;
249615
+ }
249616
+ };
249617
+ const stop = () => {
249618
+ if (stopping)
249619
+ return;
249620
+ stopping = true;
249621
+ kill("SIGTERM");
249622
+ timer = setTimeout(() => kill("SIGKILL"), 1500);
249623
+ };
249624
+ const output = (chunk) => {
249625
+ try {
249626
+ options.onOutput?.(chunk.toString("utf8"));
249627
+ } catch (error) {
249628
+ outputError = { error };
249629
+ stop();
249630
+ }
249631
+ };
249632
+ child.stdout.on("data", output);
249633
+ child.stderr.on("data", output);
249634
+ options.signal?.addEventListener("abort", stop, { once: true });
249635
+ process.once("SIGINT", stop);
249636
+ process.once("SIGTERM", stop);
249637
+ if (options.signal?.aborted)
249638
+ stop();
249639
+ try {
249640
+ const exitCode = await new Promise((resolveExit, reject) => {
249641
+ child.once("error", reject);
249642
+ child.once("close", (code) => resolveExit(stopping ? 0 : code ?? 1));
249643
+ });
249644
+ if (outputError)
249645
+ throw outputError.error;
249646
+ return { profile, script, exitCode, state: "stopped" };
249647
+ } finally {
249648
+ if (timer)
249649
+ clearTimeout(timer);
249650
+ options.signal?.removeEventListener("abort", stop);
249651
+ process.removeListener("SIGINT", stop);
249652
+ process.removeListener("SIGTERM", stop);
249653
+ }
249654
+ }
249655
+
249656
+ // src/shared/tools/app-verification-plan.ts
249657
+ import { readdir as readdir5, realpath as realpath5 } from "node:fs/promises";
249658
+ import { dirname as dirname11, isAbsolute as isAbsolute6, relative as relative11, resolve as resolve18 } from "node:path";
249659
+ function inside3(root, file) {
249660
+ const path = relative11(root, file).replaceAll("\\", "/");
249661
+ return path !== ".." && !path.startsWith("../") && !isAbsolute6(path);
249662
+ }
249663
+ async function createVerificationPlan(root, graph, target, sourceRoot = root) {
249664
+ const owners = graph.modules.filter((module) => module.name === target || module.className === target || [...module.providers, ...module.controllers, ...module.commands, ...module.jobs ?? [], ...module.queries ?? []].some((item) => ("className" in item) && item.className === target || ("name" in item) && item.name === target || ("token" in item) && item.token === target || ("useClass" in item) && item.useClass === target));
249665
+ if (owners.length !== 1)
249666
+ throw new Error("Select one unambiguous module or owned symbol for verification");
249667
+ const owner = owners[0];
249668
+ const project = await realpath5(root);
249669
+ const requestedRoot = resolve18(root);
249670
+ const sources = [
249671
+ owner.file,
249672
+ ...owner.providers.map((item) => item.file),
249673
+ ...owner.controllers.map((item) => item.file)
249674
+ ].filter((file) => typeof file === "string");
249675
+ const directories = [...new Set(sources.map((file) => dirname11(resolve18(sourceRoot, file))))].sort();
249676
+ const tests = new Set;
249677
+ const manual = [];
249678
+ for (const directory of directories) {
249679
+ if (!inside3(requestedRoot, directory)) {
249680
+ manual.push("A source directory is outside the project; select its owning project explicitly");
249681
+ continue;
249682
+ }
249683
+ const actual = await realpath5(directory);
249684
+ if (!inside3(project, actual)) {
249685
+ manual.push("A source directory is outside the project; select its owning project explicitly");
249686
+ continue;
249687
+ }
249688
+ for (const entry of await readdir5(actual, { withFileTypes: true })) {
249689
+ if (entry.isFile() && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
249690
+ tests.add(relative11(project, resolve18(actual, entry.name)).replaceAll("\\", "/"));
249691
+ }
249692
+ }
249693
+ }
249694
+ const selected = [...tests].sort();
249695
+ if (!selected.length)
249696
+ manual.push("No colocated tests found; declare a focused test before changing this module");
249697
+ if (selected.length > 32)
249698
+ manual.push("More than 32 local tests; narrow module ownership before execution");
249699
+ return {
249700
+ version: 1,
249701
+ source: "declaration",
249702
+ target: owner.name,
249703
+ ready: manual.length === 0,
249704
+ files: [...new Set(sources.map((file) => relative11(requestedRoot, resolve18(sourceRoot, file)).replaceAll("\\", "/")))].sort(),
249705
+ tests: selected.slice(0, 32),
249706
+ commands: selected.length <= 32 ? [
249707
+ ...selected.map((file) => ["bun", "test", `./${file}`]),
249708
+ ["git", "diff", "--check"]
249709
+ ] : [],
249710
+ manual,
249711
+ releaseOnly: ["generated artifact drift", "consumer types", "database/RLS and authenticated integration", "delivery read-back"],
249712
+ executed: false
249713
+ };
249714
+ }
249715
+
249652
249716
  // src/shared/tools/app-scaffold-writes.ts
249653
249717
  import { randomUUID as randomUUID2 } from "node:crypto";
249654
- import { link, lstat as lstat4, mkdir as mkdir5, open as open4, readFile as readFile4, realpath as realpath5, rename as rename5, rmdir, unlink as unlink3 } from "node:fs/promises";
249655
- import { dirname as dirname11, isAbsolute as isAbsolute6, join as join14, relative as relative11, resolve as resolve16, sep as sep11, win32 } from "node:path";
249718
+ import { link, lstat as lstat4, mkdir as mkdir5, open as open4, readFile as readFile8, realpath as realpath6, rename as rename5, rmdir, unlink as unlink3 } from "node:fs/promises";
249719
+ import { dirname as dirname12, isAbsolute as isAbsolute7, join as join16, relative as relative12, resolve as resolve20, sep as sep11, win32 } from "node:path";
249656
249720
 
249657
249721
  class ScaffoldError extends Error {
249658
249722
  code;
@@ -249666,26 +249730,26 @@ function fsCode(error) {
249666
249730
  return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : undefined;
249667
249731
  }
249668
249732
  function scaffoldPath(root, path) {
249669
- if (!path || path.includes("\x00") || path.includes("\\") || isAbsolute6(path) || win32.isAbsolute(path)) {
249733
+ if (!path || path.includes("\x00") || path.includes("\\") || isAbsolute7(path) || win32.isAbsolute(path)) {
249670
249734
  throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold paths must be relative to the project root");
249671
249735
  }
249672
249736
  const parts = path.split("/");
249673
249737
  if (parts.some((part) => /[<>:"|?*\u0000-\u001f]/.test(part) || /[ .]$/.test(part) && part !== "." && part !== ".." || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(part) || part.toLowerCase() === ".git" || part.toLowerCase() === ".supacloud-generate.lock")) {
249674
249738
  throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is reserved or not portable");
249675
249739
  }
249676
- const target = resolve16(root, path);
249677
- const local = relative11(root, target);
249678
- if (!local || local === ".." || local.startsWith(`..${sep11}`) || isAbsolute6(local)) {
249740
+ const target = resolve20(root, path);
249741
+ const local = relative12(root, target);
249742
+ if (!local || local === ".." || local.startsWith(`..${sep11}`) || isAbsolute7(local)) {
249679
249743
  throw new ScaffoldError("SCAFFOLD_PATH_INVALID", "Scaffold path is outside the project root");
249680
249744
  }
249681
249745
  return target;
249682
249746
  }
249683
249747
  async function snapshot(root, path) {
249684
249748
  const target = scaffoldPath(root, path);
249685
- const parts = relative11(root, target).split(sep11);
249749
+ const parts = relative12(root, target).split(sep11);
249686
249750
  let current = root;
249687
249751
  for (const [index, part] of parts.entries()) {
249688
- current = join14(current, part);
249752
+ current = join16(current, part);
249689
249753
  let stat;
249690
249754
  try {
249691
249755
  stat = await lstat4(current);
@@ -249703,7 +249767,7 @@ async function snapshot(root, path) {
249703
249767
  if (!stat.isFile() || stat.nlink !== 1) {
249704
249768
  throw new ScaffoldError("SCAFFOLD_PATH_INVALID", `Scaffold target must be a regular, unlinked file: ${current}`);
249705
249769
  }
249706
- return { content: await readFile4(current, "utf8"), mode: stat.mode, dev: stat.dev, ino: stat.ino };
249770
+ return { content: await readFile8(current, "utf8"), mode: stat.mode, dev: stat.dev, ino: stat.ino };
249707
249771
  }
249708
249772
  }
249709
249773
  return null;
@@ -249712,7 +249776,7 @@ function sameFile(a, b) {
249712
249776
  return a === null ? b === null : b !== null && a.content === b.content && a.dev === b.dev && a.ino === b.ino && a.mode === b.mode;
249713
249777
  }
249714
249778
  async function planScaffoldWrites(root, writes) {
249715
- const canonical = await realpath5(resolve16(root));
249779
+ const canonical = await realpath6(resolve20(root));
249716
249780
  const targets = new Set;
249717
249781
  const planned = [];
249718
249782
  for (const write of writes) {
@@ -249735,7 +249799,7 @@ async function planScaffoldWrites(root, writes) {
249735
249799
  return Object.freeze({ root: canonical, writes: Object.freeze(planned) });
249736
249800
  }
249737
249801
  async function applyScaffoldWrites(plan) {
249738
- const lockPath = join14(plan.root, ".supacloud-generate.lock");
249802
+ const lockPath = join16(plan.root, ".supacloud-generate.lock");
249739
249803
  const lock = await open4(lockPath, "wx").catch((error) => {
249740
249804
  if (fsCode(error) === "EEXIST")
249741
249805
  throw new ScaffoldError("SCAFFOLD_BUSY", "Another generator owns .supacloud-generate.lock; no files written");
@@ -249745,7 +249809,7 @@ async function applyScaffoldWrites(plan) {
249745
249809
  const applied = [];
249746
249810
  const temporary = [];
249747
249811
  try {
249748
- if (await realpath5(plan.root) !== plan.root)
249812
+ if (await realpath6(plan.root) !== plan.root)
249749
249813
  throw new ScaffoldError("SCAFFOLD_CHANGED", "Project root changed");
249750
249814
  for (const write of plan.writes) {
249751
249815
  if (!sameFile(write.before, await snapshot(plan.root, write.path))) {
@@ -249755,8 +249819,8 @@ async function applyScaffoldWrites(plan) {
249755
249819
  for (const write of plan.writes) {
249756
249820
  const target = scaffoldPath(plan.root, write.path);
249757
249821
  let current = plan.root;
249758
- for (const part of relative11(plan.root, dirname11(target)).split(sep11).filter(Boolean)) {
249759
- current = join14(current, part);
249822
+ for (const part of relative12(plan.root, dirname12(target)).split(sep11).filter(Boolean)) {
249823
+ current = join16(current, part);
249760
249824
  try {
249761
249825
  await mkdir5(current);
249762
249826
  directories.push(current);
@@ -249859,12 +249923,13 @@ var REMOTE_APP_SCHEMA = Object.fromEntries(Object.entries(APPLICATION_TOOL_SCHEM
249859
249923
  async function initProject(args) {
249860
249924
  if (args.force)
249861
249925
  throw new Error("app init never overwrites files; choose an empty directory");
249862
- const template = args.template ?? "command";
249926
+ const template = args.template ?? "minimal";
249863
249927
  const { root, name, files } = await initializeAppProject({ root: args.root, name: args.name, template });
249864
249928
  return textResult([
249865
249929
  `Initialized ${name} in ${root} (${files.length} files, template: ${template}).`,
249866
249930
  `cd '${root.replaceAll("'", "'\\''")}'`,
249867
249931
  "bun install",
249932
+ ...template === "command" ? ["bun run db:generate"] : [],
249868
249933
  "bun run check",
249869
249934
  "bun run dev",
249870
249935
  "Local demo only. Production requires persistent governance and identity adapters; see README.md."
@@ -249971,7 +250036,7 @@ async function generateScaffold(args) {
249971
250036
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", `--${flag.replaceAll("_", "-")} must be boolean`);
249972
250037
  }
249973
250038
  }
249974
- const root = resolve17(args.root || process.cwd());
250039
+ const root = resolve21(args.root || process.cwd());
249975
250040
  const dir = args.dir ?? "src/features";
249976
250041
  if (dir !== ".")
249977
250042
  scaffoldPath(root, dir);
@@ -249987,20 +250052,20 @@ async function generateScaffold(args) {
249987
250052
  let contractName;
249988
250053
  if (kind === "module" || kind === "resource") {
249989
250054
  const name = requireIdentifier(args.name, "name");
249990
- modulePath = join15(dir, name, `${name}.module.ts`).replaceAll("\\", "/");
250055
+ modulePath = join17(dir, name, `${name}.module.ts`).replaceAll("\\", "/");
249991
250056
  moduleSymbol = `${pascalName2(name)}Module`;
249992
250057
  if (kind === "module")
249993
250058
  writes.push({ path: modulePath, content: moduleScaffold(name), overwrite: args.force === true });
249994
250059
  else
249995
250060
  for (const [file, content] of Object.entries(resourceScaffold(name, pascalName2(name)))) {
249996
- writes.push({ path: join15(dir, name, file).replaceAll("\\", "/"), content });
250061
+ writes.push({ path: join17(dir, name, file).replaceAll("\\", "/"), content });
249997
250062
  }
249998
250063
  } else {
249999
250064
  const moduleName = requireIdentifier(args.module, "module");
250000
250065
  const name = kind === "controller" ? moduleName : requireIdentifier(args.name, "name");
250001
250066
  const subdir = kind === "command" ? "commands" : kind === "query" ? "queries" : kind === "job" ? "jobs" : kind === "contract" ? "contracts" : "";
250002
- const path = join15(dir, moduleName, subdir, `${name}.${kind}.ts`).replaceAll("\\", "/");
250003
- if (kind === "controller" && existsSync11(scaffoldPath(root, path))) {
250067
+ const path = join17(dir, moduleName, subdir, `${name}.${kind}.ts`).replaceAll("\\", "/");
250068
+ if (kind === "controller" && existsSync12(scaffoldPath(root, path))) {
250004
250069
  throw new ScaffoldError("SCAFFOLD_EXISTS", `Controller already exists: ${path}(请手工合并路由到现有 controller)`);
250005
250070
  }
250006
250071
  const content = kind === "command" ? commandScaffold(moduleName, name) : kind === "query" ? queryScaffold(moduleName, name) : kind === "job" ? jobScaffold(moduleName, name) : kind === "contract" ? contractScaffold(moduleName, name) : controllerScaffold(moduleName);
@@ -250018,7 +250083,7 @@ async function generateScaffold(args) {
250018
250083
  if (new RegExp(`\\b${moduleSymbol}\\b`).test(original)) {
250019
250084
  throw new ScaffoldError("SCAFFOLD_REGISTRATION_CONFLICT", `Registration target already references ${moduleSymbol}; review the import manually`);
250020
250085
  }
250021
- let importPath = relative12(dirname12(target), resolve17(root, modulePath)).replaceAll("\\", "/").replace(/\.ts$/, "");
250086
+ let importPath = relative13(dirname13(target), resolve21(root, modulePath)).replaceAll("\\", "/").replace(/\.ts$/, "");
250022
250087
  if (!importPath.startsWith("."))
250023
250088
  importPath = `./${importPath}`;
250024
250089
  const edit = await applyDiagnosticFix({
@@ -250028,7 +250093,7 @@ async function generateScaffold(args) {
250028
250093
  importPath,
250029
250094
  symbol: moduleSymbol
250030
250095
  }, { rootDir: root, dryRun: true });
250031
- if (await readFile8(target, "utf8") !== original)
250096
+ if (await readFile9(target, "utf8") !== original)
250032
250097
  throw new ScaffoldError("SCAFFOLD_CHANGED", "Registration source changed during planning");
250033
250098
  writes.push({ path: args.register_in, content: edit.content, expected: original });
250034
250099
  }
@@ -250049,7 +250114,7 @@ async function generateScaffold(args) {
250049
250114
  written: !args.dry_run,
250050
250115
  changes
250051
250116
  }, null, 2));
250052
- const lines = changes.map((change) => `${args.dry_run ? "PREVIEW" : "✅"} ${change.action}: ${join15(plan.root, change.path)}`);
250117
+ const lines = changes.map((change) => `${args.dry_run ? "PREVIEW" : "✅"} ${change.action}: ${join17(plan.root, change.path)}`);
250053
250118
  if (args.dry_run)
250054
250119
  lines.push("No files written. Omit --dry-run to apply this generation.");
250055
250120
  if (kind === "resource")
@@ -250085,32 +250150,33 @@ function parseInclude(include) {
250085
250150
  return patterns && patterns.length > 0 ? patterns : undefined;
250086
250151
  }
250087
250152
  function sourceRoot(args, root, configured) {
250088
- const hasConfig = ["ts", "mts", "js", "mjs"].some((extension) => existsSync11(join15(root, `supacloud.config.${extension}`)));
250153
+ const hasConfig = ["ts", "mts", "js", "mjs"].some((extension) => existsSync12(join17(root, `supacloud.config.${extension}`)));
250089
250154
  return args.root && !hasConfig ? "." : configured;
250090
250155
  }
250091
250156
  async function projectCompileConfig(args) {
250092
- const root = resolve17(args.root || process.cwd());
250093
- const configFile = ["ts", "mts", "js", "mjs"].map((extension) => join15(root, `supacloud.config.${extension}`)).find((candidate) => existsSync11(candidate)) ?? null;
250157
+ const root = resolve21(args.root || process.cwd());
250158
+ const configFile = ["ts", "mts", "js", "mjs"].map((extension) => join17(root, `supacloud.config.${extension}`)).find((candidate) => existsSync12(candidate)) ?? null;
250094
250159
  const loadedConfig = await loadSupacloudConfig(root);
250095
250160
  const defaults = resolveSupacloudConfig(loadedConfig, root);
250096
250161
  const configuredRoot = sourceRoot(args, root, loadedConfig.root);
250097
250162
  const include = parseInclude(args.include) ?? loadedConfig.include;
250098
- const outDir = args.out_dir ? resolve17(root, args.out_dir) : defaults.outDir;
250099
- const result = await checkProject(compileOptionsFromConfig({
250163
+ const outDir = args.out_dir ? resolve21(root, args.out_dir) : defaults.outDir;
250164
+ const compileOptions = compileOptionsFromConfig({
250100
250165
  ...loadedConfig,
250101
250166
  ...configuredRoot === undefined ? {} : { root: configuredRoot },
250102
250167
  outDir,
250103
250168
  ...include === undefined ? {} : { include },
250104
250169
  strict: args.strict ?? loadedConfig.strict ?? false
250105
- }, root));
250106
- return { root, configFile, outDir, result };
250170
+ }, root);
250171
+ const result = await checkProject(compileOptions);
250172
+ return { root, sourceDir: compileOptions.rootDir, configFile, outDir, result };
250107
250173
  }
250108
250174
  async function readAllowedDependencies(root) {
250109
- const path = join15(root, "package.json");
250110
- if (!existsSync11(path))
250175
+ const path = join17(root, "package.json");
250176
+ if (!existsSync12(path))
250111
250177
  return { dependencies: [], devDependencies: [] };
250112
250178
  try {
250113
- const parsed = JSON.parse(await readFile8(path, "utf8"));
250179
+ const parsed = JSON.parse(await readFile9(path, "utf8"));
250114
250180
  return {
250115
250181
  dependencies: Object.keys(parsed.dependencies ?? {}).sort(),
250116
250182
  devDependencies: Object.keys(parsed.devDependencies ?? {}).sort()
@@ -250187,14 +250253,14 @@ async function findUnwiredContracts(rootDir) {
250187
250253
  async function walk(directory) {
250188
250254
  let entries;
250189
250255
  try {
250190
- entries = await readdir5(directory, { withFileTypes: true });
250256
+ entries = await readdir6(directory, { withFileTypes: true });
250191
250257
  } catch {
250192
250258
  return;
250193
250259
  }
250194
250260
  for (const entry of entries) {
250195
250261
  if (skip.has(entry.name))
250196
250262
  continue;
250197
- const path = join15(directory, entry.name);
250263
+ const path = join17(directory, entry.name);
250198
250264
  if (entry.isDirectory())
250199
250265
  await walk(path);
250200
250266
  else if (entry.isFile() && entry.name.endsWith(".ts"))
@@ -250205,23 +250271,29 @@ async function findUnwiredContracts(rootDir) {
250205
250271
  const contracts = files.filter((file) => file.endsWith(".contract.ts"));
250206
250272
  if (contracts.length === 0)
250207
250273
  return [];
250208
- const otherSources = await Promise.all(files.filter((file) => !contracts.includes(file)).map(async (file) => await readFile8(file, "utf8").catch(() => "")));
250274
+ const otherSources = await Promise.all(files.filter((file) => !contracts.includes(file)).map(async (file) => await readFile9(file, "utf8").catch(() => "")));
250209
250275
  const unwired = [];
250210
250276
  for (const contract of contracts) {
250211
- const source = await readFile8(contract, "utf8").catch(() => "");
250277
+ const source = await readFile9(contract, "utf8").catch(() => "");
250212
250278
  const symbols = [...source.matchAll(/export\s+const\s+([A-Za-z_$][A-Za-z0-9_$]*)/g)].map((match) => match[1]);
250213
250279
  if (symbols.length === 0)
250214
250280
  continue;
250215
250281
  const moduleName = basename8(contract).replace(/\.ts$/, "");
250216
250282
  const wired = otherSources.some((text) => text.includes(moduleName) || symbols.some((symbol) => new RegExp(`\\b${symbol}\\b`).test(text)));
250217
250283
  if (!wired)
250218
- unwired.push({ file: relative12(rootDir, contract), symbols });
250284
+ unwired.push({ file: relative13(rootDir, contract), symbols });
250219
250285
  }
250220
250286
  return unwired;
250221
250287
  }
250222
250288
  async function runDoctor(args) {
250223
250289
  const { root, outDir, result } = await projectCompileConfig(args);
250224
250290
  const doctor = doctorProject(root, outDir, result.graph, result.upToDate, result.diagnostics);
250291
+ const database = checkAppDatabaseSources(root);
250292
+ if (database) {
250293
+ doctor.checks.push(database);
250294
+ if (!database.ok)
250295
+ doctor.errors += 1;
250296
+ }
250225
250297
  const fixPlan = doctorFixPlan(doctor);
250226
250298
  const autoFixable = fixPlan.filter((repair) => repair.readiness === "preview").length;
250227
250299
  const inputRequired = fixPlan.filter((repair) => repair.readiness === "input-required").length;
@@ -250252,11 +250324,11 @@ async function runDoctor(args) {
250252
250324
  `), doctor.errors > 0);
250253
250325
  }
250254
250326
  async function runFix(args) {
250255
- const root = resolve17(args.root || process.cwd());
250327
+ const root = resolve21(args.root || process.cwd());
250256
250328
  const fixPath = args.fix?.trim();
250257
250329
  if (!fixPath)
250258
250330
  throw new Error("app fix requires --fix <path-to-diagnostic-fix.json>");
250259
- const parsed = JSON.parse(await readFile8(resolve17(root, fixPath), "utf8"));
250331
+ const parsed = JSON.parse(await readFile9(resolve21(root, fixPath), "utf8"));
250260
250332
  const write = args.write === true;
250261
250333
  const applied = await applyDiagnosticFix(parsed, { rootDir: root, dryRun: !write });
250262
250334
  return textResult(JSON.stringify({
@@ -250299,7 +250371,7 @@ function devDatabase(profile, explicit) {
250299
250371
  }
250300
250372
  function readManifestModules(outDir) {
250301
250373
  try {
250302
- const parsed = JSON.parse(readFileSync9(join15(outDir, "app.manifest.json"), "utf8"));
250374
+ const parsed = JSON.parse(readFileSync9(join17(outDir, "app.manifest.json"), "utf8"));
250303
250375
  return Array.isArray(parsed.modules) ? parsed.modules.map((module) => module.name) : [];
250304
250376
  } catch {
250305
250377
  return [];
@@ -250327,17 +250399,27 @@ function shutdownWaiter(signal) {
250327
250399
  return { done, stop, dispose };
250328
250400
  }
250329
250401
  async function runAppDev(args, options) {
250402
+ if (args.action === "dev" && args.once !== true && args.watch !== false) {
250403
+ const result = await runLocalDevelopment({
250404
+ root: args.root || process.cwd(),
250405
+ profile: args.profile,
250406
+ databaseUrl: args.database_url,
250407
+ signal: options.signal,
250408
+ onOutput: (text) => options.onDevProgress?.(textResult(args.format === "json" ? JSON.stringify({ version: 1, source: "application", output: text }) : text))
250409
+ });
250410
+ return textResult(JSON.stringify({ version: 1, ok: result.exitCode === 0, ...result }), result.exitCode !== 0);
250411
+ }
250330
250412
  const profile = args.profile ?? "fast";
250331
250413
  if (profile !== "fast" && profile !== "integration") {
250332
250414
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "app dev --profile must be fast or integration");
250333
250415
  }
250334
250416
  const database = devDatabase(profile, args.database_url);
250335
- const root = resolve17(args.root || process.cwd());
250417
+ const root = resolve21(args.root || process.cwd());
250336
250418
  const loadedConfig = await loadSupacloudConfig(root);
250337
250419
  const defaults = resolveSupacloudConfig(loadedConfig, root);
250338
250420
  const configuredRoot = sourceRoot(args, root, loadedConfig.root);
250339
250421
  const include = parseInclude(args.include) ?? loadedConfig.include;
250340
- const outDir = args.out_dir ? resolve17(root, args.out_dir) : defaults.outDir;
250422
+ const outDir = args.out_dir ? resolve21(root, args.out_dir) : defaults.outDir;
250341
250423
  const watch = args.once !== true && args.watch !== false;
250342
250424
  const compileOptions = compileOptionsFromConfig({
250343
250425
  ...loadedConfig,
@@ -250369,7 +250451,7 @@ async function runAppDev(args, options) {
250369
250451
  }, null, progress ? undefined : 2), hasError);
250370
250452
  }
250371
250453
  return textResult([
250372
- `app dev (profile: ${profile}, watch: ${watch ? "on" : "off"}, state: ${state})`,
250454
+ `app watch (compile-only, profile: ${profile}, watch: ${watch ? "on" : "off"}, state: ${state})`,
250373
250455
  ` root: ${root}`,
250374
250456
  ` outDir: ${outDir}`,
250375
250457
  ` database: ${profile === "fast" ? "local profile (no database provisioned or connected)" : `explicit ${database.url} (credentials omitted; not connected)`}`,
@@ -250423,7 +250505,7 @@ async function runAppDev(args, options) {
250423
250505
  return report(lastEvent, "stopped");
250424
250506
  }
250425
250507
  async function runCompile(args) {
250426
- const root = resolve17(args.root || process.cwd());
250508
+ const root = resolve21(args.root || process.cwd());
250427
250509
  const loadedConfig = await loadSupacloudConfig(root);
250428
250510
  const defaults = resolveSupacloudConfig(loadedConfig, root);
250429
250511
  const configuredRoot = sourceRoot(args, root, loadedConfig.root);
@@ -250432,7 +250514,7 @@ async function runCompile(args) {
250432
250514
  ...compileOptionsFromConfig({
250433
250515
  ...loadedConfig,
250434
250516
  ...configuredRoot === undefined ? {} : { root: configuredRoot },
250435
- outDir: args.out_dir ? resolve17(root, args.out_dir) : defaults.outDir,
250517
+ outDir: args.out_dir ? resolve21(root, args.out_dir) : defaults.outDir,
250436
250518
  ...include === undefined ? {} : { include },
250437
250519
  strict: args.strict ?? loadedConfig.strict ?? false
250438
250520
  }, root),
@@ -250456,7 +250538,8 @@ async function runCompile(args) {
250456
250538
  return textResult(text, hasError);
250457
250539
  }
250458
250540
  async function runCheck(args) {
250459
- const root = resolve17(args.root || process.cwd());
250541
+ const root = resolve21(args.root || process.cwd());
250542
+ const database = checkAppDatabaseSources(root);
250460
250543
  const loadedConfig = await loadSupacloudConfig(root);
250461
250544
  const defaults = resolveSupacloudConfig(loadedConfig, root);
250462
250545
  const configuredRoot = sourceRoot(args, root, loadedConfig.root);
@@ -250464,12 +250547,12 @@ async function runCheck(args) {
250464
250547
  const config = compileOptionsFromConfig({
250465
250548
  ...loadedConfig,
250466
250549
  ...configuredRoot === undefined ? {} : { root: configuredRoot },
250467
- outDir: args.out_dir ? resolve17(root, args.out_dir) : defaults.outDir,
250550
+ outDir: args.out_dir ? resolve21(root, args.out_dir) : defaults.outDir,
250468
250551
  ...include === undefined ? {} : { include },
250469
250552
  strict: args.strict ?? loadedConfig.strict ?? false
250470
250553
  }, root);
250471
250554
  const result = await checkProject(config);
250472
- const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.mismatches.length > 0;
250555
+ const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error") || result.mismatches.length > 0 || database?.ok === false;
250473
250556
  if (args.format === "json")
250474
250557
  return textResult(JSON.stringify({
250475
250558
  version: 1,
@@ -250479,12 +250562,14 @@ async function runCheck(args) {
250479
250562
  mismatches: result.mismatches,
250480
250563
  upToDate: result.upToDate,
250481
250564
  written: [],
250482
- modules: result.graph.modules.map((module) => module.name)
250565
+ modules: result.graph.modules.map((module) => module.name),
250566
+ ...database ? { database } : {}
250483
250567
  }, null, 2), hasError);
250484
250568
  const summary = `checked ${result.graph.modules.length} module(s), no files written`;
250485
250569
  return textResult([
250486
250570
  formatDiagnostics(result.diagnostics),
250487
250571
  ...result.mismatches.map((path) => `generated artifact mismatch: ${path}`),
250572
+ ...database ? [`${database.ok ? "OK" : "FAIL"} ${database.name}: ${database.detail}`] : [],
250488
250573
  "",
250489
250574
  summary
250490
250575
  ].join(`
@@ -250492,12 +250577,12 @@ async function runCheck(args) {
250492
250577
  }
250493
250578
  async function readManifest(root, args) {
250494
250579
  const config = await loadSupacloudConfig(root);
250495
- const outDir = args.out_dir ? resolve17(root, args.out_dir) : resolveSupacloudConfig(config, root).outDir;
250496
- const manifestPath = join15(outDir, "app.manifest.json");
250497
- if (!existsSync11(manifestPath)) {
250580
+ const outDir = args.out_dir ? resolve21(root, args.out_dir) : resolveSupacloudConfig(config, root).outDir;
250581
+ const manifestPath = join17(outDir, "app.manifest.json");
250582
+ if (!existsSync12(manifestPath)) {
250498
250583
  throw new Error(`Manifest not found: ${manifestPath}(先运行 app compile 生成)`);
250499
250584
  }
250500
- const parsed = JSON.parse(await readFile8(manifestPath, "utf8"));
250585
+ const parsed = JSON.parse(await readFile9(manifestPath, "utf8"));
250501
250586
  if (!Array.isArray(parsed.modules)) {
250502
250587
  throw new Error(`Invalid manifest: ${manifestPath}`);
250503
250588
  }
@@ -250537,7 +250622,7 @@ function formatGraphText(manifest) {
250537
250622
  `);
250538
250623
  }
250539
250624
  async function runGraph(args) {
250540
- const root = resolve17(args.root || process.cwd());
250625
+ const root = resolve21(args.root || process.cwd());
250541
250626
  const manifest = await readManifest(root, args);
250542
250627
  if (args.format === "json")
250543
250628
  return textResult(JSON.stringify(manifest, null, 2));
@@ -250605,7 +250690,7 @@ async function runExplain(args) {
250605
250690
  const target = args.target?.trim();
250606
250691
  if (!target)
250607
250692
  throw new Error("app explain requires --target(provider 类名 / token 名 / command 名 / job 名)");
250608
- const root = resolve17(args.root || process.cwd());
250693
+ const root = resolve21(args.root || process.cwd());
250609
250694
  const manifest = await readManifest(root, args);
250610
250695
  for (const module of manifest.modules) {
250611
250696
  const job = module.jobs?.find((entry) => entry.name === target || entry.className === target);
@@ -250680,16 +250765,16 @@ async function runExplain(args) {
250680
250765
  return textResult(`未找到对象: ${target}`, true);
250681
250766
  }
250682
250767
  async function runExportTools(args) {
250683
- const root = resolve17(args.root || process.cwd());
250768
+ const root = resolve21(args.root || process.cwd());
250684
250769
  const manifest = await readManifest(root, { ...args, out_dir: undefined });
250685
250770
  const definitions = buildToolDefinitions(manifest);
250686
- const outDir = resolve17(args.out_dir || join15(root, "generated"));
250771
+ const outDir = resolve21(args.out_dir || join17(root, "generated"));
250687
250772
  if (args.format === "json") {
250688
250773
  return textResult(JSON.stringify(definitions, null, 2));
250689
250774
  }
250690
250775
  await mkdir6(outDir, { recursive: true });
250691
- const openaiPath = join15(outDir, "tool-definitions.openai.json");
250692
- const mcpPath = join15(outDir, "tool-definitions.mcp.json");
250776
+ const openaiPath = join17(outDir, "tool-definitions.openai.json");
250777
+ const mcpPath = join17(outDir, "tool-definitions.mcp.json");
250693
250778
  await writeFile5(openaiPath, JSON.stringify(definitions.openai, null, 2), "utf8");
250694
250779
  await writeFile5(mcpPath, JSON.stringify(definitions.mcp, null, 2), "utf8");
250695
250780
  const summary = [
@@ -250704,11 +250789,17 @@ async function runExportTools(args) {
250704
250789
  return textResult(summary);
250705
250790
  }
250706
250791
  async function runDelivery(args) {
250707
- const root = resolve17(args.root || process.cwd());
250792
+ const root = resolve21(args.root || process.cwd());
250793
+ if (args.action === "build") {
250794
+ const database = checkAppDatabaseSources(root);
250795
+ if (database && !database.ok) {
250796
+ return textResult(JSON.stringify({ ok: false, database, written: [] }, null, 2), true);
250797
+ }
250798
+ }
250708
250799
  const loaded = await loadSupacloudConfig(root);
250709
250800
  const options = compileOptionsFromConfig({
250710
250801
  ...loaded,
250711
- ...args.out_dir === undefined ? {} : { outDir: resolve17(root, args.out_dir) },
250802
+ ...args.out_dir === undefined ? {} : { outDir: resolve21(root, args.out_dir) },
250712
250803
  ...args.include === undefined ? {} : { include: parseInclude(args.include) },
250713
250804
  ...args.strict === undefined ? {} : { strict: args.strict }
250714
250805
  }, root);
@@ -250732,7 +250823,7 @@ async function runAppTool(request, options = {}) {
250732
250823
  if ((request.dry_run !== undefined || request.register_in !== undefined) && request.action !== "generate") {
250733
250824
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--dry-run and --register-in apply only to app generate");
250734
250825
  }
250735
- if (request.database_url !== undefined && request.action !== "dev") {
250826
+ if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
250736
250827
  throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
250737
250828
  }
250738
250829
  if (Object.hasOwn(REMOTE_APP_ACTIONS, request.action)) {
@@ -250748,7 +250839,16 @@ async function runAppTool(request, options = {}) {
250748
250839
  return delegate(args);
250749
250840
  }
250750
250841
  switch (request.action) {
250842
+ case "verify-plan": {
250843
+ if (!request.target?.trim())
250844
+ throw new Error("verify-plan requires --target; it never defaults to a full test suite");
250845
+ const { root, sourceDir, result } = await projectCompileConfig(request);
250846
+ const plan = await createVerificationPlan(root, result.graph, request.target.trim(), sourceDir);
250847
+ const ready = plan.ready && !result.diagnostics.some((item) => item.severity === "error");
250848
+ return textResult(JSON.stringify({ ...plan, ready, diagnostics: result.diagnostics }, null, 2), !ready);
250849
+ }
250751
250850
  case "dev":
250851
+ case "watch":
250752
250852
  return runAppDev(request, options);
250753
250853
  case "plan":
250754
250854
  case "build":
@@ -250796,6 +250896,8 @@ function registerAppTools(server2, options = {}) {
250796
250896
  "init",
250797
250897
  "generate",
250798
250898
  "dev",
250899
+ "watch",
250900
+ "verify-plan",
250799
250901
  "compile",
250800
250902
  "check",
250801
250903
  "graph",
@@ -250815,7 +250917,7 @@ function registerAppTools(server2, options = {}) {
250815
250917
  "retire"
250816
250918
  ]), "App action; upload/configure only prepare, rollback activates an explicit old release without schema downgrade"),
250817
250919
  kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
250818
- template: optional(stringEnum(["http", "command", "edge"]), "[init] Golden-path template (default: command)"),
250920
+ template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
250819
250921
  name: optional(Type.String(), "[init/generate] Project or object name"),
250820
250922
  module: optional(Type.String(), "[generate] Target feature module (required for command/query/controller)"),
250821
250923
  dir: optional(Type.String(), "[generate] Feature root directory (default: src/features)"),
@@ -250829,12 +250931,12 @@ function registerAppTools(server2, options = {}) {
250829
250931
  out_dir: optional(Type.String(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
250830
250932
  strict: optional(Type.Boolean(), "[dev/compile/check] Promote warnings to errors"),
250831
250933
  format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/graph/export-tools] Output format (default: text)"),
250832
- profile: optional(stringEnum(["fast", "integration"]), "[dev] Local development profile (default: fast); integration requires an explicit database URL"),
250934
+ profile: optional(stringEnum(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
250833
250935
  once: optional(Type.Boolean(), "[dev] Validate and report once without watching"),
250834
250936
  watch: optional(Type.Boolean(), "[dev] Watch for changes (default: true; --once disables)"),
250835
- database_url: optional(Type.String(), "[dev] Explicit integration database URL (validated shape only; never connected)"),
250937
+ database_url: optional(Type.String(), "[dev] Explicit loopback integration URL; watch/once only inspect its shape"),
250836
250938
  "database-url": optional(Type.String(), "[dev] CLI alias of database_url"),
250837
- target: optional(Type.String(), "[explain/context] Provider class name / token name / command name / job name / module name"),
250939
+ target: optional(Type.String(), "[explain/context/verify-plan] Provider class name / token name / command name / job name / module name"),
250838
250940
  fix: optional(Type.String(), "[fix] Path to a DiagnosticFix JSON file produced by `doctor --format json`"),
250839
250941
  write: optional(Type.Boolean(), "[fix] Write the fix to disk (default: preview only)")
250840
250942
  }, (request) => runAppTool(request, options));
@@ -250866,8 +250968,8 @@ function registerAppAliases(server2) {
250866
250968
  }
250867
250969
 
250868
250970
  // src/shared/tools/db-governance-tools.ts
250869
- import { existsSync as existsSync12 } from "node:fs";
250870
- import { join as join16, resolve as resolve18 } from "node:path";
250971
+ import { existsSync as existsSync13 } from "node:fs";
250972
+ import { join as join18, resolve as resolve23 } from "node:path";
250871
250973
 
250872
250974
  // node_modules/@supacloud/db/dist/index.js
250873
250975
  var TABLES_SQL = `
@@ -251319,8 +251421,8 @@ function normalizeModule(candidate, sourcePath) {
251319
251421
  };
251320
251422
  }
251321
251423
  async function loadDatabaseModules(root, moduleFile) {
251322
- const filePath = moduleFile ? resolve18(root, moduleFile) : join16(root, "db", "modules.ts");
251323
- if (!existsSync12(filePath)) {
251424
+ const filePath = moduleFile ? resolve23(root, moduleFile) : join18(root, "db", "modules.ts");
251425
+ if (!existsSync13(filePath)) {
251324
251426
  throw new Error(moduleFile ? `Module file not found: ${filePath}` : `Module file not found: ${filePath}(用 --module_file 指定一个导出 defineDatabaseModule(...) 的文件)`);
251325
251427
  }
251326
251428
  let imported;
@@ -251368,7 +251470,7 @@ async function runLint(args, root) {
251368
251470
  const issues = [];
251369
251471
  for (const module of selected) {
251370
251472
  const readSqlFile = async (path) => {
251371
- const absolute = resolve18(root, path);
251473
+ const absolute = resolve23(root, path);
251372
251474
  try {
251373
251475
  return await Bun.file(absolute).text();
251374
251476
  } catch (error) {
@@ -251404,14 +251506,14 @@ var defaultLiteSpawn = async (command, cwd) => {
251404
251506
  return { exitCode, stdout, stderr };
251405
251507
  };
251406
251508
  async function runModuleCheckLite(args, root, spawn, liteBinary) {
251407
- const projectDir = resolve18(args.project_dir?.trim() || root);
251509
+ const projectDir = resolve23(args.project_dir?.trim() || root);
251408
251510
  const binary = liteBinary === undefined ? Bun.which("supacloud-lite") : liteBinary;
251409
251511
  if (!binary) {
251410
251512
  throw new Error("db module_check --lite 需要本机可用的 supacloud-lite(npm i -g @supacloud/lite,或加入 PATH)");
251411
251513
  }
251412
251514
  const command = [binary, "db", "check", "--project-dir", projectDir];
251413
251515
  if (args.module_file?.trim())
251414
- command.push("--module-file", resolve18(root, args.module_file.trim()));
251516
+ command.push("--module-file", resolve23(root, args.module_file.trim()));
251415
251517
  const result = await spawn(command, projectDir);
251416
251518
  const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
251417
251519
  `);
@@ -251462,7 +251564,7 @@ function registerDbGovernanceTools(server2, options = {}) {
251462
251564
  lite: optional(Type.Boolean(), "[module_check] Reconcile against a local SupaCloud Lite project (runs supacloud-lite db check)"),
251463
251565
  project_dir: optional(Type.String(), "[module_check] Lite project directory (with --lite; default: --root)")
251464
251566
  }, async (request) => {
251465
- const root = resolve18(request.root || fallbackRoot);
251567
+ const root = resolve23(request.root || fallbackRoot);
251466
251568
  switch (request.action) {
251467
251569
  case "lint":
251468
251570
  return runLint(request, root);
@@ -251479,7 +251581,7 @@ function registerDbGovernanceTools(server2, options = {}) {
251479
251581
  // src/shared/tools/scheduled-function-tools.ts
251480
251582
  import { randomUUID as randomUUID3 } from "node:crypto";
251481
251583
  import { readFileSync as readFileSync10, statSync as statSync7 } from "node:fs";
251482
- import { resolve as resolve20 } from "node:path";
251584
+ import { resolve as resolve24 } from "node:path";
251483
251585
  import { isDeepStrictEqual } from "node:util";
251484
251586
  var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
251485
251587
  var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
@@ -251563,7 +251665,7 @@ function validScheduledFunctionCron(expression) {
251563
251665
  function readScheduleBodyFile(bodyPathInput) {
251564
251666
  if (!bodyPathInput.trim())
251565
251667
  throw new Error("'body_file' must be a path");
251566
- const bodyPath = resolve20(bodyPathInput);
251668
+ const bodyPath = resolve24(bodyPathInput);
251567
251669
  const bodyStat = statSync7(bodyPath);
251568
251670
  if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
251569
251671
  throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
@@ -252146,8 +252248,8 @@ var MUTATION_TOOL_SCHEMA = {
252146
252248
  // src/shared/tools/release-tools.ts
252147
252249
  import { execFileSync } from "node:child_process";
252148
252250
  import { createHash as createHash10 } from "node:crypto";
252149
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
252150
- import { dirname as dirname13, join as join17, relative as relative13, resolve as resolve21 } from "node:path";
252251
+ import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
252252
+ import { dirname as dirname14, join as join19, relative as relative14, resolve as resolve25 } from "node:path";
252151
252253
  var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
252152
252254
  var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
252153
252255
  var SHA256 = /^[a-f0-9]{64}$/;
@@ -252441,15 +252543,15 @@ function collectScopeFiles(cwd, file, files, dir, task) {
252441
252543
  const results = [];
252442
252544
  const resolvePathWithFallbacks = (filePath) => {
252443
252545
  const trimmed = filePath.trim();
252444
- const resolved = resolve21(cwd, trimmed);
252445
- if (existsSync13(resolved))
252546
+ const resolved = resolve25(cwd, trimmed);
252547
+ if (existsSync14(resolved))
252446
252548
  return resolved;
252447
252549
  const candidates = [
252448
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
252449
- resolve21(cwd, "release-scopes", trimmed),
252450
- resolve21(cwd, "scopes", trimmed)
252550
+ resolve25(cwd, "supacloud/fa/release-scopes", trimmed),
252551
+ resolve25(cwd, "release-scopes", trimmed),
252552
+ resolve25(cwd, "scopes", trimmed)
252451
252553
  ];
252452
- const found = candidates.find(existsSync13);
252554
+ const found = candidates.find(existsSync14);
252453
252555
  if (found)
252454
252556
  return found;
252455
252557
  throw new Error(`Scope file not found: ${filePath}`);
@@ -252470,18 +252572,18 @@ function collectScopeFiles(cwd, file, files, dir, task) {
252470
252572
  if (!taskFilter && (!dir || typeof dir !== "string" || !dir.trim())) {
252471
252573
  throw new Error("At least one of --file, --files, or --task is required for scope_rebind");
252472
252574
  }
252473
- const candidates = typeof dir === "string" && dir.trim() ? [resolve21(cwd, dir.trim())] : [
252474
- resolve21(cwd, "supacloud/fa/release-scopes"),
252475
- resolve21(cwd, "release-scopes"),
252476
- resolve21(cwd, "scopes")
252575
+ const candidates = typeof dir === "string" && dir.trim() ? [resolve25(cwd, dir.trim())] : [
252576
+ resolve25(cwd, "supacloud/fa/release-scopes"),
252577
+ resolve25(cwd, "release-scopes"),
252578
+ resolve25(cwd, "scopes")
252477
252579
  ];
252478
252580
  for (const directory of candidates) {
252479
- if (existsSync13(directory)) {
252581
+ if (existsSync14(directory)) {
252480
252582
  try {
252481
252583
  const entries = readdirSync4(directory, { withFileTypes: true });
252482
252584
  for (const entry of entries) {
252483
252585
  if (entry.isFile() && entry.name.endsWith(".json")) {
252484
- const fullPath = join17(directory, entry.name);
252586
+ const fullPath = join19(directory, entry.name);
252485
252587
  if (!taskFilter) {
252486
252588
  results.push(fullPath);
252487
252589
  } else if (entry.name.toLowerCase().includes(taskFilter)) {
@@ -252507,7 +252609,7 @@ function collectScopeFiles(cwd, file, files, dir, task) {
252507
252609
  return [...new Set(results)];
252508
252610
  }
252509
252611
  function handleScopeRebind(args) {
252510
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
252612
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve25(args.cwd.trim()) : process.cwd();
252511
252613
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
252512
252614
  const scopeFiles = collectScopeFiles(cwd, args.file, args.files, args.dir, args.task);
252513
252615
  const dryRun = args.dry_run === true;
@@ -252541,7 +252643,7 @@ function handleScopeRebind(args) {
252541
252643
  writeFileSync3(filePath, formatted, "utf8");
252542
252644
  }
252543
252645
  scopes.push({
252544
- file: relative13(cwd, filePath),
252646
+ file: relative14(cwd, filePath),
252545
252647
  task_id: typeof parsed.taskId === "string" ? parsed.taskId : "",
252546
252648
  target_project_ref: typeof parsed.targetProjectRef === "string" ? parsed.targetProjectRef : "",
252547
252649
  previous_base_commit: previousBaseCommit,
@@ -252559,19 +252661,19 @@ function handleScopeRebind(args) {
252559
252661
  });
252560
252662
  }
252561
252663
  function handleScopeInspect(args) {
252562
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
252664
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve25(args.cwd.trim()) : process.cwd();
252563
252665
  if (typeof args.file !== "string" || !args.file.trim()) {
252564
252666
  throw new Error("'file' is required for scope_inspect");
252565
252667
  }
252566
252668
  const trimmed = args.file.trim();
252567
- let filePath = resolve21(cwd, trimmed);
252568
- if (!existsSync13(filePath)) {
252669
+ let filePath = resolve25(cwd, trimmed);
252670
+ if (!existsSync14(filePath)) {
252569
252671
  const candidates = [
252570
- resolve21(cwd, "supacloud/fa/release-scopes", trimmed),
252571
- resolve21(cwd, "release-scopes", trimmed),
252572
- resolve21(cwd, "scopes", trimmed)
252672
+ resolve25(cwd, "supacloud/fa/release-scopes", trimmed),
252673
+ resolve25(cwd, "release-scopes", trimmed),
252674
+ resolve25(cwd, "scopes", trimmed)
252573
252675
  ];
252574
- const found = candidates.find(existsSync13);
252676
+ const found = candidates.find(existsSync14);
252575
252677
  if (found)
252576
252678
  filePath = found;
252577
252679
  else
@@ -252595,7 +252697,7 @@ function handleScopeInspect(args) {
252595
252697
  const scopeSha256 = genericScopedReleaseScopeSha256(parsed);
252596
252698
  const git = gitCommitInfo(cwd);
252597
252699
  return releaseControlSuccess("release.scope_inspect", {
252598
- file: relative13(cwd, filePath),
252700
+ file: relative14(cwd, filePath),
252599
252701
  scope: {
252600
252702
  task_id: parsed.taskId,
252601
252703
  target_project_ref: parsed.targetProjectRef,
@@ -252629,7 +252731,7 @@ function parseCommaList(value) {
252629
252731
  return [];
252630
252732
  }
252631
252733
  function handleScopeCreate(args, fallbackRef) {
252632
- const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve21(args.cwd.trim()) : process.cwd();
252734
+ const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve25(args.cwd.trim()) : process.cwd();
252633
252735
  if (typeof args.file !== "string" || !args.file.trim()) {
252634
252736
  throw new Error("'file' is required for scope_create");
252635
252737
  }
@@ -252644,8 +252746,8 @@ function handleScopeCreate(args, fallbackRef) {
252644
252746
  throw new Error("'ref' is invalid for release controls");
252645
252747
  }
252646
252748
  const targetBaseCommit = resolveGitBaseCommit(cwd, args.base_commit);
252647
- const filePath = resolve21(cwd, args.file.trim());
252648
- mkdirSync4(dirname13(filePath), { recursive: true });
252749
+ const filePath = resolve25(cwd, args.file.trim());
252750
+ mkdirSync4(dirname14(filePath), { recursive: true });
252649
252751
  const scope = {
252650
252752
  baseCommit: targetBaseCommit,
252651
252753
  excludedFunctions: parseCommaList(args.excluded_functions),
@@ -252661,7 +252763,7 @@ function handleScopeCreate(args, fallbackRef) {
252661
252763
  `;
252662
252764
  writeFileSync3(filePath, formatted, "utf8");
252663
252765
  return releaseControlSuccess("release.scope_create", {
252664
- file: relative13(cwd, filePath),
252766
+ file: relative14(cwd, filePath),
252665
252767
  task_id: scope.taskId,
252666
252768
  target_project_ref: projectRef,
252667
252769
  base_commit: targetBaseCommit,
@@ -252866,11 +252968,11 @@ function registerReleaseTools(server2, http, options = {}) {
252866
252968
  }
252867
252969
 
252868
252970
  // src/shared/tools/deploy-tools.ts
252869
- import { access as access4, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile9, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
252971
+ import { access as access4, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile10, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
252870
252972
  import { createHash as createHash11 } from "node:crypto";
252871
252973
  import { tmpdir as tmpdir2 } from "node:os";
252872
- import { basename as basename9, dirname as dirname14, join as join18, relative as relative14, resolve as resolve23, sep as sep12 } from "node:path";
252873
- import { spawn as spawn3 } from "node:child_process";
252974
+ import { basename as basename9, dirname as dirname15, join as join20, relative as relative15, resolve as resolve26, sep as sep12 } from "node:path";
252975
+ import { spawn as spawn4 } from "node:child_process";
252874
252976
 
252875
252977
  // node_modules/fflate/esm/index.mjs
252876
252978
  import { createRequire as createRequire3 } from "module";
@@ -253553,7 +253655,7 @@ function frontendDeployment(candidate) {
253553
253655
  }
253554
253656
  async function readJson(path) {
253555
253657
  try {
253556
- return JSON.parse(await readFile9(path, "utf8"));
253658
+ return JSON.parse(await readFile10(path, "utf8"));
253557
253659
  } catch (error) {
253558
253660
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
253559
253661
  return null;
@@ -253561,13 +253663,13 @@ async function readJson(path) {
253561
253663
  }
253562
253664
  }
253563
253665
  async function findDeployConfigRoot(startDirectory) {
253564
- let directory = resolve23(startDirectory);
253666
+ let directory = resolve26(startDirectory);
253565
253667
  while (true) {
253566
- if (await pathExists(join18(directory, "supacloud.json")))
253668
+ if (await pathExists(join20(directory, "supacloud.json")))
253567
253669
  return directory;
253568
- const parent = dirname14(directory);
253670
+ const parent = dirname15(directory);
253569
253671
  if (parent === directory)
253570
- return resolve23(startDirectory);
253672
+ return resolve26(startDirectory);
253571
253673
  directory = parent;
253572
253674
  }
253573
253675
  }
@@ -253610,7 +253712,7 @@ function deployTargetConfig(candidate, name) {
253610
253712
  };
253611
253713
  }
253612
253714
  async function readDeployConfig(configRoot) {
253613
- const configPath = join18(configRoot, "supacloud.json");
253715
+ const configPath = join20(configRoot, "supacloud.json");
253614
253716
  const candidate = await readJson(configPath);
253615
253717
  if (candidate === null)
253616
253718
  return {};
@@ -253632,29 +253734,29 @@ async function readDeployConfig(configRoot) {
253632
253734
  };
253633
253735
  }
253634
253736
  async function packageName(projectDirectory) {
253635
- const candidate = record4(await readJson(join18(projectDirectory, "package.json")));
253737
+ const candidate = record4(await readJson(join20(projectDirectory, "package.json")));
253636
253738
  return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
253637
253739
  }
253638
253740
  async function pathExists(path) {
253639
253741
  return access4(path).then(() => true, () => false);
253640
253742
  }
253641
253743
  async function defaultBuildCommand(projectDirectory, configRoot) {
253642
- const packageJson = record4(await readJson(join18(projectDirectory, "package.json")));
253744
+ const packageJson = record4(await readJson(join20(projectDirectory, "package.json")));
253643
253745
  const scripts = record4(packageJson?.scripts);
253644
253746
  if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
253645
253747
  throw new Error("No build command was configured and package.json has no build script");
253646
253748
  }
253647
253749
  let directory = projectDirectory;
253648
253750
  while (true) {
253649
- if (await pathExists(join18(directory, "bun.lock")) || await pathExists(join18(directory, "bun.lockb")))
253751
+ if (await pathExists(join20(directory, "bun.lock")) || await pathExists(join20(directory, "bun.lockb")))
253650
253752
  return "bun run build";
253651
- if (await pathExists(join18(directory, "pnpm-lock.yaml")))
253753
+ if (await pathExists(join20(directory, "pnpm-lock.yaml")))
253652
253754
  return "pnpm run build";
253653
- if (await pathExists(join18(directory, "yarn.lock")))
253755
+ if (await pathExists(join20(directory, "yarn.lock")))
253654
253756
  return "yarn build";
253655
253757
  if (directory === configRoot)
253656
253758
  break;
253657
- const parent = dirname14(directory);
253759
+ const parent = dirname15(directory);
253658
253760
  if (parent === directory || !directory.startsWith(`${configRoot}${sep12}`))
253659
253761
  break;
253660
253762
  directory = parent;
@@ -253685,9 +253787,9 @@ function selectDeployment(deployments, requestedId, projectName) {
253685
253787
  throw new Error(deployments.length === 0 ? "No frontend deployments exist for this project" : `Multiple frontend deployments found (${ids}). Set frontend.id in supacloud.json or pass --id`);
253686
253788
  }
253687
253789
  function targetRoot(configRoot, target, name) {
253688
- const root = resolve23(configRoot, target.root || (name === "frontend" ? "." : name));
253689
- const prefix = `${resolve23(configRoot)}${sep12}`;
253690
- if (root !== resolve23(configRoot) && !root.startsWith(prefix)) {
253790
+ const root = resolve26(configRoot, target.root || (name === "frontend" ? "." : name));
253791
+ const prefix = `${resolve26(configRoot)}${sep12}`;
253792
+ if (root !== resolve26(configRoot) && !root.startsWith(prefix)) {
253691
253793
  throw new Error(`Deploy target '${name}' root must stay inside the repository root`);
253692
253794
  }
253693
253795
  return root;
@@ -253738,8 +253840,8 @@ function defaultOutputDirectory(framework) {
253738
253840
  }
253739
253841
  }
253740
253842
  function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
253741
- const resolved = resolve23(projectDirectory, candidate);
253742
- const projectRoot = resolve23(projectDirectory);
253843
+ const resolved = resolve26(projectDirectory, candidate);
253844
+ const projectRoot = resolve26(projectDirectory);
253743
253845
  const projectPrefix = `${projectRoot}${sep12}`;
253744
253846
  if (!allowRoot && resolved === projectRoot || resolved !== projectRoot && !resolved.startsWith(projectPrefix)) {
253745
253847
  throw new Error("Frontend output directory must stay inside the project directory");
@@ -253748,7 +253850,7 @@ function resolveInsideProject(projectDirectory, candidate, allowRoot = false) {
253748
253850
  }
253749
253851
  async function runBuild(command, cwd) {
253750
253852
  await new Promise((resolvePromise, reject) => {
253751
- const child = spawn3(command, { cwd, env: process.env, shell: true, stdio: "inherit" });
253853
+ const child = spawn4(command, { cwd, env: process.env, shell: true, stdio: "inherit" });
253752
253854
  child.once("error", reject);
253753
253855
  child.once("exit", (code, signal) => {
253754
253856
  if (code === 0)
@@ -253770,7 +253872,7 @@ async function collectArchiveFiles(root) {
253770
253872
  entries.push(entry);
253771
253873
  entries.sort((left, right) => left.name.localeCompare(right.name, "en"));
253772
253874
  for (const entry of entries) {
253773
- const path = join18(directory, entry.name);
253875
+ const path = join20(directory, entry.name);
253774
253876
  const stats = await lstat5(path);
253775
253877
  if (stats.isSymbolicLink())
253776
253878
  throw new Error(`Frontend output cannot contain symbolic links: ${path}`);
@@ -253788,19 +253890,19 @@ async function collectArchiveFiles(root) {
253788
253890
  let bytes = 0;
253789
253891
  const files = {};
253790
253892
  for (const path of paths) {
253791
- const data = new Uint8Array(await readFile9(path));
253893
+ const data = new Uint8Array(await readFile10(path));
253792
253894
  bytes += data.byteLength;
253793
253895
  if (bytes > MAX_SOURCE_BYTES)
253794
253896
  throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
253795
- const archivePath = relative14(root, path).split(sep12).join("/");
253897
+ const archivePath = relative15(root, path).split(sep12).join("/");
253796
253898
  files[archivePath] = [data, { mtime: FIXED_ZIP_MTIME }];
253797
253899
  }
253798
253900
  return { files, bytes, count: paths.length };
253799
253901
  }
253800
253902
  async function createFrontendArchive(outputDirectory) {
253801
253903
  const collected = await collectArchiveFiles(outputDirectory);
253802
- const temporaryDirectory = await mkdtemp2(join18(tmpdir2(), "supacloud-deploy-"));
253803
- const archivePath = join18(temporaryDirectory, `${basename9(outputDirectory)}.zip`);
253904
+ const temporaryDirectory = await mkdtemp2(join20(tmpdir2(), "supacloud-deploy-"));
253905
+ const archivePath = join20(temporaryDirectory, `${basename9(outputDirectory)}.zip`);
253804
253906
  try {
253805
253907
  const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
253806
253908
  await writeFile6(archivePath, archiveBytes);
@@ -253850,7 +253952,7 @@ function deployResponse(result, json) {
253850
253952
  }
253851
253953
  function registerDeployTools(server2, http, options = {}) {
253852
253954
  server2.tool("deploy", "Build and publish the linked frontend with one command", deployToolSchema, async (args) => {
253853
- const invocationDirectory = resolve23(String(args.cwd || options.cwd || process.cwd()));
253955
+ const invocationDirectory = resolve26(String(args.cwd || options.cwd || process.cwd()));
253854
253956
  const projectRef = String(args.ref || options.projectRef || "").trim();
253855
253957
  if (!projectRef)
253856
253958
  throw new Error("A linked project ref is required. Set SUPACLOUD_PROJECT_REF or pass --ref");
@@ -253964,7 +254066,7 @@ function registerDeployTools(server2, http, options = {}) {
253964
254066
  } else {
253965
254067
  report("build", "skipped");
253966
254068
  }
253967
- report("package", relative14(projectDirectory, outputDirectory) || ".");
254069
+ report("package", relative15(projectDirectory, outputDirectory) || ".");
253968
254070
  const archive = await createFrontendArchive(outputDirectory);
253969
254071
  try {
253970
254072
  const inventory = payload(await listFrontendReleases(http, projectRef, selected.id, undefined, 100));
@@ -254022,12 +254124,12 @@ function registerDeployTools(server2, http, options = {}) {
254022
254124
  }
254023
254125
 
254024
254126
  // src/shared/tools/remote-dev-tools.ts
254025
- import { spawn as spawn4 } from "node:child_process";
254026
- import { existsSync as existsSync14 } from "node:fs";
254027
- import { readFile as readFile10 } from "node:fs/promises";
254028
- import { readdir as readdir6 } from "node:fs/promises";
254127
+ import { spawn as spawn5 } from "node:child_process";
254128
+ import { existsSync as existsSync15 } from "node:fs";
254129
+ import { readFile as readFile11 } from "node:fs/promises";
254130
+ import { readdir as readdir7 } from "node:fs/promises";
254029
254131
  import { createHash as createHash12 } from "node:crypto";
254030
- import { join as join19, resolve as resolve24 } from "node:path";
254132
+ import { join as join21, resolve as resolve27 } from "node:path";
254031
254133
  var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
254032
254134
  var SAFE_REMOTE_ROOT = /^\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\/?$/;
254033
254135
  var remoteDevToolSchema = {
@@ -254051,7 +254153,7 @@ var remoteDevToolSchema = {
254051
254153
  };
254052
254154
  function runProcess(executable, args, cwd) {
254053
254155
  return new Promise((resolveResult, reject) => {
254054
- const child = spawn4(executable, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
254156
+ const child = spawn5(executable, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
254055
254157
  let stdout = "";
254056
254158
  let stderr = "";
254057
254159
  child.stdout?.setEncoding("utf8");
@@ -254069,21 +254171,21 @@ function runProcess(executable, args, cwd) {
254069
254171
  function resolveDrizzleCommand(root, configured) {
254070
254172
  if (configured?.trim())
254071
254173
  return configured.trim();
254072
- const local = join19(root, "node_modules", ".bin", "drizzle-kit");
254073
- return existsSync14(local) ? local : "drizzle-kit";
254174
+ const local = join21(root, "node_modules", ".bin", "drizzle-kit");
254175
+ return existsSync15(local) ? local : "drizzle-kit";
254074
254176
  }
254075
254177
  function toolFailed(value) {
254076
254178
  return value?.isError === true || value?.content?.some((chunk) => typeof chunk?.text === "string" && chunk.text.trimStart().startsWith("❌"));
254077
254179
  }
254078
254180
  async function migrateDatabase(args, options) {
254079
- const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
254181
+ const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
254080
254182
  const projectConfig = await readProjectConfig3(root);
254081
254183
  const config = projectConfig.dev || {};
254082
254184
  const database = config.database || {};
254083
254185
  const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
254084
- const drizzleConfig = resolve24(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
254186
+ const drizzleConfig = resolve27(root, String(args.drizzle_config || database.drizzleConfig || "drizzle.config.ts"));
254085
254187
  const migrationsDir = String(args.migrations_dir || database.migrationsDir || "supabase/migrations");
254086
- if (!existsSync14(drizzleConfig))
254188
+ if (!existsSync15(drizzleConfig))
254087
254189
  throw new Error(`Drizzle config not found: ${drizzleConfig}`);
254088
254190
  const generated = await execute(resolveDrizzleCommand(root, typeof args.drizzle_bin === "string" ? args.drizzle_bin : database.drizzleBin), ["generate", "--config", drizzleConfig], root);
254089
254191
  if (generated.exitCode !== 0)
@@ -254101,11 +254203,11 @@ async function migrateDatabase(args, options) {
254101
254203
  return { ok: true, mode: "dev", action: "migrate", generated: true, applied: true, migrations_dir: migrationsDir, result: applied };
254102
254204
  }
254103
254205
  async function readProjectConfig3(root) {
254104
- const file = join19(root, "supacloud.json");
254105
- if (!existsSync14(file))
254206
+ const file = join21(root, "supacloud.json");
254207
+ if (!existsSync15(file))
254106
254208
  return {};
254107
254209
  try {
254108
- const parsed = JSON.parse(await readFile10(file, "utf8"));
254210
+ const parsed = JSON.parse(await readFile11(file, "utf8"));
254109
254211
  return parsed && typeof parsed === "object" ? parsed : {};
254110
254212
  } catch (error) {
254111
254213
  throw new Error(`Invalid supacloud.json: ${error instanceof Error ? error.message : String(error)}`);
@@ -254118,9 +254220,9 @@ async function readDevConfig(root) {
254118
254220
  async function sourceFingerprint(root) {
254119
254221
  const files = [];
254120
254222
  const visit = async (directory) => {
254121
- const entries = await readdir6(directory, { withFileTypes: true });
254223
+ const entries = await readdir7(directory, { withFileTypes: true });
254122
254224
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
254123
- const path = join19(directory, entry.name);
254225
+ const path = join21(directory, entry.name);
254124
254226
  if (entry.isDirectory() && ![".git", "node_modules", "dist", "generated", ".supacloud"].includes(entry.name))
254125
254227
  await visit(path);
254126
254228
  else if (entry.isFile())
@@ -254131,7 +254233,7 @@ async function sourceFingerprint(root) {
254131
254233
  const hash = createHash12("sha256");
254132
254234
  for (const file of files) {
254133
254235
  hash.update(file.slice(root.length).replace(/\\/g, "/"));
254134
- hash.update(await readFile10(file));
254236
+ hash.update(await readFile11(file));
254135
254237
  }
254136
254238
  return hash.digest("hex");
254137
254239
  }
@@ -254148,17 +254250,17 @@ function remoteRoot(value) {
254148
254250
  }
254149
254251
  function targetDirectory(root, target, functionSlug, config = {}) {
254150
254252
  if (target === "db")
254151
- return join19(root, "supabase", "migrations");
254253
+ return join21(root, "supabase", "migrations");
254152
254254
  if (target === "functions") {
254153
254255
  const targets = config.targets && typeof config.targets === "object" ? config.targets : {};
254154
254256
  const match = Object.values(targets).find((entry) => entry?.type === "edge_function" && (!functionSlug || String(entry.slug || "") === functionSlug));
254155
- const base = match?.root ? resolve24(root, String(match.root)) : join19(root, "supabase", "functions");
254156
- return match?.root ? base : functionSlug ? join19(base, safeToken(functionSlug, "function")) : base;
254257
+ const base = match?.root ? resolve27(root, String(match.root)) : join21(root, "supabase", "functions");
254258
+ return match?.root ? base : functionSlug ? join21(base, safeToken(functionSlug, "function")) : base;
254157
254259
  }
254158
254260
  if (target === "frontend") {
254159
254261
  const targets = config.targets && typeof config.targets === "object" ? config.targets : {};
254160
254262
  const match = Object.values(targets).find((entry) => entry?.type === "frontend");
254161
- return match?.root ? resolve24(root, String(match.root)) : join19(root, "apps", "web");
254263
+ return match?.root ? resolve27(root, String(match.root)) : join21(root, "apps", "web");
254162
254264
  }
254163
254265
  return root;
254164
254266
  }
@@ -254197,10 +254299,10 @@ function reloadCommand(config, target, projectRef, functionSlug) {
254197
254299
  return [...command.split(" "), "--project-ref", safeToken(projectRef || "test", "project_ref"), "--target", targetConfigPath(target), ...functionSlug ? ["--function", safeToken(functionSlug, "function")] : []];
254198
254300
  }
254199
254301
  function sshArgs(connection, remoteCommand) {
254200
- return ["-p", String(connection.port), "-i", resolve24(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
254302
+ return ["-p", String(connection.port), "-i", resolve27(connection.key), "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes", `${connection.user}@${connection.host}`, ...remoteCommand];
254201
254303
  }
254202
254304
  function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
254203
- const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve24(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
254305
+ const args = ["-az", "--checksum", "--partial", "--protect-args", "-e", `ssh -p ${connection.port} -i ${resolve27(connection.key)} -o BatchMode=yes -o StrictHostKeyChecking=yes`];
254204
254306
  if (deleteRemote)
254205
254307
  args.push("--delete-delay");
254206
254308
  for (const exclude of excludes) {
@@ -254212,17 +254314,17 @@ function rsyncArgs(connection, source, destination, excludes, deleteRemote) {
254212
254314
  return args;
254213
254315
  }
254214
254316
  async function syncOnce(args, options) {
254215
- const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
254317
+ const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
254216
254318
  const projectConfig = await readProjectConfig3(root);
254217
254319
  const config = projectConfig.dev || {};
254218
254320
  const target = String(args.target || "project");
254219
254321
  const source = targetDirectory(root, target, typeof args.function === "string" ? args.function : undefined, projectConfig);
254220
- if (!existsSync14(source))
254322
+ if (!existsSync15(source))
254221
254323
  throw new Error(`Dev source directory not found: ${source}`);
254222
254324
  let compiled = false;
254223
254325
  if (config.compile === true && target !== "db") {
254224
- const compileRoot = resolve24(root, config.compileRoot || ".");
254225
- const compileOutDir = resolve24(root, config.compileOutDir || "generated");
254326
+ const compileRoot = resolve27(root, config.compileRoot || ".");
254327
+ const compileOutDir = resolve27(root, config.compileOutDir || "generated");
254226
254328
  const compilation = await compileProject({
254227
254329
  rootDir: compileRoot,
254228
254330
  outDir: compileOutDir,
@@ -254260,7 +254362,7 @@ function registerRemoteDevTools(server2, options = {}) {
254260
254362
  if (["production", "prod"].includes((options.environment || "").toLowerCase()))
254261
254363
  throw new Error("Remote dev mode is forbidden for production environments");
254262
254364
  if (args.action === "status") {
254263
- const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
254365
+ const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
254264
254366
  const config = await readDevConfig(root);
254265
254367
  const connection = connectionArgs(options, config, args);
254266
254368
  const execute = options.execute || ((command, commandArgs, cwd) => runProcess(command, commandArgs, cwd));
@@ -254274,7 +254376,7 @@ function registerRemoteDevTools(server2, options = {}) {
254274
254376
  return { content: [{ type: "text", text: JSON.stringify(await migrateDatabase(args, options), null, 2) }] };
254275
254377
  }
254276
254378
  if (args.action === "watch") {
254277
- const root = resolve24(String(args.project_dir || options.cwd || process.cwd()));
254379
+ const root = resolve27(String(args.project_dir || options.cwd || process.cwd()));
254278
254380
  const interval = Math.max(100, Math.min(1e4, Number(args.interval_ms || 300)));
254279
254381
  let fingerprint = "";
254280
254382
  let lastSync = null;
@@ -254293,9 +254395,9 @@ function registerRemoteDevTools(server2, options = {}) {
254293
254395
  });
254294
254396
  }
254295
254397
  // package.json
254296
- var package_default8 = {
254398
+ var package_default = {
254297
254399
  name: "@supacloud/cli",
254298
- version: "0.62.1",
254400
+ version: "0.64.0",
254299
254401
  description: "Project-scoped CLI for SupaCloud users",
254300
254402
  type: "module",
254301
254403
  main: "./dist/index.js",
@@ -254330,7 +254432,7 @@ var package_default8 = {
254330
254432
  },
254331
254433
  dependencies: {
254332
254434
  "@sinclair/typebox": "^0.34.52",
254333
- "@supacloud/compiler": "^0.31.0",
254435
+ "@supacloud/compiler": "^0.31.1",
254334
254436
  "@supacloud/delivery": "0.4.0",
254335
254437
  "@supacloud/db": "^0.2.0",
254336
254438
  fflate: "^0.8.3",
@@ -254340,7 +254442,9 @@ var package_default8 = {
254340
254442
  "@supacloud/delivery": "0.4.0"
254341
254443
  },
254342
254444
  devDependencies: {
254445
+ "@supacloud/elysia": "^0.23.1",
254343
254446
  "@types/bun": "^1.4.2",
254447
+ elysia: "2.0.0-beta.19",
254344
254448
  typescript: "^7.0.2"
254345
254449
  }
254346
254450
  };
@@ -254879,7 +254983,7 @@ function createCliTools(context, confirmProduction) {
254879
254983
  async function main() {
254880
254984
  const rawArgs = process.argv.slice(2);
254881
254985
  if (rawArgs.length === 1 && rawArgs[0] === "--version") {
254882
- console.log(package_default8.version);
254986
+ console.log(package_default.version);
254883
254987
  return;
254884
254988
  }
254885
254989
  const globalOptions = parseGlobalOptions(rawArgs);