@supatype/cli 0.1.6 → 0.1.7
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +144 -94
- package/.turbo/turbo-typecheck.log +1 -1
- package/deno.d.ts +52 -0
- package/dist/augmentation-generator.d.ts +22 -0
- package/dist/augmentation-generator.d.ts.map +1 -1
- package/dist/augmentation-generator.js +18 -4
- package/dist/augmentation-generator.js.map +1 -1
- package/dist/binary-cache.d.ts +14 -0
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +33 -13
- package/dist/binary-cache.js.map +1 -1
- package/dist/cache-clean.d.ts +15 -0
- package/dist/cache-clean.d.ts.map +1 -0
- package/dist/cache-clean.js +76 -0
- package/dist/cache-clean.js.map +1 -0
- package/dist/cache-pins.d.ts +51 -0
- package/dist/cache-pins.d.ts.map +1 -0
- package/dist/cache-pins.js +184 -0
- package/dist/cache-pins.js.map +1 -0
- package/dist/cli-install-method.d.ts +19 -0
- package/dist/cli-install-method.d.ts.map +1 -0
- package/dist/cli-install-method.js +60 -0
- package/dist/cli-install-method.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud-credentials.d.ts +34 -0
- package/dist/cloud-credentials.d.ts.map +1 -0
- package/dist/cloud-credentials.js +139 -0
- package/dist/cloud-credentials.js.map +1 -0
- package/dist/commands/admin.d.ts +11 -0
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +121 -19
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js +67 -71
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +32 -8
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts +1 -0
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +147 -1
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +50 -10
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +60 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/functions.d.ts +7 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +40 -6
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +5 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/hooks.d.ts +12 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +118 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +41 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/internal.d.ts +6 -0
- package/dist/commands/internal.d.ts.map +1 -0
- package/dist/commands/internal.js +22 -0
- package/dist/commands/internal.js.map +1 -0
- package/dist/commands/link-helpers.d.ts.map +1 -1
- package/dist/commands/link-helpers.js +8 -2
- package/dist/commands/link-helpers.js.map +1 -1
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +110 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +110 -7
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/self-update.d.ts +7 -0
- package/dist/commands/self-update.d.ts.map +1 -1
- package/dist/commands/self-update.js +18 -11
- package/dist/commands/self-update.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -3
- package/dist/config.js.map +1 -1
- package/dist/db-preflight.d.ts +78 -0
- package/dist/db-preflight.d.ts.map +1 -0
- package/dist/db-preflight.js +452 -0
- package/dist/db-preflight.js.map +1 -0
- package/dist/dev-compose.d.ts +1 -1
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +127 -72
- package/dist/dev-compose.js.map +1 -1
- package/dist/env-file.d.ts +40 -2
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +132 -11
- package/dist/env-file.js.map +1 -1
- package/dist/field-masking-tier.d.ts +50 -0
- package/dist/field-masking-tier.d.ts.map +1 -0
- package/dist/field-masking-tier.js +91 -0
- package/dist/field-masking-tier.js.map +1 -0
- package/dist/functions-deno-types.d.ts +24 -0
- package/dist/functions-deno-types.d.ts.map +1 -0
- package/dist/functions-deno-types.js +155 -0
- package/dist/functions-deno-types.js.map +1 -0
- package/dist/hook-upload.d.ts +30 -0
- package/dist/hook-upload.d.ts.map +1 -0
- package/dist/hook-upload.js +86 -0
- package/dist/hook-upload.js.map +1 -0
- package/dist/hooks-generator.d.ts +8 -0
- package/dist/hooks-generator.d.ts.map +1 -0
- package/dist/hooks-generator.js +332 -0
- package/dist/hooks-generator.js.map +1 -0
- package/dist/link.d.ts +4 -0
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +2 -0
- package/dist/link.js.map +1 -1
- package/dist/local-secrets.d.ts +71 -0
- package/dist/local-secrets.d.ts.map +1 -0
- package/dist/local-secrets.js +100 -0
- package/dist/local-secrets.js.map +1 -0
- package/dist/model-hooks.d.ts +73 -0
- package/dist/model-hooks.d.ts.map +1 -0
- package/dist/model-hooks.js +211 -0
- package/dist/model-hooks.js.map +1 -0
- package/dist/postgres-ctl.d.ts +11 -0
- package/dist/postgres-ctl.d.ts.map +1 -1
- package/dist/postgres-ctl.js +25 -2
- package/dist/postgres-ctl.js.map +1 -1
- package/dist/project-config.d.ts +121 -1
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +150 -1
- package/dist/project-config.js.map +1 -1
- package/dist/resolve-target.d.ts +4 -0
- package/dist/resolve-target.d.ts.map +1 -1
- package/dist/resolve-target.js +69 -73
- package/dist/resolve-target.js.map +1 -1
- package/dist/schema-ast-v2.d.ts +2 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +4 -2
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-push-lock.d.ts.map +1 -1
- package/dist/schema-push-lock.js +43 -16
- package/dist/schema-push-lock.js.map +1 -1
- package/dist/self-host-compose.d.ts +24 -0
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +301 -69
- package/dist/self-host-compose.js.map +1 -1
- package/dist/service-role-check.d.ts +21 -0
- package/dist/service-role-check.d.ts.map +1 -0
- package/dist/service-role-check.js +85 -0
- package/dist/service-role-check.js.map +1 -0
- package/dist/target-client.d.ts +17 -0
- package/dist/target-client.d.ts.map +1 -1
- package/dist/target-client.js +69 -17
- package/dist/target-client.js.map +1 -1
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +736 -32
- package/dist/type-extractor.js.map +1 -1
- package/dist/ui/runtime/command-chrome.js +1 -1
- package/dist/ui/runtime/command-chrome.js.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.js +3 -1
- package/dist/ui/runtime/stdin-after-ink.js.map +1 -1
- package/package.json +4 -1
- package/scripts/generate-homebrew-formula.mjs +121 -0
- package/src/augmentation-generator.ts +18 -5
- package/src/binary-cache.ts +47 -15
- package/src/cache-clean.ts +81 -0
- package/src/cache-pins.ts +243 -0
- package/src/cli-install-method.ts +67 -0
- package/src/cli.ts +6 -0
- package/src/cloud-credentials.ts +183 -0
- package/src/commands/admin.ts +140 -25
- package/src/commands/cache.ts +90 -75
- package/src/commands/cloud.ts +32 -8
- package/src/commands/db.ts +162 -1
- package/src/commands/dev.ts +57 -8
- package/src/commands/doctor.ts +69 -3
- package/src/commands/functions.ts +42 -5
- package/src/commands/generate.ts +5 -1
- package/src/commands/hooks.ts +138 -0
- package/src/commands/init.ts +42 -8
- package/src/commands/internal.ts +23 -0
- package/src/commands/link-helpers.ts +9 -2
- package/src/commands/login.ts +127 -0
- package/src/commands/push.ts +124 -7
- package/src/commands/self-update.ts +85 -13
- package/src/config.ts +22 -3
- package/src/db-preflight.ts +575 -0
- package/src/dev-compose.ts +142 -73
- package/src/env-file.ts +157 -11
- package/src/field-masking-tier.ts +107 -0
- package/src/functions-deno-types.ts +188 -0
- package/src/hook-upload.ts +107 -0
- package/src/hooks-generator.ts +352 -0
- package/src/link.ts +7 -0
- package/src/local-secrets.ts +104 -0
- package/src/model-hooks.ts +260 -0
- package/src/postgres-ctl.ts +29 -2
- package/src/project-config.ts +236 -2
- package/src/resolve-target.ts +113 -68
- package/src/schema-ast-v2.ts +5 -2
- package/src/schema-push-lock.ts +56 -17
- package/src/self-host-compose.ts +344 -68
- package/src/service-role-check.ts +111 -0
- package/src/target-client.ts +105 -18
- package/src/type-extractor.ts +883 -28
- package/src/ui/runtime/command-chrome.ts +1 -1
- package/src/ui/runtime/stdin-after-ink.ts +3 -1
- package/tests/access-composition.test.ts +667 -0
- package/tests/admin-studio-membership.test.ts +121 -0
- package/tests/api-schemas.test.ts +83 -0
- package/tests/archive-name-alias.test.ts +42 -0
- package/tests/augmentation-generator.test.ts +28 -0
- package/tests/cache-clean.test.ts +206 -0
- package/tests/cli-help.test.ts +3 -0
- package/tests/cli-install-method.test.ts +66 -0
- package/tests/command-chrome-coverage.test.ts +31 -26
- package/tests/command-chrome-wrap.test.ts +1 -0
- package/tests/db-preflight.test.ts +72 -0
- package/tests/doctor-render.test.ts +62 -0
- package/tests/doctor-service-role.test.ts +55 -0
- package/tests/env-file.test.ts +121 -0
- package/tests/external-database-compose.test.ts +184 -0
- package/tests/external-database-config.test.ts +154 -0
- package/tests/field-masking-tier.test.ts +184 -0
- package/tests/functions-deno-types.test.ts +197 -0
- package/tests/homebrew-formula.test.ts +58 -0
- package/tests/hook-upload.test.ts +125 -0
- package/tests/hooks-generator.test.ts +212 -0
- package/tests/init.test.ts +3 -0
- package/tests/local-secrets.test.ts +92 -0
- package/tests/model-hooks.test.ts +322 -0
- package/tests/runtime-contract.test.ts +113 -1
- package/tests/service-role-check.test.ts +109 -0
- package/tests/service-role-routes.test.ts +51 -0
- package/tests/target-client-refresh.test.ts +94 -0
- package/tests/type-extractor.test.ts +187 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/tests/engine-distribution.test.ts +0 -418
package/dist/env-file.js
CHANGED
|
@@ -1,18 +1,132 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Marks a value the CLI wrote from `versions` in `supatype.config.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Provenance, so that removing a pin from config can clean up the stale image reference it left
|
|
7
|
+
* behind *without* deleting a value the operator wrote by hand. Before this, the image keys derived
|
|
8
|
+
* from config were removed unconditionally on every compose run: an operator could override
|
|
9
|
+
* `SUPATYPE_REALTIME_IMAGE` from `.env` and could not override `SUPATYPE_SERVER_IMAGE`, because the
|
|
10
|
+
* latter is derivable from a pin and the former is not. Same file, same shape, different rules.
|
|
11
|
+
*/
|
|
12
|
+
export const MANAGED_MARKER = "# supatype:managed — value from versions in supatype.config.ts";
|
|
13
|
+
function parseLines(text) {
|
|
14
|
+
const out = [];
|
|
15
|
+
if (text === "")
|
|
16
|
+
return out;
|
|
17
|
+
let markerPending = false;
|
|
18
|
+
// A file ending in a newline yields a final empty element; keeping it would put a blank line
|
|
19
|
+
// between the existing content and anything appended.
|
|
20
|
+
const raws = text.split(/\r?\n/);
|
|
21
|
+
while (raws.length > 0 && raws[raws.length - 1] === "")
|
|
22
|
+
raws.pop();
|
|
23
|
+
for (const raw of raws) {
|
|
24
|
+
const trimmed = raw.trim();
|
|
25
|
+
if (trimmed === MANAGED_MARKER) {
|
|
26
|
+
markerPending = true;
|
|
27
|
+
out.push({ text: raw, managed: false });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const eq = raw.indexOf("=");
|
|
31
|
+
const key = eq > 0 ? raw.slice(0, eq).trim() : undefined;
|
|
32
|
+
const isAssignment = key !== undefined && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
|
|
33
|
+
out.push({ ...(isAssignment && { key }), text: raw, managed: isAssignment && markerPending });
|
|
34
|
+
if (!isAssignment && trimmed !== "")
|
|
35
|
+
markerPending = false;
|
|
36
|
+
else if (isAssignment)
|
|
37
|
+
markerPending = false;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Merge key/value updates into a project `.env`.
|
|
43
|
+
*
|
|
44
|
+
* Comments, blank lines and ordering are preserved: this file is edited by hand, and the previous
|
|
45
|
+
* implementation rebuilt it from the assignments alone, so every comment an operator had written
|
|
46
|
+
* disappeared the first time they ran anything that touched `.env`.
|
|
47
|
+
*/
|
|
48
|
+
export function upsertEnvFile(cwd, updates, options = {}) {
|
|
49
|
+
// Historically the third argument was a plain list of keys to remove unconditionally.
|
|
50
|
+
const opts = Array.isArray(options)
|
|
51
|
+
? { remove: options }
|
|
52
|
+
: options;
|
|
53
|
+
const removeManaged = new Set(opts.removeManaged ?? []);
|
|
54
|
+
const removeAlways = new Set(opts.remove ?? []);
|
|
55
|
+
const managed = new Set(opts.managed ?? []);
|
|
5
56
|
const envPath = join(cwd, ".env");
|
|
6
57
|
const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
58
|
+
const lines = parseLines(existing);
|
|
59
|
+
const written = new Set();
|
|
60
|
+
const kept = [];
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
const line = lines[i];
|
|
63
|
+
const key = line.key;
|
|
64
|
+
if (key === undefined) {
|
|
65
|
+
kept.push(line);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (removeAlways.has(key) || (removeManaged.has(key) && line.managed)) {
|
|
69
|
+
// Drop the marker that introduced it, so no orphan comment is left behind.
|
|
70
|
+
if (line.managed && kept[kept.length - 1]?.text.trim() === MANAGED_MARKER)
|
|
71
|
+
kept.pop();
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (key in updates) {
|
|
75
|
+
const wantsMarker = managed.has(key);
|
|
76
|
+
if (wantsMarker && kept[kept.length - 1]?.text.trim() !== MANAGED_MARKER) {
|
|
77
|
+
kept.push({ text: MANAGED_MARKER, managed: false });
|
|
78
|
+
}
|
|
79
|
+
if (!wantsMarker && line.managed && kept[kept.length - 1]?.text.trim() === MANAGED_MARKER) {
|
|
80
|
+
kept.pop();
|
|
81
|
+
}
|
|
82
|
+
kept.push({ key, text: `${key}=${updates[key]}`, managed: wantsMarker });
|
|
83
|
+
written.add(key);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
kept.push(line);
|
|
87
|
+
}
|
|
88
|
+
const appended = [];
|
|
89
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
90
|
+
if (written.has(key))
|
|
91
|
+
continue;
|
|
92
|
+
if (managed.has(key))
|
|
93
|
+
appended.push(MANAGED_MARKER);
|
|
94
|
+
appended.push(`${key}=${value}`);
|
|
95
|
+
}
|
|
96
|
+
const body = [...kept.map((l) => l.text), ...appended].join("\n");
|
|
97
|
+
writeFileSync(envPath, `${body.replace(/\n+$/, "")}\n`, "utf8");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse a project `.env` into key/value pairs.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately small: `KEY=value`, `#` comments, one optional layer of surrounding quotes. No
|
|
103
|
+
* interpolation and no `export` handling, because Compose's own parser does not do those either and
|
|
104
|
+
* a `.env` that means two different things depending on who reads it is worse than a limited one.
|
|
105
|
+
*/
|
|
106
|
+
export function readEnvFile(cwd) {
|
|
107
|
+
const envPath = join(cwd, ".env");
|
|
108
|
+
if (!existsSync(envPath))
|
|
109
|
+
return {};
|
|
110
|
+
const out = {};
|
|
111
|
+
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
if (trimmed.length === 0 || trimmed.startsWith("#"))
|
|
114
|
+
continue;
|
|
115
|
+
const eq = trimmed.indexOf("=");
|
|
116
|
+
if (eq <= 0)
|
|
117
|
+
continue;
|
|
118
|
+
const key = trimmed.slice(0, eq).trim();
|
|
119
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
120
|
+
continue;
|
|
121
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
122
|
+
if (value.length >= 2 &&
|
|
123
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
124
|
+
(value.startsWith("'") && value.endsWith("'")))) {
|
|
125
|
+
value = value.slice(1, -1);
|
|
126
|
+
}
|
|
127
|
+
out[key] = value;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
16
130
|
}
|
|
17
131
|
export function readEnvValue(cwd, key, fallback) {
|
|
18
132
|
const envPath = join(cwd, ".env");
|
|
@@ -23,6 +137,13 @@ export function readEnvValue(cwd, key, fallback) {
|
|
|
23
137
|
}
|
|
24
138
|
return fallback;
|
|
25
139
|
}
|
|
140
|
+
/** Whether `.env` carries a non-empty value for `key`. Distinguishes absent from defaulted. */
|
|
141
|
+
export function hasEnvValue(cwd, key) {
|
|
142
|
+
const envPath = join(cwd, ".env");
|
|
143
|
+
if (!existsSync(envPath))
|
|
144
|
+
return false;
|
|
145
|
+
return new RegExp(`^${key}=(.+)$`, "m").test(readFileSync(envPath, "utf8"));
|
|
146
|
+
}
|
|
26
147
|
export function readEnvInt(cwd, key) {
|
|
27
148
|
const raw = readEnvValue(cwd, key, "");
|
|
28
149
|
if (!raw)
|
package/dist/env-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,
|
|
1
|
+
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which mechanism will enforce this project's per-column rules — decided offline.
|
|
3
|
+
*
|
|
4
|
+
* The engine makes the same decision at push time by asking the database whether `supatype_mask` is
|
|
5
|
+
* installed. The CLI has to answer earlier: it writes `PGRST_DB_SCHEMA` when it generates compose,
|
|
6
|
+
* before any database necessarily exists. It can, because the extension needs
|
|
7
|
+
* `shared_preload_libraries` and a compiled library on the host, so exactly one path ships it —
|
|
8
|
+
* `supatype/postgres`. Everything else gets the view tier:
|
|
9
|
+
*
|
|
10
|
+
* | Config | Ships the extension | Tier |
|
|
11
|
+
* | -------------------------------------------- | ------------------- | --------- |
|
|
12
|
+
* | `database.provider: "docker"` | yes | extension |
|
|
13
|
+
* | `provider: native` | no | views |
|
|
14
|
+
* | `database.external` | no | views |
|
|
15
|
+
* | `provider: docker` + `database.image` override | no | views |
|
|
16
|
+
*
|
|
17
|
+
* Deliberately **not** "is the database external": a native `supatype dev` has no extension either,
|
|
18
|
+
* and treating it as tier 1 would point PostgREST at a schema whose tables the API roles no longer
|
|
19
|
+
* hold privileges on — every request denied.
|
|
20
|
+
*
|
|
21
|
+
* The one case this cannot call is a self-managed Postgres where the operator compiled and installed
|
|
22
|
+
* the extension themselves and points at it with `database.external`. They set `schema.api_schemas`
|
|
23
|
+
* explicitly; `supatype db check` reports the mismatch either way.
|
|
24
|
+
*/
|
|
25
|
+
export { nativeMaskLibraryPresent } from "./postgres-ctl.js";
|
|
26
|
+
import type { SupatypeProjectConfig } from "./project-config.js";
|
|
27
|
+
export type FieldMaskingTier = "none" | "extension" | "views";
|
|
28
|
+
/** True when the Postgres this project runs is one that carries `supatype_mask`. */
|
|
29
|
+
export declare function imageShipsMaskExtension(config: SupatypeProjectConfig): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether any model masks a column.
|
|
32
|
+
*
|
|
33
|
+
* Read from the extracted AST rather than the config, because that is where field rules live. Shaped
|
|
34
|
+
* loosely on purpose: the CLI's AST type carries `platform.access` as an opaque record, and this only
|
|
35
|
+
* needs to know whether a `fields` map is present and non-empty.
|
|
36
|
+
*/
|
|
37
|
+
export declare function schemaHasFieldRules(ast: unknown): boolean;
|
|
38
|
+
/** The tier this project's next push will use. */
|
|
39
|
+
export declare function fieldMaskingTier(config: SupatypeProjectConfig, ast: unknown,
|
|
40
|
+
/** Set on the native path once the Postgres install is known — see [`nativeMaskLibraryPresent`]. */
|
|
41
|
+
maskLibraryPresent?: boolean): FieldMaskingTier;
|
|
42
|
+
/**
|
|
43
|
+
* The tier for a project on disk, loading its schema to see whether any column is masked.
|
|
44
|
+
*
|
|
45
|
+
* A schema that fails to load falls back to `"none"` — the default exposed-schema list. Deciding the
|
|
46
|
+
* tier is the wrong moment to report a syntax error, and `push`/`dev` do it properly moments later
|
|
47
|
+
* with the file and line.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fieldMaskingTierFromProject(cwd: string, config: SupatypeProjectConfig, maskLibraryPresent?: boolean): FieldMaskingTier;
|
|
50
|
+
//# sourceMappingURL=field-masking-tier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-masking-tier.d.ts","sourceRoot":"","sources":["../src/field-masking-tier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAOhE,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAA;AAY7D,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAG9E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUzD;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,qBAAqB,EAC7B,GAAG,EAAE,OAAO;AACZ,oGAAoG;AACpG,kBAAkB,CAAC,EAAE,OAAO,GAC3B,gBAAgB,CAIlB;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,qBAAqB,EAC7B,kBAAkB,CAAC,EAAE,OAAO,GAC3B,gBAAgB,CAQlB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which mechanism will enforce this project's per-column rules — decided offline.
|
|
3
|
+
*
|
|
4
|
+
* The engine makes the same decision at push time by asking the database whether `supatype_mask` is
|
|
5
|
+
* installed. The CLI has to answer earlier: it writes `PGRST_DB_SCHEMA` when it generates compose,
|
|
6
|
+
* before any database necessarily exists. It can, because the extension needs
|
|
7
|
+
* `shared_preload_libraries` and a compiled library on the host, so exactly one path ships it —
|
|
8
|
+
* `supatype/postgres`. Everything else gets the view tier:
|
|
9
|
+
*
|
|
10
|
+
* | Config | Ships the extension | Tier |
|
|
11
|
+
* | -------------------------------------------- | ------------------- | --------- |
|
|
12
|
+
* | `database.provider: "docker"` | yes | extension |
|
|
13
|
+
* | `provider: native` | no | views |
|
|
14
|
+
* | `database.external` | no | views |
|
|
15
|
+
* | `provider: docker` + `database.image` override | no | views |
|
|
16
|
+
*
|
|
17
|
+
* Deliberately **not** "is the database external": a native `supatype dev` has no extension either,
|
|
18
|
+
* and treating it as tier 1 would point PostgREST at a schema whose tables the API roles no longer
|
|
19
|
+
* hold privileges on — every request denied.
|
|
20
|
+
*
|
|
21
|
+
* The one case this cannot call is a self-managed Postgres where the operator compiled and installed
|
|
22
|
+
* the extension themselves and points at it with `database.external`. They set `schema.api_schemas`
|
|
23
|
+
* explicitly; `supatype db check` reports the mismatch either way.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
// Statically imported: these packages are ESM, where `require` does not exist. The first attempt used
|
|
27
|
+
// it for a lazy import and every command that generates compose would have thrown at runtime — caught
|
|
28
|
+
// by the tests that run the built binary rather than the source.
|
|
29
|
+
import { loadSchemaAst } from "./config.js";
|
|
30
|
+
export { nativeMaskLibraryPresent } from "./postgres-ctl.js";
|
|
31
|
+
import { resolveRuntimeProvider, schemaPathFromProject, usesExternalDatabase, } from "./project-config.js";
|
|
32
|
+
/** Postgres image this project would run, when it runs one at all. */
|
|
33
|
+
function postgresImage(config) {
|
|
34
|
+
if (usesExternalDatabase(config))
|
|
35
|
+
return undefined;
|
|
36
|
+
if (config.database.provider !== "docker" && resolveRuntimeProvider(config) !== "docker") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return config.database.image?.trim() || "supatype/postgres:latest";
|
|
40
|
+
}
|
|
41
|
+
/** True when the Postgres this project runs is one that carries `supatype_mask`. */
|
|
42
|
+
export function imageShipsMaskExtension(config) {
|
|
43
|
+
const image = postgresImage(config);
|
|
44
|
+
return image !== undefined && /^supatype\/postgres(:|$)/.test(image);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether any model masks a column.
|
|
48
|
+
*
|
|
49
|
+
* Read from the extracted AST rather than the config, because that is where field rules live. Shaped
|
|
50
|
+
* loosely on purpose: the CLI's AST type carries `platform.access` as an opaque record, and this only
|
|
51
|
+
* needs to know whether a `fields` map is present and non-empty.
|
|
52
|
+
*/
|
|
53
|
+
export function schemaHasFieldRules(ast) {
|
|
54
|
+
const models = ast?.models;
|
|
55
|
+
if (!Array.isArray(models))
|
|
56
|
+
return false;
|
|
57
|
+
return models.some((model) => {
|
|
58
|
+
const access = model?.annotations?.platform?.access;
|
|
59
|
+
const fields = access?.["fields"];
|
|
60
|
+
return typeof fields === "object" && fields !== null && Object.keys(fields).length > 0;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** The tier this project's next push will use. */
|
|
64
|
+
export function fieldMaskingTier(config, ast,
|
|
65
|
+
/** Set on the native path once the Postgres install is known — see [`nativeMaskLibraryPresent`]. */
|
|
66
|
+
maskLibraryPresent) {
|
|
67
|
+
if (!schemaHasFieldRules(ast))
|
|
68
|
+
return "none";
|
|
69
|
+
if (maskLibraryPresent === true)
|
|
70
|
+
return "extension";
|
|
71
|
+
return imageShipsMaskExtension(config) ? "extension" : "views";
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The tier for a project on disk, loading its schema to see whether any column is masked.
|
|
75
|
+
*
|
|
76
|
+
* A schema that fails to load falls back to `"none"` — the default exposed-schema list. Deciding the
|
|
77
|
+
* tier is the wrong moment to report a syntax error, and `push`/`dev` do it properly moments later
|
|
78
|
+
* with the file and line.
|
|
79
|
+
*/
|
|
80
|
+
export function fieldMaskingTierFromProject(cwd, config, maskLibraryPresent) {
|
|
81
|
+
try {
|
|
82
|
+
const schemaPath = schemaPathFromProject(config, cwd);
|
|
83
|
+
if (!existsSync(schemaPath))
|
|
84
|
+
return "none";
|
|
85
|
+
return fieldMaskingTier(config, loadSchemaAst(schemaPath, cwd), maskLibraryPresent);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return "none";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=field-masking-tier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-masking-tier.js","sourceRoot":"","sources":["../src/field-masking-tier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,sGAAsG;AACtG,sGAAsG;AACtG,iEAAiE;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAE5D,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAK5B,sEAAsE;AACtE,SAAS,aAAa,CAAC,MAA6B;IAClD,IAAI,oBAAoB,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAA;IAClD,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,sBAAsB,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzF,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,0BAA0B,CAAA;AACpE,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,uBAAuB,CAAC,MAA6B;IACnE,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACnC,OAAO,KAAK,KAAK,SAAS,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,MAAM,MAAM,GAAI,GAAqC,EAAE,MAAM,CAAA;IAC7D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,MAAM,MAAM,GACV,KACD,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAA;QACjC,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,gBAAgB,CAC9B,MAA6B,EAC7B,GAAY;AACZ,oGAAoG;AACpG,kBAA4B;IAE5B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAA;IAC5C,IAAI,kBAAkB,KAAK,IAAI;QAAE,OAAO,WAAW,CAAA;IACnD,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAA;AAChE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACzC,GAAW,EACX,MAA6B,EAC7B,kBAA4B;IAE5B,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACrD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,MAAM,CAAA;QAC1C,OAAO,gBAAgB,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,kBAAkB,CAAC,CAAA;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAA;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold Deno IDE types for edge functions so tsserver / Cursor stop
|
|
3
|
+
* reporting "Cannot find name 'Deno'" under the project root tsconfig.
|
|
4
|
+
*
|
|
5
|
+
* We ship a curated ambient declaration (not `@types/deno` at the app root):
|
|
6
|
+
* installing `@types/deno` as a project dependency auto-includes it in the
|
|
7
|
+
* whole app when root `tsconfig` omits `"types"`, which pollutes React/Next.
|
|
8
|
+
*/
|
|
9
|
+
/** Ambient Deno types written into `functions/deno.d.ts`. */
|
|
10
|
+
export declare function functionsDenoAmbientSource(): string;
|
|
11
|
+
/** `functions/tsconfig.json` — owns function files so the app config can exclude them. */
|
|
12
|
+
export declare function functionsTsConfigSource(): string;
|
|
13
|
+
export type FunctionsDenoTypesResult = {
|
|
14
|
+
wroteDenoDts: boolean;
|
|
15
|
+
wroteTsconfig: boolean;
|
|
16
|
+
rootExclude: "updated" | "already" | "missing" | "manual";
|
|
17
|
+
hints: string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Ensure `functions/deno.d.ts` + `functions/tsconfig.json` exist, and try to
|
|
21
|
+
* exclude `functions` from the project-root `tsconfig.json`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensureFunctionsDenoTypes(projectRoot: string, functionsDir: string): FunctionsDenoTypesResult;
|
|
24
|
+
//# sourceMappingURL=functions-deno-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions-deno-types.d.ts","sourceRoot":"","sources":["../src/functions-deno-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,6DAA6D;AAC7D,wBAAgB,0BAA0B,IAAI,MAAM,CAsDnD;AAED,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,MAAM,CAiBhD;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,OAAO,CAAA;IACrB,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;IACzD,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,wBAAwB,CAiC1B"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold Deno IDE types for edge functions so tsserver / Cursor stop
|
|
3
|
+
* reporting "Cannot find name 'Deno'" under the project root tsconfig.
|
|
4
|
+
*
|
|
5
|
+
* We ship a curated ambient declaration (not `@types/deno` at the app root):
|
|
6
|
+
* installing `@types/deno` as a project dependency auto-includes it in the
|
|
7
|
+
* whole app when root `tsconfig` omits `"types"`, which pollutes React/Next.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import { join, relative } from "node:path";
|
|
11
|
+
import ts from "typescript";
|
|
12
|
+
const MARKER = "Supatype edge functions (IDE / tsserver)";
|
|
13
|
+
/** Ambient Deno types written into `functions/deno.d.ts`. */
|
|
14
|
+
export function functionsDenoAmbientSource() {
|
|
15
|
+
return `/**
|
|
16
|
+
* ${MARKER}.
|
|
17
|
+
* Runtime is Deno; this file covers APIs edge functions commonly use.
|
|
18
|
+
* Do not add \`@types/deno\` to the app package.json — it leaks into the
|
|
19
|
+
* whole project when root tsconfig has no \`"types"\` field.
|
|
20
|
+
*
|
|
21
|
+
* Optional: for the full Deno API, install the Deno VS Code/Cursor extension
|
|
22
|
+
* and open \`functions/\` as a Deno-enabled workspace folder.
|
|
23
|
+
*/
|
|
24
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
25
|
+
|
|
26
|
+
interface DenoEnv {
|
|
27
|
+
/** Returns the value of the environment variable, or \`undefined\` if unset. */
|
|
28
|
+
get(key: string): string | undefined
|
|
29
|
+
set(key: string, value: string): void
|
|
30
|
+
delete(key: string): void
|
|
31
|
+
has(key: string): boolean
|
|
32
|
+
toObject(): { [key: string]: string }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare namespace Deno {
|
|
36
|
+
const env: DenoEnv
|
|
37
|
+
|
|
38
|
+
namespace errors {
|
|
39
|
+
class NotFound extends Error {}
|
|
40
|
+
class PermissionDenied extends Error {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface DirEntry {
|
|
44
|
+
name: string
|
|
45
|
+
isFile: boolean
|
|
46
|
+
isDirectory: boolean
|
|
47
|
+
isSymlink: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface FileInfo {
|
|
51
|
+
isFile: boolean
|
|
52
|
+
isDirectory: boolean
|
|
53
|
+
isSymlink: boolean
|
|
54
|
+
size: number
|
|
55
|
+
mtime: Date | null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readDir(path: string): AsyncIterable<DirEntry>
|
|
59
|
+
function readTextFile(path: string): Promise<string>
|
|
60
|
+
function stat(path: string): Promise<FileInfo>
|
|
61
|
+
|
|
62
|
+
function serve(
|
|
63
|
+
options: { port: number; hostname?: string; onListen?: (params: { hostname: string; port: number }) => void },
|
|
64
|
+
handler: (req: Request) => Response | Promise<Response>,
|
|
65
|
+
): void
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
/** `functions/tsconfig.json` — owns function files so the app config can exclude them. */
|
|
70
|
+
export function functionsTsConfigSource() {
|
|
71
|
+
return `{
|
|
72
|
+
"compilerOptions": {
|
|
73
|
+
"target": "ES2022",
|
|
74
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
75
|
+
"strict": true,
|
|
76
|
+
"noEmit": true,
|
|
77
|
+
"module": "ESNext",
|
|
78
|
+
"moduleResolution": "bundler",
|
|
79
|
+
"isolatedModules": true,
|
|
80
|
+
"skipLibCheck": true,
|
|
81
|
+
"allowImportingTsExtensions": true,
|
|
82
|
+
"types": []
|
|
83
|
+
},
|
|
84
|
+
"include": ["./**/*.ts", "./deno.d.ts"]
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Ensure `functions/deno.d.ts` + `functions/tsconfig.json` exist, and try to
|
|
90
|
+
* exclude `functions` from the project-root `tsconfig.json`.
|
|
91
|
+
*/
|
|
92
|
+
export function ensureFunctionsDenoTypes(projectRoot, functionsDir) {
|
|
93
|
+
mkdirSync(functionsDir, { recursive: true });
|
|
94
|
+
const denoDtsPath = join(functionsDir, "deno.d.ts");
|
|
95
|
+
const tsconfigPath = join(functionsDir, "tsconfig.json");
|
|
96
|
+
let wroteDenoDts = false;
|
|
97
|
+
if (!existsSync(denoDtsPath)) {
|
|
98
|
+
writeFileSync(denoDtsPath, functionsDenoAmbientSource(), "utf8");
|
|
99
|
+
wroteDenoDts = true;
|
|
100
|
+
}
|
|
101
|
+
let wroteTsconfig = false;
|
|
102
|
+
if (!existsSync(tsconfigPath)) {
|
|
103
|
+
writeFileSync(tsconfigPath, functionsTsConfigSource(), "utf8");
|
|
104
|
+
wroteTsconfig = true;
|
|
105
|
+
}
|
|
106
|
+
const rootExclude = ensureRootTsconfigExcludesFunctions(projectRoot);
|
|
107
|
+
const hints = [];
|
|
108
|
+
const fnRel = toPosix(relative(projectRoot, functionsDir)) || "functions";
|
|
109
|
+
if (rootExclude === "manual") {
|
|
110
|
+
hints.push(`Add "functions" to the root tsconfig.json "exclude" array so the app checker skips ${fnRel}/.`);
|
|
111
|
+
}
|
|
112
|
+
else if (rootExclude === "missing") {
|
|
113
|
+
hints.push(`If you add a root tsconfig.json, exclude "${fnRel}" (or "functions") so app typecheck stays separate.`);
|
|
114
|
+
}
|
|
115
|
+
return { wroteDenoDts, wroteTsconfig, rootExclude, hints };
|
|
116
|
+
}
|
|
117
|
+
function ensureRootTsconfigExcludesFunctions(projectRoot) {
|
|
118
|
+
const path = join(projectRoot, "tsconfig.json");
|
|
119
|
+
if (!existsSync(path))
|
|
120
|
+
return "missing";
|
|
121
|
+
const raw = readFileSync(path, "utf8");
|
|
122
|
+
const parsed = ts.parseConfigFileTextToJson(path, raw);
|
|
123
|
+
if (parsed.error || parsed.config === undefined || typeof parsed.config !== "object") {
|
|
124
|
+
return "manual";
|
|
125
|
+
}
|
|
126
|
+
const cfg = parsed.config;
|
|
127
|
+
const exclude = Array.isArray(cfg.exclude) ? cfg.exclude.map(String) : [];
|
|
128
|
+
if (exclude.some(isFunctionsExcludeEntry))
|
|
129
|
+
return "already";
|
|
130
|
+
try {
|
|
131
|
+
const asJson = JSON.parse(raw);
|
|
132
|
+
asJson.exclude = [...(Array.isArray(asJson.exclude) ? asJson.exclude : []), "functions"];
|
|
133
|
+
writeFileSync(path, `${JSON.stringify(asJson, null, 2)}\n`, "utf8");
|
|
134
|
+
return "updated";
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// JSONC / trailing commas — try a conservative string splice into an existing exclude array.
|
|
138
|
+
if (/"exclude"\s*:\s*\[/.test(raw)) {
|
|
139
|
+
const next = raw.replace(/("exclude"\s*:\s*\[)/, `$1\n "functions",`);
|
|
140
|
+
if (next !== raw) {
|
|
141
|
+
writeFileSync(path, next, "utf8");
|
|
142
|
+
return "updated";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return "manual";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isFunctionsExcludeEntry(entry) {
|
|
149
|
+
const n = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
150
|
+
return n === "functions" || n === "functions/**" || n.startsWith("functions/");
|
|
151
|
+
}
|
|
152
|
+
function toPosix(path) {
|
|
153
|
+
return path.replace(/\\/g, "/");
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=functions-deno-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions-deno-types.js","sourceRoot":"","sources":["../src/functions-deno-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ManifestHookEntry } from "./model-hooks.js";
|
|
2
|
+
/** The generated adapter, flattened. `_`-prefixed, so the worker never routes to it. */
|
|
3
|
+
export declare const ADAPTER_KEY = "_supatype-hooks.ts";
|
|
4
|
+
export interface HookUpload {
|
|
5
|
+
/** Handler name → its source, flattened and import-rewritten. */
|
|
6
|
+
handlers: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
source: string;
|
|
9
|
+
}>;
|
|
10
|
+
/** table → event → config, exactly as the manifest would carry it. */
|
|
11
|
+
map: Record<string, Record<string, ManifestHookEntry>>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read the hooks a schema actually names, ready to upload.
|
|
15
|
+
*
|
|
16
|
+
* **Named-only**, not every directory under `hooks/`: an unnamed handler would sit on the worker
|
|
17
|
+
* holding the service-role key with nothing able to call it. Uploading it buys nothing and widens what
|
|
18
|
+
* runs with that credential.
|
|
19
|
+
*
|
|
20
|
+
* Throws with a message meant for a terminal when a handler cannot be flattened faithfully — the
|
|
21
|
+
* alternative is a deploy that succeeds and a hook that fails to import, which reads to the caller as
|
|
22
|
+
* the table's writes being broken for no visible reason.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readHookUpload(cwd: string, hooksDir: string, ast: unknown): HookUpload | null;
|
|
25
|
+
/** The generated adapter as a handler-shaped entry, so it travels with them. */
|
|
26
|
+
export declare function adapterEntry(module: string): {
|
|
27
|
+
name: string;
|
|
28
|
+
source: string;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=hook-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-upload.d.ts","sourceRoot":"","sources":["../src/hook-upload.ts"],"names":[],"mappings":"AAWA,OAAO,EAAgC,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEvF,wFAAwF;AACxF,eAAO,MAAM,WAAW,uBAAuB,CAAA;AAK/C,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjD,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAA;CACvD;AA0BD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAwC7F;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAE7E"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package a project's hooks for a managed stack.
|
|
3
|
+
*
|
|
4
|
+
* Self-hosted stacks need none of this: the worker mounts the project directory, so `hooks/` is already
|
|
5
|
+
* where it looks. A managed project has no directory — its worker reads a ConfigMap, whose keys may only
|
|
6
|
+
* contain alphanumerics, `-`, `_` and `.`. So the tree is flattened here, where it can be checked and
|
|
7
|
+
* explained, rather than in the deploy route where a dropped file would be silent.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
10
|
+
import { join, relative } from "node:path";
|
|
11
|
+
import { declaredHooks, manifestHooks } from "./model-hooks.js";
|
|
12
|
+
/** The generated adapter, flattened. `_`-prefixed, so the worker never routes to it. */
|
|
13
|
+
export const ADAPTER_KEY = "_supatype-hooks.ts";
|
|
14
|
+
/** What the handlers import locally, and what has to be rewritten to reach the flattened name. */
|
|
15
|
+
const ADAPTER_IMPORT = "../_supatype/hooks.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite the one import a hook handler is generated to make.
|
|
18
|
+
*
|
|
19
|
+
* `../_supatype/hooks.ts` is a path that cannot survive flattening, and it is the specifier our own
|
|
20
|
+
* scaffold writes — so it is rewritten rather than refused. Any *other* relative import is refused
|
|
21
|
+
* below: it would resolve to nothing on the worker, and a hook that fails to import is a hooked table
|
|
22
|
+
* whose writes all start failing.
|
|
23
|
+
*/
|
|
24
|
+
function rewriteAdapterImport(source) {
|
|
25
|
+
return source.split(ADAPTER_IMPORT).join(`./${ADAPTER_KEY}`);
|
|
26
|
+
}
|
|
27
|
+
/** Relative specifiers a flattened layout cannot honour, so the caller can refuse with a real reason. */
|
|
28
|
+
function unsupportedImports(source) {
|
|
29
|
+
const found = new Set();
|
|
30
|
+
// Matches `from "…"` and `import("…")`, which is every form that resolves against the file's own path.
|
|
31
|
+
for (const match of source.matchAll(/(?:from|import)\s*\(?\s*["'](\.[^"']*)["']/g)) {
|
|
32
|
+
const specifier = match[1];
|
|
33
|
+
if (specifier === ADAPTER_IMPORT || specifier === `./${ADAPTER_KEY}`)
|
|
34
|
+
continue;
|
|
35
|
+
found.add(specifier);
|
|
36
|
+
}
|
|
37
|
+
return [...found].sort();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read the hooks a schema actually names, ready to upload.
|
|
41
|
+
*
|
|
42
|
+
* **Named-only**, not every directory under `hooks/`: an unnamed handler would sit on the worker
|
|
43
|
+
* holding the service-role key with nothing able to call it. Uploading it buys nothing and widens what
|
|
44
|
+
* runs with that credential.
|
|
45
|
+
*
|
|
46
|
+
* Throws with a message meant for a terminal when a handler cannot be flattened faithfully — the
|
|
47
|
+
* alternative is a deploy that succeeds and a hook that fails to import, which reads to the caller as
|
|
48
|
+
* the table's writes being broken for no visible reason.
|
|
49
|
+
*/
|
|
50
|
+
export function readHookUpload(cwd, hooksDir, ast) {
|
|
51
|
+
const declared = declaredHooks(ast);
|
|
52
|
+
if (declared.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const names = [...new Set(declared.map((hook) => hook.function))].sort();
|
|
55
|
+
const where = relative(cwd, hooksDir) || hooksDir;
|
|
56
|
+
const handlers = [];
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const dir = join(hooksDir, name);
|
|
59
|
+
const entry = join(dir, "index.ts");
|
|
60
|
+
if (!existsSync(entry)) {
|
|
61
|
+
throw new Error(`Hook "${name}" has no ${where}/${name}/index.ts`);
|
|
62
|
+
}
|
|
63
|
+
const extra = readdirSync(dir)
|
|
64
|
+
.filter((file) => file !== "index.ts")
|
|
65
|
+
.filter((file) => statSync(join(dir, file)).isFile() || !file.startsWith("."));
|
|
66
|
+
if (extra.length > 0) {
|
|
67
|
+
throw new Error(`Hook "${name}" has more than one file (${extra.join(", ")}).\n` +
|
|
68
|
+
`A managed stack serves each hook as a single module today, so the others would not be ` +
|
|
69
|
+
`uploaded and the hook would fail to import.\nMove what it needs into index.ts, or run this ` +
|
|
70
|
+
`hook on a self-hosted stack, which mounts the directory as it is.`);
|
|
71
|
+
}
|
|
72
|
+
const source = readFileSync(entry, "utf8");
|
|
73
|
+
const unsupported = unsupportedImports(source);
|
|
74
|
+
if (unsupported.length > 0) {
|
|
75
|
+
throw new Error(`Hook "${name}" imports ${unsupported.join(", ")}, which a managed stack cannot resolve.\n` +
|
|
76
|
+
`Only the generated ${ADAPTER_IMPORT} survives upload; bare specifiers and URLs are fine.`);
|
|
77
|
+
}
|
|
78
|
+
handlers.push({ name, source: rewriteAdapterImport(source) });
|
|
79
|
+
}
|
|
80
|
+
return { handlers, map: manifestHooks(ast) };
|
|
81
|
+
}
|
|
82
|
+
/** The generated adapter as a handler-shaped entry, so it travels with them. */
|
|
83
|
+
export function adapterEntry(module) {
|
|
84
|
+
return { name: ADAPTER_KEY.replace(/\.ts$/, ""), source: module };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=hook-upload.js.map
|