@rehearsal-db/core 0.1.0-beta.13 → 0.1.0-beta.15
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/CHANGELOG.md +93 -0
- package/README.md +4 -1
- package/dist/package.json +100 -0
- package/dist/scripts/install/create_root_config.d.mts +3 -0
- package/dist/scripts/install/create_root_config.mjs +13 -0
- package/dist/scripts/runtime/manage_database.d.mts +6 -0
- package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
- package/dist/src/application/session.d.mts +96 -0
- package/dist/src/application/session.mjs +380 -0
- package/dist/src/baseline/artifact.d.mts +119 -0
- package/dist/src/baseline/artifact.mjs +522 -0
- package/dist/src/baseline/builder.d.mts +19 -0
- package/dist/src/baseline/builder.mjs +142 -0
- package/dist/src/baseline/input_discovery.d.mts +13 -0
- package/dist/src/baseline/input_discovery.mjs +149 -0
- package/dist/src/baseline/policy_review.d.mts +56 -0
- package/dist/src/baseline/policy_review.mjs +73 -0
- package/dist/src/baseline/preparation.d.mts +63 -0
- package/dist/src/baseline/preparation.mjs +295 -0
- package/dist/src/baseline/privacy_engine.d.mts +162 -0
- package/dist/src/baseline/privacy_engine.mjs +1162 -0
- package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
- package/dist/src/baseline/privacy_structured_json.mjs +464 -0
- package/dist/src/baseline/sanitization_policy.d.mts +69 -0
- package/dist/src/baseline/sanitization_policy.mjs +227 -0
- package/dist/src/baseline/schema_snapshot.d.mts +19 -0
- package/dist/src/baseline/schema_snapshot.mjs +79 -0
- package/dist/src/cli/arguments.d.mts +43 -0
- package/dist/src/cli/arguments.mjs +135 -0
- package/dist/src/cli/guided.d.mts +51 -0
- package/dist/src/cli/guided.mjs +713 -0
- package/dist/src/cli/init_command.d.mts +21 -0
- package/dist/src/cli/init_command.mjs +90 -0
- package/dist/src/cli/project_commands.d.mts +157 -0
- package/dist/src/cli/project_commands.mjs +183 -0
- package/dist/src/cli/rehearsal.d.mts +7 -0
- package/dist/src/cli/rehearsal.mjs +706 -0
- package/dist/src/cli/renderers.d.mts +299 -0
- package/dist/src/cli/renderers.mjs +554 -0
- package/dist/src/cli/runtime_commands.d.mts +265 -0
- package/dist/src/cli/runtime_commands.mjs +599 -0
- package/dist/src/cli/source_commands.d.mts +432 -0
- package/dist/src/cli/source_commands.mjs +299 -0
- package/dist/src/cli/terminal.d.mts +59 -0
- package/dist/src/cli/terminal.mjs +230 -0
- package/dist/src/identity/claim.d.mts +144 -0
- package/dist/src/identity/claim.mjs +947 -0
- package/dist/src/identity/provider_configuration.d.mts +37 -0
- package/dist/src/identity/provider_configuration.mjs +132 -0
- package/dist/src/identity/storage.d.mts +21 -0
- package/dist/src/identity/storage.mjs +122 -0
- package/dist/src/index.d.mts +4 -0
- package/dist/src/index.mjs +3 -0
- package/dist/src/project/configuration.d.mts +181 -0
- package/dist/src/project/configuration.mjs +958 -0
- package/dist/src/project/install_scaffold.d.mts +42 -0
- package/dist/src/project/install_scaffold.mjs +263 -0
- package/dist/src/project/setup.d.mts +87 -0
- package/dist/src/project/setup.mjs +414 -0
- package/dist/src/project/support_report.d.mts +88 -0
- package/dist/src/project/support_report.mjs +83 -0
- package/dist/src/runtime/cleanup.d.mts +85 -0
- package/dist/src/runtime/cleanup.mjs +311 -0
- package/dist/src/runtime/migration_history.d.mts +51 -0
- package/dist/src/runtime/migration_history.mjs +170 -0
- package/dist/src/runtime/plan.d.mts +134 -0
- package/dist/src/runtime/plan.mjs +636 -0
- package/dist/src/runtime/policy.d.mts +66 -0
- package/dist/src/runtime/policy.mjs +369 -0
- package/dist/src/runtime/restore.d.mts +22 -0
- package/dist/src/runtime/restore.mjs +276 -0
- package/dist/src/runtime/service_environment.d.mts +11 -0
- package/dist/src/runtime/service_environment.mjs +58 -0
- package/dist/src/runtime/topology.d.mts +51 -0
- package/dist/src/runtime/topology.mjs +128 -0
- package/dist/src/shared/diagnostics.d.mts +97 -0
- package/dist/src/shared/diagnostics.mjs +170 -0
- package/dist/src/shared/human_output.d.mts +2 -0
- package/{src → dist/src}/shared/human_output.mjs +1 -3
- package/dist/src/shared/node_runtime.d.mts +14 -0
- package/dist/src/shared/node_runtime.mjs +25 -0
- package/dist/src/shared/operation_guard.d.mts +22 -0
- package/dist/src/shared/operation_guard.mjs +150 -0
- package/dist/src/shared/process_environment.d.mts +15 -0
- package/dist/src/shared/process_environment.mjs +50 -0
- package/dist/src/source/access.d.mts +165 -0
- package/dist/src/source/access.mjs +602 -0
- package/dist/src/source/asset_transfer.d.mts +27 -0
- package/dist/src/source/asset_transfer.mjs +202 -0
- package/dist/src/source/baseline.d.mts +66 -0
- package/dist/src/source/baseline.mjs +372 -0
- package/dist/src/source/external_reader.d.mts +42 -0
- package/dist/src/source/external_reader.mjs +278 -0
- package/dist/src/source/postgresql_access.d.mts +86 -0
- package/dist/src/source/postgresql_access.mjs +454 -0
- package/dist/src/source/privacy_paths.d.mts +10 -0
- package/dist/src/source/privacy_paths.mjs +27 -0
- package/dist/src/targets/driver.d.mts +2 -0
- package/dist/src/targets/driver.mjs +13 -0
- package/dist/src/targets/postgresql.d.mts +5 -0
- package/dist/src/targets/postgresql.mjs +641 -0
- package/dist/src/targets/supabase.d.mts +8 -0
- package/dist/src/targets/supabase.mjs +659 -0
- package/dist/src/targets/supabase_environment.d.mts +49 -0
- package/dist/src/targets/supabase_environment.mjs +162 -0
- package/dist/src/targets/target.d.mts +33 -0
- package/dist/src/targets/target.mjs +39 -0
- package/docs/README.md +1 -0
- package/docs/architecture.md +17 -9
- package/docs/configuration.md +49 -8
- package/docs/getting-started.md +28 -0
- package/docs/index.md +1 -0
- package/docs/production-source.md +43 -7
- package/docs/releasing.md +5 -3
- package/docs/roadmap.md +3 -2
- package/docs/sanitization.md +415 -14
- package/docs/standalone-workflow.md +12 -6
- package/docs/troubleshooting.md +5 -1
- package/docs/tutorial.md +5 -0
- package/docs/typescript.md +87 -0
- package/package.json +35 -29
- package/scripts/install/create_root_config.mjs +0 -36
- package/src/README.md +0 -17
- package/src/application/session.mjs +0 -435
- package/src/baseline/artifact.mjs +0 -684
- package/src/baseline/builder.mjs +0 -164
- package/src/baseline/input_discovery.mjs +0 -155
- package/src/baseline/policy_review.mjs +0 -92
- package/src/baseline/preparation.mjs +0 -323
- package/src/baseline/privacy_engine.mjs +0 -952
- package/src/baseline/sanitization_policy.mjs +0 -299
- package/src/baseline/schema_snapshot.mjs +0 -113
- package/src/cli/arguments.mjs +0 -126
- package/src/cli/guided.mjs +0 -814
- package/src/cli/init_command.mjs +0 -94
- package/src/cli/rehearsal.mjs +0 -970
- package/src/cli/renderers.mjs +0 -626
- package/src/cli/runtime_commands.mjs +0 -707
- package/src/cli/source_commands.mjs +0 -347
- package/src/cli/terminal.mjs +0 -275
- package/src/identity/claim.mjs +0 -1163
- package/src/identity/provider_configuration.mjs +0 -177
- package/src/identity/storage.mjs +0 -165
- package/src/project/configuration.mjs +0 -1337
- package/src/project/install_scaffold.mjs +0 -291
- package/src/project/setup.mjs +0 -466
- package/src/project/support_report.mjs +0 -105
- package/src/runtime/cleanup.mjs +0 -381
- package/src/runtime/migration_history.mjs +0 -220
- package/src/runtime/plan.mjs +0 -722
- package/src/runtime/policy.mjs +0 -438
- package/src/runtime/restore.mjs +0 -337
- package/src/runtime/service_environment.mjs +0 -82
- package/src/runtime/topology.mjs +0 -177
- package/src/shared/diagnostics.mjs +0 -207
- package/src/shared/node_runtime.mjs +0 -31
- package/src/shared/operation_guard.mjs +0 -162
- package/src/shared/process_environment.mjs +0 -68
- package/src/source/access.mjs +0 -753
- package/src/source/asset_transfer.mjs +0 -207
- package/src/source/baseline.mjs +0 -482
- package/src/source/external_reader.mjs +0 -255
- package/src/source/postgresql_access.mjs +0 -581
- package/src/source/privacy_paths.mjs +0 -38
- package/src/targets/postgresql.mjs +0 -808
- package/src/targets/supabase.mjs +0 -840
- package/src/targets/supabase_environment.mjs +0 -229
- package/src/targets/target.mjs +0 -66
- /package/{src/project → types}/configuration.d.mts +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Render reviewed local OAuth declarations into the disposable Supabase runtime. */
|
|
2
|
+
export interface SupabaseAuthenticationProvider {
|
|
3
|
+
name: "google" | "github";
|
|
4
|
+
clientIdEnvironmentVariable: string;
|
|
5
|
+
clientSecretEnvironmentVariable: string;
|
|
6
|
+
skipNonceCheck?: boolean;
|
|
7
|
+
emailOptional?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SupabaseAuthentication {
|
|
10
|
+
providers: readonly SupabaseAuthenticationProvider[];
|
|
11
|
+
}
|
|
12
|
+
interface TomlSection {
|
|
13
|
+
name: string;
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
}
|
|
17
|
+
interface ProviderReplacementSection extends TomlSection {
|
|
18
|
+
provider: string;
|
|
19
|
+
}
|
|
20
|
+
interface ProviderTransition {
|
|
21
|
+
readonly replacedProviders: readonly string[];
|
|
22
|
+
readonly replacementSections: readonly ProviderReplacementSection[];
|
|
23
|
+
}
|
|
24
|
+
export declare const inspectSupabaseAuthenticationProviderTransition: ({ source, authentication, }: {
|
|
25
|
+
source: string;
|
|
26
|
+
authentication: SupabaseAuthentication | null;
|
|
27
|
+
}) => Readonly<ProviderTransition>;
|
|
28
|
+
export declare const renderSupabaseAuthenticationProviders: ({ authentication, apiPort, }: {
|
|
29
|
+
authentication: SupabaseAuthentication | null;
|
|
30
|
+
apiPort: number;
|
|
31
|
+
}) => string;
|
|
32
|
+
export declare const applySupabaseAuthenticationProviders: ({ source, authentication, apiPort, }: {
|
|
33
|
+
source: string;
|
|
34
|
+
authentication: SupabaseAuthentication | null;
|
|
35
|
+
apiPort: number;
|
|
36
|
+
}) => string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** Render reviewed local OAuth declarations into the disposable Supabase runtime. */
|
|
2
|
+
const escapePattern = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
3
|
+
const LEGACY_PROVIDER_KEYS = new Set([
|
|
4
|
+
"client_id",
|
|
5
|
+
"email_optional",
|
|
6
|
+
"enabled",
|
|
7
|
+
"redirect_uri",
|
|
8
|
+
"secret",
|
|
9
|
+
"skip_nonce_check",
|
|
10
|
+
]);
|
|
11
|
+
const SAFE_BOOLEAN_PROVIDER_KEYS = new Set([
|
|
12
|
+
"email_optional",
|
|
13
|
+
"skip_nonce_check",
|
|
14
|
+
]);
|
|
15
|
+
const readTomlSections = (source) => {
|
|
16
|
+
const lines = source.split("\n");
|
|
17
|
+
const sections = [];
|
|
18
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
19
|
+
const match = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/u.exec(lines[index]);
|
|
20
|
+
if (!match)
|
|
21
|
+
continue;
|
|
22
|
+
if (sections.length)
|
|
23
|
+
sections.at(-1).end = index;
|
|
24
|
+
sections.push({ name: match[1], start: index, end: lines.length });
|
|
25
|
+
}
|
|
26
|
+
return { lines, sections };
|
|
27
|
+
};
|
|
28
|
+
const validateLegacyProviderSection = ({ lines, section, provider, }) => {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const line of lines.slice(section.start + 1, section.end)) {
|
|
31
|
+
if (!line.trim() || /^\s*#/u.test(line))
|
|
32
|
+
continue;
|
|
33
|
+
const assignment = /^\s*([A-Za-z0-9_]+)\s*=/u.exec(line);
|
|
34
|
+
if (!assignment || !LEGACY_PROVIDER_KEYS.has(assignment[1])) {
|
|
35
|
+
throw new Error(`The local Supabase template auth.external.${provider.name} section contains an unsupported setting. Rehearsal left the tracked template unchanged.`);
|
|
36
|
+
}
|
|
37
|
+
const key = assignment[1];
|
|
38
|
+
if (seen.has(key)) {
|
|
39
|
+
throw new Error(`The local Supabase template auth.external.${provider.name} section contains duplicate ${key} settings.`);
|
|
40
|
+
}
|
|
41
|
+
seen.add(key);
|
|
42
|
+
if (SAFE_BOOLEAN_PROVIDER_KEYS.has(key) &&
|
|
43
|
+
!new RegExp(`^\\s*${escapePattern(key)}\\s*=\\s*${key === "skip_nonce_check"
|
|
44
|
+
? (provider.skipNonceCheck ?? false)
|
|
45
|
+
: (provider.emailOptional ?? false)}\\s*(?:#.*)?$`, "u").test(line)) {
|
|
46
|
+
throw new Error(`The local Supabase template auth.external.${provider.name}.${key} setting must match its explicit declarative authentication option before Rehearsal can replace the legacy provider in its disposable runtime copy.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export const inspectSupabaseAuthenticationProviderTransition = ({ source, authentication, }) => {
|
|
51
|
+
if (!authentication)
|
|
52
|
+
return Object.freeze({ replacedProviders: [], replacementSections: [] });
|
|
53
|
+
const { lines, sections } = readTomlSections(source);
|
|
54
|
+
const replacementSections = [];
|
|
55
|
+
for (const provider of authentication.providers) {
|
|
56
|
+
const sectionName = `auth.external.${provider.name}`;
|
|
57
|
+
const nested = sections.filter((section) => section.name.startsWith(`${sectionName}.`));
|
|
58
|
+
if (nested.length) {
|
|
59
|
+
throw new Error(`The local Supabase template contains unsupported nested ${sectionName} configuration. Rehearsal left the tracked template unchanged.`);
|
|
60
|
+
}
|
|
61
|
+
const matching = sections.filter((section) => section.name === sectionName);
|
|
62
|
+
if (matching.length > 1) {
|
|
63
|
+
throw new Error(`The local Supabase template declares ${sectionName} more than once.`);
|
|
64
|
+
}
|
|
65
|
+
if (matching.length === 1) {
|
|
66
|
+
validateLegacyProviderSection({
|
|
67
|
+
lines,
|
|
68
|
+
section: matching[0],
|
|
69
|
+
provider,
|
|
70
|
+
});
|
|
71
|
+
replacementSections.push({ ...matching[0], provider: provider.name });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
replacedProviders: Object.freeze(replacementSections.map(({ provider }) => provider)),
|
|
76
|
+
replacementSections: Object.freeze(replacementSections),
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
export const renderSupabaseAuthenticationProviders = ({ authentication, apiPort, }) => {
|
|
80
|
+
if (!authentication)
|
|
81
|
+
return "";
|
|
82
|
+
return authentication.providers
|
|
83
|
+
.map((provider) => `# Generated by Rehearsal from config.supabase.authentication.
|
|
84
|
+
[auth.external.${provider.name}]
|
|
85
|
+
enabled = true
|
|
86
|
+
client_id = "env(${provider.clientIdEnvironmentVariable})"
|
|
87
|
+
secret = "env(${provider.clientSecretEnvironmentVariable})"
|
|
88
|
+
redirect_uri = "http://127.0.0.1:${apiPort}/auth/v1/callback"
|
|
89
|
+
${provider.skipNonceCheck ? "skip_nonce_check = true\n" : ""}${provider.emailOptional ? "email_optional = true\n" : ""}
|
|
90
|
+
`)
|
|
91
|
+
.join("\n");
|
|
92
|
+
};
|
|
93
|
+
const enableLocalSignup = (source) => {
|
|
94
|
+
const lines = source.split("\n");
|
|
95
|
+
let inAuth = false;
|
|
96
|
+
let authSections = 0;
|
|
97
|
+
let signupSettings = 0;
|
|
98
|
+
const updated = lines.map((line) => {
|
|
99
|
+
const section = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/u.exec(line);
|
|
100
|
+
if (section) {
|
|
101
|
+
inAuth = section[1] === "auth";
|
|
102
|
+
if (inAuth)
|
|
103
|
+
authSections += 1;
|
|
104
|
+
return line;
|
|
105
|
+
}
|
|
106
|
+
if (!inAuth || !/^\s*enable_signup\s*=/u.test(line))
|
|
107
|
+
return line;
|
|
108
|
+
signupSettings += 1;
|
|
109
|
+
const comment = line.match(/\s+#.*$/u)?.[0] ?? "";
|
|
110
|
+
return `enable_signup = true${comment}`;
|
|
111
|
+
});
|
|
112
|
+
if (authSections !== 1 || signupSettings !== 1) {
|
|
113
|
+
throw new Error("The local Supabase template must declare exactly one [auth] section with one enable_signup setting before Rehearsal can configure OAuth.");
|
|
114
|
+
}
|
|
115
|
+
return updated.join("\n");
|
|
116
|
+
};
|
|
117
|
+
export const applySupabaseAuthenticationProviders = ({ source, authentication, apiPort, }) => {
|
|
118
|
+
if (!authentication)
|
|
119
|
+
return source;
|
|
120
|
+
const transition = inspectSupabaseAuthenticationProviderTransition({
|
|
121
|
+
source,
|
|
122
|
+
authentication,
|
|
123
|
+
});
|
|
124
|
+
const { lines } = readTomlSections(source);
|
|
125
|
+
const replacedIndexes = new Set(transition.replacementSections.flatMap((section) => Array.from({ length: section.end - section.start }, (_, offset) => section.start + offset)));
|
|
126
|
+
const transitionedSource = lines
|
|
127
|
+
.filter((_, index) => !replacedIndexes.has(index))
|
|
128
|
+
.join("\n");
|
|
129
|
+
const runtimeSource = enableLocalSignup(transitionedSource);
|
|
130
|
+
const separator = runtimeSource.endsWith("\n") ? "\n" : "\n\n";
|
|
131
|
+
return `${runtimeSource}${separator}${renderSupabaseAuthenticationProviders({ authentication, apiPort })}`;
|
|
132
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Copy and verify local Supabase Storage objects during an identity claim. */
|
|
2
|
+
export interface StorageObjectReceipt {
|
|
3
|
+
readonly bytes: number;
|
|
4
|
+
readonly sha256: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SupabaseStorageTransfer {
|
|
7
|
+
copyAndVerify(input: {
|
|
8
|
+
bucket: string;
|
|
9
|
+
source: string;
|
|
10
|
+
destination: string;
|
|
11
|
+
}): Promise<StorageObjectReceipt>;
|
|
12
|
+
remove(input: {
|
|
13
|
+
bucket: string;
|
|
14
|
+
paths: readonly string[];
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare const createSupabaseStorageTransfer: ({ url, serviceRoleKey, fetchImpl, }: {
|
|
18
|
+
url: string;
|
|
19
|
+
serviceRoleKey: string;
|
|
20
|
+
fetchImpl?: typeof fetch;
|
|
21
|
+
}) => SupabaseStorageTransfer;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Copy and verify local Supabase Storage objects during an identity claim. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { assertLoopbackUrl } from "../shared/process_environment.mjs";
|
|
4
|
+
const BUCKET = /^[a-z0-9][a-z0-9.-]{0,99}$/u;
|
|
5
|
+
const MAXIMUM_VERIFICATION_BYTES = 100 * 1024 * 1024;
|
|
6
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
7
|
+
const objectPath = (value) => {
|
|
8
|
+
if (typeof value !== "string" ||
|
|
9
|
+
!value ||
|
|
10
|
+
value.startsWith("/") ||
|
|
11
|
+
value.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
12
|
+
throw new Error("A Storage identity-claim path is unsafe.");
|
|
13
|
+
}
|
|
14
|
+
return value.split("/").map(encodeURIComponent).join("/");
|
|
15
|
+
};
|
|
16
|
+
const readVerifiedObject = async ({ fetchImpl, baseUrl, headers, bucket, path, }) => {
|
|
17
|
+
const response = await fetchImpl(`${baseUrl}/storage/v1/object/${encodeURIComponent(bucket)}/${objectPath(path)}?rehearsal=${Date.now()}`, {
|
|
18
|
+
headers,
|
|
19
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw new Error(`Supabase Storage could not read an identity-claim object (HTTP ${response.status}).`);
|
|
23
|
+
}
|
|
24
|
+
const declaredBytes = Number(response.headers.get("content-length"));
|
|
25
|
+
if (Number.isFinite(declaredBytes) &&
|
|
26
|
+
declaredBytes > MAXIMUM_VERIFICATION_BYTES) {
|
|
27
|
+
throw new Error("A Storage identity-claim object exceeds the 100 MiB verification limit.");
|
|
28
|
+
}
|
|
29
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
30
|
+
if (bytes.length > MAXIMUM_VERIFICATION_BYTES) {
|
|
31
|
+
throw new Error("A Storage identity-claim object exceeds the 100 MiB verification limit.");
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
bytes: bytes.length,
|
|
35
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
export const createSupabaseStorageTransfer = ({ url, serviceRoleKey, fetchImpl = globalThis.fetch, }) => {
|
|
39
|
+
assertLoopbackUrl("Rehearsal Supabase Storage", url);
|
|
40
|
+
if (typeof serviceRoleKey !== "string" || !serviceRoleKey) {
|
|
41
|
+
throw new Error("The local runtime environment has no Storage service key.");
|
|
42
|
+
}
|
|
43
|
+
if (typeof fetchImpl !== "function") {
|
|
44
|
+
throw new Error("A Fetch implementation is required for Storage transfer.");
|
|
45
|
+
}
|
|
46
|
+
const baseUrl = url.replace(/\/$/u, "");
|
|
47
|
+
const headers = Object.freeze({
|
|
48
|
+
apikey: serviceRoleKey,
|
|
49
|
+
authorization: `Bearer ${serviceRoleKey}`,
|
|
50
|
+
});
|
|
51
|
+
const removePaths = async ({ bucket, paths, }) => {
|
|
52
|
+
if (!BUCKET.test(bucket ?? "") || !Array.isArray(paths) || !paths.length) {
|
|
53
|
+
throw new Error("A Storage identity-claim removal is invalid.");
|
|
54
|
+
}
|
|
55
|
+
paths.forEach(objectPath);
|
|
56
|
+
const response = await fetchImpl(`${baseUrl}/storage/v1/object/${encodeURIComponent(bucket)}`, {
|
|
57
|
+
method: "DELETE",
|
|
58
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
59
|
+
body: JSON.stringify({ prefixes: paths }),
|
|
60
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Supabase Storage could not remove identity-claim objects (HTTP ${response.status}).`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
async copyAndVerify({ bucket, source, destination, }) {
|
|
68
|
+
if (!BUCKET.test(bucket ?? "")) {
|
|
69
|
+
throw new Error("A Storage identity-claim bucket is invalid.");
|
|
70
|
+
}
|
|
71
|
+
objectPath(source);
|
|
72
|
+
objectPath(destination);
|
|
73
|
+
if (source === destination) {
|
|
74
|
+
throw new Error("A Storage identity-claim copy must change the path.");
|
|
75
|
+
}
|
|
76
|
+
const sourceReceipt = await readVerifiedObject({
|
|
77
|
+
fetchImpl,
|
|
78
|
+
baseUrl,
|
|
79
|
+
headers,
|
|
80
|
+
bucket,
|
|
81
|
+
path: source,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetchImpl(`${baseUrl}/storage/v1/object/copy`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
bucketId: bucket,
|
|
89
|
+
sourceKey: source,
|
|
90
|
+
destinationKey: destination,
|
|
91
|
+
}),
|
|
92
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Supabase Storage could not copy an identity-claim object (HTTP ${response.status}).`);
|
|
96
|
+
}
|
|
97
|
+
const destinationReceipt = await readVerifiedObject({
|
|
98
|
+
fetchImpl,
|
|
99
|
+
baseUrl,
|
|
100
|
+
headers,
|
|
101
|
+
bucket,
|
|
102
|
+
path: destination,
|
|
103
|
+
});
|
|
104
|
+
if (sourceReceipt.bytes !== destinationReceipt.bytes ||
|
|
105
|
+
sourceReceipt.sha256 !== destinationReceipt.sha256) {
|
|
106
|
+
throw new Error("Supabase Storage copied an identity object with a different byte digest.");
|
|
107
|
+
}
|
|
108
|
+
return destinationReceipt;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
try {
|
|
112
|
+
await removePaths({ bucket, paths: [destination] });
|
|
113
|
+
}
|
|
114
|
+
catch (cleanupError) {
|
|
115
|
+
throw new AggregateError([error, cleanupError], "Storage identity-copy verification failed and destination cleanup needs attention.");
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
remove: removePaths,
|
|
121
|
+
});
|
|
122
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Public Rehearsal package entry point. */
|
|
2
|
+
export { REHEARSAL_CONFIG_VERSION, defineRehearsalConfig, findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, renderDetectedConfig, renderDetectedPostgresqlConfig, } from "./project/configuration.mjs";
|
|
3
|
+
export type { DetectedProject, RehearsalConfig, RehearsalConfigPathOptions, RehearsalConfigVersion, } from "./project/configuration.mjs";
|
|
4
|
+
export { EXCLUDED_VALUE, SANITIZATION_ACTIONS, applySanitizationAction, normalizeSanitizationAction, readBoundRuntimeSanitizationPolicy, validateSanitizationCoverage, validateRuntimeSanitizationPolicy, } from "./baseline/sanitization_policy.mjs";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Public Rehearsal package entry point. */
|
|
2
|
+
export { REHEARSAL_CONFIG_VERSION, defineRehearsalConfig, findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, renderDetectedConfig, renderDetectedPostgresqlConfig, } from "./project/configuration.mjs";
|
|
3
|
+
export { EXCLUDED_VALUE, SANITIZATION_ACTIONS, applySanitizationAction, normalizeSanitizationAction, readBoundRuntimeSanitizationPolicy, validateSanitizationCoverage, validateRuntimeSanitizationPolicy, } from "./baseline/sanitization_policy.mjs";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Load and strictly validate the versioned, project-owned Rehearsal
|
|
3
|
+
* configuration contract. Do not run directly; this module is reusable script
|
|
4
|
+
* infrastructure.
|
|
5
|
+
*/
|
|
6
|
+
import type { RehearsalConfig, RehearsalConfigVersion } from "../../types/configuration.mjs";
|
|
7
|
+
export type { RehearsalConfig, RehearsalConfigVersion };
|
|
8
|
+
export declare const REHEARSAL_CONFIG_VERSION = 1;
|
|
9
|
+
export declare const REHEARSAL_DEFAULTS: Readonly<{
|
|
10
|
+
baseline: Readonly<{
|
|
11
|
+
artifactDirectory: ".rehearsal";
|
|
12
|
+
}>;
|
|
13
|
+
containerRuntime: Readonly<{
|
|
14
|
+
autoStartColima: true;
|
|
15
|
+
}>;
|
|
16
|
+
cleanup: Readonly<{
|
|
17
|
+
retainBaselineGenerations: 2;
|
|
18
|
+
}>;
|
|
19
|
+
runtime: Readonly<{
|
|
20
|
+
applicationUrl: "http://localhost:5175";
|
|
21
|
+
projectId: "rehearsal-local";
|
|
22
|
+
}>;
|
|
23
|
+
safety: Readonly<{
|
|
24
|
+
allowedHosts: readonly string[];
|
|
25
|
+
blockedEnvironmentVariables: readonly string[];
|
|
26
|
+
hostedAccess: "disabled";
|
|
27
|
+
outboundNetwork: "deny";
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
30
|
+
export interface DetectedProject {
|
|
31
|
+
projectName: string;
|
|
32
|
+
packageManager: "npm" | "pnpm" | "yarn";
|
|
33
|
+
hasSupabaseConfig: boolean;
|
|
34
|
+
hasMigrations: boolean;
|
|
35
|
+
hasPostgresqlMigrations: boolean;
|
|
36
|
+
postgresqlMigrationDirectory: string;
|
|
37
|
+
applicationCommand: string;
|
|
38
|
+
verificationCommand: string;
|
|
39
|
+
}
|
|
40
|
+
export interface RehearsalConfigPathOptions {
|
|
41
|
+
projectRoot?: string;
|
|
42
|
+
configPath?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const defineRehearsalConfig: <const Config extends RehearsalConfig>(config: Config) => Config;
|
|
45
|
+
export declare const findRehearsalConfigPath: ({ projectRoot, configPath, }?: RehearsalConfigPathOptions) => Promise<string>;
|
|
46
|
+
export declare const loadRehearsalConfig: ({ projectRoot, configPath, }?: RehearsalConfigPathOptions) => Promise<Readonly<{
|
|
47
|
+
config: Readonly<{
|
|
48
|
+
schemaVersion: 1;
|
|
49
|
+
project: Readonly<{
|
|
50
|
+
name: string;
|
|
51
|
+
}>;
|
|
52
|
+
supabase: Readonly<{
|
|
53
|
+
workdir: string;
|
|
54
|
+
migrationDirectory: string;
|
|
55
|
+
rehearsalConfig: string;
|
|
56
|
+
runtimeWorkdir: string;
|
|
57
|
+
authentication: Readonly<{
|
|
58
|
+
enableLocalSignup: true;
|
|
59
|
+
environmentFile: string;
|
|
60
|
+
providers: readonly Readonly<{
|
|
61
|
+
name: "github" | "google";
|
|
62
|
+
clientIdEnvironmentVariable: string;
|
|
63
|
+
clientSecretEnvironmentVariable: string;
|
|
64
|
+
skipNonceCheck: boolean;
|
|
65
|
+
emailOptional: boolean;
|
|
66
|
+
}>[];
|
|
67
|
+
}> | null;
|
|
68
|
+
serviceEnvironmentFile: string | null;
|
|
69
|
+
serviceEnvironmentVariables: readonly string[];
|
|
70
|
+
}> | null;
|
|
71
|
+
postgresql: Readonly<{
|
|
72
|
+
migrationDirectory: string;
|
|
73
|
+
runtimeWorkdir: string;
|
|
74
|
+
image: string;
|
|
75
|
+
database: string;
|
|
76
|
+
user: string;
|
|
77
|
+
}> | null;
|
|
78
|
+
baseline: Readonly<{
|
|
79
|
+
artifactDirectory: string;
|
|
80
|
+
sanitizationPolicy: string;
|
|
81
|
+
}>;
|
|
82
|
+
containerRuntime: Readonly<{
|
|
83
|
+
autoStartColima: boolean;
|
|
84
|
+
}>;
|
|
85
|
+
cleanup: Readonly<{
|
|
86
|
+
retainBaselineGenerations: number;
|
|
87
|
+
}>;
|
|
88
|
+
preparation: Readonly<{
|
|
89
|
+
sourcePolicy: string;
|
|
90
|
+
privacyKey: string;
|
|
91
|
+
batchRows: number;
|
|
92
|
+
maximumRows: number;
|
|
93
|
+
maximumBytes: number;
|
|
94
|
+
diskHeadroomBytes: number;
|
|
95
|
+
}> | null;
|
|
96
|
+
runtimePolicy: string | null;
|
|
97
|
+
identityPolicy: string | null;
|
|
98
|
+
dependentTargets: readonly Readonly<{
|
|
99
|
+
name: string;
|
|
100
|
+
configPath: string;
|
|
101
|
+
prepareCommand: string | null;
|
|
102
|
+
}>[];
|
|
103
|
+
application: Readonly<{
|
|
104
|
+
startCommand: string;
|
|
105
|
+
proofCommand: string;
|
|
106
|
+
environmentFile: string;
|
|
107
|
+
environmentVariables: Readonly<Record<string, string>>;
|
|
108
|
+
readiness: Readonly<{
|
|
109
|
+
url: string;
|
|
110
|
+
expectedStatus: number;
|
|
111
|
+
timeoutSeconds: number;
|
|
112
|
+
}> | null;
|
|
113
|
+
httpProofs: readonly Readonly<{
|
|
114
|
+
name: string;
|
|
115
|
+
kind: "positive" | "negative";
|
|
116
|
+
url: string;
|
|
117
|
+
method: string;
|
|
118
|
+
expectedStatus: number;
|
|
119
|
+
json: Readonly<{
|
|
120
|
+
path: readonly string[];
|
|
121
|
+
equals?: unknown;
|
|
122
|
+
notEquals?: unknown;
|
|
123
|
+
minimumItems?: number;
|
|
124
|
+
}> | null;
|
|
125
|
+
}>[];
|
|
126
|
+
runtimeAdapter: string | null;
|
|
127
|
+
}>;
|
|
128
|
+
runtime: Readonly<{
|
|
129
|
+
target: "postgresql" | "supabase";
|
|
130
|
+
applicationUrl: string;
|
|
131
|
+
projectId: string;
|
|
132
|
+
ports: Readonly<{
|
|
133
|
+
api?: number;
|
|
134
|
+
database: number;
|
|
135
|
+
studio?: number;
|
|
136
|
+
}>;
|
|
137
|
+
}>;
|
|
138
|
+
safety: Readonly<{
|
|
139
|
+
allowedHosts: readonly string[];
|
|
140
|
+
blockedEnvironmentVariables: readonly string[];
|
|
141
|
+
authenticationProviders: readonly string[];
|
|
142
|
+
hostedAccess: "disabled";
|
|
143
|
+
outboundNetwork: "deny";
|
|
144
|
+
}>;
|
|
145
|
+
verification: Readonly<{
|
|
146
|
+
commands: readonly string[];
|
|
147
|
+
}>;
|
|
148
|
+
}>;
|
|
149
|
+
configPath: string;
|
|
150
|
+
projectRoot: string;
|
|
151
|
+
paths: Readonly<{
|
|
152
|
+
artifactDirectory: string;
|
|
153
|
+
applicationEnvironment: string;
|
|
154
|
+
migrationDirectory: string;
|
|
155
|
+
rehearsalConfig: string | null;
|
|
156
|
+
runtimeWorkdir: string;
|
|
157
|
+
serviceEnvironment: string | null;
|
|
158
|
+
sanitizationPolicy: string;
|
|
159
|
+
sourcePolicy: string | null;
|
|
160
|
+
privacyKey: string | null;
|
|
161
|
+
runtimePolicy: string | null;
|
|
162
|
+
identityPolicy: string | null;
|
|
163
|
+
supabaseWorkdir: string | null;
|
|
164
|
+
runtimeAdapter: string | null;
|
|
165
|
+
}>;
|
|
166
|
+
}>>;
|
|
167
|
+
export declare const inspectDetectedProject: ({ projectRoot, }?: {
|
|
168
|
+
projectRoot?: string;
|
|
169
|
+
}) => Promise<DetectedProject>;
|
|
170
|
+
export declare const renderDetectedConfig: (detected: DetectedProject, { applicationUrl, ports, }?: {
|
|
171
|
+
applicationUrl?: string;
|
|
172
|
+
ports?: {
|
|
173
|
+
api: number;
|
|
174
|
+
database: number;
|
|
175
|
+
studio: number;
|
|
176
|
+
};
|
|
177
|
+
}) => string;
|
|
178
|
+
export declare const renderDetectedPostgresqlConfig: (detected: DetectedProject, { applicationUrl, databasePort }?: {
|
|
179
|
+
applicationUrl?: string | undefined;
|
|
180
|
+
databasePort?: number | undefined;
|
|
181
|
+
}) => string;
|