@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.
- package/README.md +12 -3
- package/dist/index.js +881 -777
- 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
|
|
221522
|
-
import { mkdir as mkdir6, readFile as
|
|
221523
|
-
import { basename as basename8, dirname as
|
|
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?:
|
|
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": `^${
|
|
247239
|
-
"@supacloud/elysia": `^${
|
|
247030
|
+
"@supacloud/app": `^${appMetadata.version}`,
|
|
247031
|
+
"@supacloud/elysia": `^${elysiaMetadata.version}`,
|
|
247240
247032
|
elysia: "2.0.0-beta.19",
|
|
247241
|
-
rxjs:
|
|
247033
|
+
...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
|
|
247242
247034
|
},
|
|
247243
247035
|
devDependencies: {
|
|
247244
|
-
"@supacloud/compiler": `^${
|
|
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
|
-
|
|
247293
|
-
|
|
247294
|
-
|
|
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
|
|
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
|
-
|
|
248525
|
-
|
|
248526
|
-
|
|
248527
|
-
version:
|
|
248528
|
-
|
|
248529
|
-
|
|
248530
|
-
|
|
248531
|
-
|
|
248532
|
-
|
|
248533
|
-
|
|
248534
|
-
|
|
248535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": `^${
|
|
248868
|
-
"@supacloud/elysia": `^${
|
|
248869
|
-
"@supacloud/commands": `^${
|
|
248870
|
-
"@supacloud/contracts": `^${
|
|
248871
|
-
"@supacloud/db": `^${
|
|
248872
|
-
"
|
|
248873
|
-
"@
|
|
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:
|
|
248586
|
+
rxjs: appMetadata2.dependencies.rxjs
|
|
248876
248587
|
},
|
|
248877
248588
|
devDependencies: {
|
|
248878
|
-
"@supacloud/compiler": `^${
|
|
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
|
-
|
|
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
|
-
|
|
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) ||
|
|
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 ?? "
|
|
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
|
|
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
|
-
|
|
249594
|
-
|
|
249595
|
-
|
|
249596
|
-
throw new Error("
|
|
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:
|
|
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("
|
|
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
|
|
249655
|
-
import { dirname as
|
|
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("\\") ||
|
|
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 =
|
|
249677
|
-
const local =
|
|
249678
|
-
if (!local || local === ".." || local.startsWith(`..${sep11}`) ||
|
|
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 =
|
|
249749
|
+
const parts = relative12(root, target).split(sep11);
|
|
249686
249750
|
let current = root;
|
|
249687
249751
|
for (const [index, part] of parts.entries()) {
|
|
249688
|
-
current =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
249759
|
-
current =
|
|
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 ?? "
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
250003
|
-
if (kind === "controller" &&
|
|
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 =
|
|
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
|
|
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}: ${
|
|
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) =>
|
|
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 =
|
|
250093
|
-
const configFile = ["ts", "mts", "js", "mjs"].map((extension) =>
|
|
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 ?
|
|
250099
|
-
const
|
|
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
|
-
|
|
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 =
|
|
250110
|
-
if (!
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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 ?
|
|
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
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 ?
|
|
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 ?
|
|
250496
|
-
const manifestPath =
|
|
250497
|
-
if (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
250692
|
-
const mcpPath =
|
|
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 =
|
|
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:
|
|
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]
|
|
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]
|
|
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
|
|
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
|
|
250870
|
-
import { join as
|
|
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 ?
|
|
251323
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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",
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
252150
|
-
import { dirname as
|
|
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 =
|
|
252445
|
-
if (
|
|
252546
|
+
const resolved = resolve25(cwd, trimmed);
|
|
252547
|
+
if (existsSync14(resolved))
|
|
252446
252548
|
return resolved;
|
|
252447
252549
|
const candidates = [
|
|
252448
|
-
|
|
252449
|
-
|
|
252450
|
-
|
|
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(
|
|
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() ? [
|
|
252474
|
-
|
|
252475
|
-
|
|
252476
|
-
|
|
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 (
|
|
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 =
|
|
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() ?
|
|
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:
|
|
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() ?
|
|
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 =
|
|
252568
|
-
if (!
|
|
252669
|
+
let filePath = resolve25(cwd, trimmed);
|
|
252670
|
+
if (!existsSync14(filePath)) {
|
|
252569
252671
|
const candidates = [
|
|
252570
|
-
|
|
252571
|
-
|
|
252572
|
-
|
|
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(
|
|
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:
|
|
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() ?
|
|
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 =
|
|
252648
|
-
mkdirSync4(
|
|
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:
|
|
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
|
|
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
|
|
252873
|
-
import { spawn as
|
|
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
|
|
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 =
|
|
253666
|
+
let directory = resolve26(startDirectory);
|
|
253565
253667
|
while (true) {
|
|
253566
|
-
if (await pathExists(
|
|
253668
|
+
if (await pathExists(join20(directory, "supacloud.json")))
|
|
253567
253669
|
return directory;
|
|
253568
|
-
const parent =
|
|
253670
|
+
const parent = dirname15(directory);
|
|
253569
253671
|
if (parent === directory)
|
|
253570
|
-
return
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
253751
|
+
if (await pathExists(join20(directory, "bun.lock")) || await pathExists(join20(directory, "bun.lockb")))
|
|
253650
253752
|
return "bun run build";
|
|
253651
|
-
if (await pathExists(
|
|
253753
|
+
if (await pathExists(join20(directory, "pnpm-lock.yaml")))
|
|
253652
253754
|
return "pnpm run build";
|
|
253653
|
-
if (await pathExists(
|
|
253755
|
+
if (await pathExists(join20(directory, "yarn.lock")))
|
|
253654
253756
|
return "yarn build";
|
|
253655
253757
|
if (directory === configRoot)
|
|
253656
253758
|
break;
|
|
253657
|
-
const parent =
|
|
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 =
|
|
253689
|
-
const prefix = `${
|
|
253690
|
-
if (root !==
|
|
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 =
|
|
253742
|
-
const projectRoot =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
253803
|
-
const archivePath =
|
|
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 =
|
|
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",
|
|
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
|
|
254026
|
-
import { existsSync as
|
|
254027
|
-
import { readFile as
|
|
254028
|
-
import { readdir as
|
|
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
|
|
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 =
|
|
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 =
|
|
254073
|
-
return
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
254105
|
-
if (!
|
|
254206
|
+
const file = join21(root, "supacloud.json");
|
|
254207
|
+
if (!existsSync15(file))
|
|
254106
254208
|
return {};
|
|
254107
254209
|
try {
|
|
254108
|
-
const parsed = JSON.parse(await
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 ?
|
|
254156
|
-
return match?.root ? base : functionSlug ?
|
|
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 ?
|
|
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",
|
|
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 ${
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
254225
|
-
const compileOutDir =
|
|
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 =
|
|
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 =
|
|
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
|
|
254398
|
+
var package_default = {
|
|
254297
254399
|
name: "@supacloud/cli",
|
|
254298
|
-
version: "0.
|
|
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.
|
|
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(
|
|
254986
|
+
console.log(package_default.version);
|
|
254883
254987
|
return;
|
|
254884
254988
|
}
|
|
254885
254989
|
const globalOptions = parseGlobalOptions(rawArgs);
|