@hot-updater/supabase 0.36.7 → 1.0.0-rc.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/dist/edge.cjs +6 -3
- package/dist/edge.d.cts +14 -2
- package/dist/edge.d.mts +14 -2
- package/dist/edge.mjs +5 -2
- package/dist/iac/index.cjs +607 -50
- package/dist/iac/index.d.cts +26 -8
- package/dist/iac/index.d.mts +26 -8
- package/dist/iac/index.mjs +604 -52
- package/dist/index.cjs +3 -145
- package/dist/index.d.cts +7 -24
- package/dist/index.d.mts +7 -24
- package/dist/index.mjs +2 -117
- package/dist/init/index.cjs +5 -2
- package/dist/init/index.d.cts +2 -2
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +5 -2
- package/dist/supabaseDatabase-BDIBsXpz.d.cts +13 -0
- package/dist/supabaseDatabase-CP9WalBi.d.mts +13 -0
- package/dist/supabaseStorage-Cw294tR4.mjs +557 -0
- package/dist/supabaseStorage-yP7u4m7E.cjs +568 -0
- package/package.json +9 -9
- package/supabase/edge-functions/index.ts +9 -13
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +417 -202
- package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +583 -0
- package/supabase/migrations.spec.ts +127 -46
- package/dist/supabaseEdgeFunctionStorage-BRxGvt-r.d.mts +0 -19
- package/dist/supabaseEdgeFunctionStorage-BSLlUeqi.cjs +0 -386
- package/dist/supabaseEdgeFunctionStorage-CU396KO3.d.cts +0 -19
- package/dist/supabaseEdgeFunctionStorage-pV0s_mTF.mjs +0 -356
- package/supabase/migrations/20250103114225_init.sql +0 -228
- package/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -134
- package/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -194
- package/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -184
- package/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -503
- package/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
- package/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
- package/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -48
package/dist/iac/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { ConfigBuilder, InitError, MissingInitInputsError, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterConfig } from "@hot-updater/cli-tools";
|
|
4
|
+
import { ConfigBuilder, InitError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterConfig } from "@hot-updater/cli-tools";
|
|
5
|
+
import { provisionApiKey } from "@hot-updater/server";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
7
8
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -17,6 +18,8 @@ import { appendFileSync, createReadStream, createWriteStream, readFileSync, stat
|
|
|
17
18
|
import { finished } from "node:stream/promises";
|
|
18
19
|
import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
|
|
19
20
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
21
|
+
import { DatabasePluginInputError, createDatabasePlugin } from "@hot-updater/plugin-core";
|
|
22
|
+
import { DatabaseRowReferencedError, createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
|
|
20
23
|
import { createClient } from "@supabase/supabase-js";
|
|
21
24
|
//#region \0rolldown/runtime.js
|
|
22
25
|
var __create = Object.create;
|
|
@@ -6271,6 +6274,437 @@ createExeca(mapNode);
|
|
|
6271
6274
|
createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
|
|
6272
6275
|
const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
|
|
6273
6276
|
//#endregion
|
|
6277
|
+
//#region src/supabaseConfig.ts
|
|
6278
|
+
const resolveSupabaseServiceRoleKey = (config) => {
|
|
6279
|
+
if (!config.supabaseServiceRoleKey) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
6280
|
+
return config.supabaseServiceRoleKey;
|
|
6281
|
+
};
|
|
6282
|
+
//#endregion
|
|
6283
|
+
//#region src/supabaseFilter.ts
|
|
6284
|
+
var InvalidSupabasePredicateError = class extends Error {
|
|
6285
|
+
name = "InvalidSupabasePredicateError";
|
|
6286
|
+
};
|
|
6287
|
+
const encodeValue = (value) => {
|
|
6288
|
+
if (typeof value !== "string") return String(value);
|
|
6289
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
6290
|
+
};
|
|
6291
|
+
const encodePattern = (value) => encodeValue(value.replaceAll("*", "\\*").replaceAll("%", "\\%").replaceAll("_", "\\_"));
|
|
6292
|
+
const predicate = (condition) => {
|
|
6293
|
+
const operator = condition.operator ?? "eq";
|
|
6294
|
+
const field = condition.field;
|
|
6295
|
+
switch (operator) {
|
|
6296
|
+
case "eq":
|
|
6297
|
+
case "ne":
|
|
6298
|
+
if (condition.value === null) return `${field}.${operator === "eq" ? "is" : "not.is"}.null`;
|
|
6299
|
+
if (typeof condition.value !== "boolean" && typeof condition.value !== "number" && typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
|
|
6300
|
+
if ("mode" in condition && condition.mode === "insensitive") {
|
|
6301
|
+
const comparison = encodePattern(condition.value);
|
|
6302
|
+
return `${field}.${operator === "eq" ? "ilike" : "not.ilike"}.${comparison}`;
|
|
6303
|
+
}
|
|
6304
|
+
return `${field}.${operator === "eq" ? "eq" : "neq"}.${encodeValue(condition.value)}`;
|
|
6305
|
+
case "gt":
|
|
6306
|
+
case "gte":
|
|
6307
|
+
case "lt":
|
|
6308
|
+
case "lte":
|
|
6309
|
+
if (typeof condition.value !== "number" && typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
|
|
6310
|
+
return `${field}.${operator}.${encodeValue(condition.value)}`;
|
|
6311
|
+
case "in":
|
|
6312
|
+
case "not_in": {
|
|
6313
|
+
if (!Array.isArray(condition.value)) throw new InvalidSupabasePredicateError();
|
|
6314
|
+
if (condition.value.length === 0) return operator === "in" ? `and(${field}.is.null,${field}.not.is.null)` : `or(${field}.is.null,${field}.not.is.null)`;
|
|
6315
|
+
const values = condition.value.map(encodeValue).join(",");
|
|
6316
|
+
return `${field}.${operator === "in" ? "in" : "not.in"}.(${values})`;
|
|
6317
|
+
}
|
|
6318
|
+
case "contains":
|
|
6319
|
+
case "starts_with":
|
|
6320
|
+
case "ends_with": {
|
|
6321
|
+
if (typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
|
|
6322
|
+
const value = encodePattern(condition.value).slice(1, -1);
|
|
6323
|
+
const pattern = operator === "contains" ? `"*${value}*"` : operator === "starts_with" ? `"${value}*"` : `"*${value}"`;
|
|
6324
|
+
return `${field}.${"mode" in condition && condition.mode === "insensitive" ? "ilike" : "like"}.${pattern}`;
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
};
|
|
6328
|
+
const buildSupabaseFilter = (where) => {
|
|
6329
|
+
const [first, ...rest] = where ?? [];
|
|
6330
|
+
if (first === void 0) return;
|
|
6331
|
+
let expression = predicate(first);
|
|
6332
|
+
for (const condition of rest) {
|
|
6333
|
+
const next = predicate(condition);
|
|
6334
|
+
expression = condition.connector === "OR" ? `or(${expression},${next})` : `and(${expression},${next})`;
|
|
6335
|
+
}
|
|
6336
|
+
return expression;
|
|
6337
|
+
};
|
|
6338
|
+
//#endregion
|
|
6339
|
+
//#region src/supabaseInfrastructureNames.ts
|
|
6340
|
+
const SUPABASE_V1_FUNCTION_NAME = "hot-updater-v1";
|
|
6341
|
+
const SUPABASE_V1_TABLE_NAMES = {
|
|
6342
|
+
apiKeys: "hot_updater_v1_api_keys",
|
|
6343
|
+
bundleEvents: "hot_updater_v1_bundle_events",
|
|
6344
|
+
bundlePatches: "hot_updater_v1_bundle_patches",
|
|
6345
|
+
bundles: "hot_updater_v1_bundles",
|
|
6346
|
+
channels: "hot_updater_v1_channels",
|
|
6347
|
+
releaseCatalogs: "hot_updater_v1_release_catalogs",
|
|
6348
|
+
releases: "hot_updater_v1_releases",
|
|
6349
|
+
settings: "hot_updater_v1_private_settings"
|
|
6350
|
+
};
|
|
6351
|
+
const SUPABASE_V1_FUNCTION_NAMES = {
|
|
6352
|
+
commit: "hot_updater_v1_commit",
|
|
6353
|
+
deleteChannel: "hot_updater_v1_delete_channel"
|
|
6354
|
+
};
|
|
6355
|
+
//#endregion
|
|
6356
|
+
//#region src/supabaseResult.ts
|
|
6357
|
+
var SupabaseDatabaseError = class extends Error {
|
|
6358
|
+
name = "SupabaseDatabaseError";
|
|
6359
|
+
constructor(operation, cause) {
|
|
6360
|
+
super(`Supabase database operation failed: ${operation}`, { cause });
|
|
6361
|
+
this.operation = operation;
|
|
6362
|
+
}
|
|
6363
|
+
};
|
|
6364
|
+
var SupabaseMissingDataError = class extends Error {
|
|
6365
|
+
name = "SupabaseMissingDataError";
|
|
6366
|
+
constructor(operation) {
|
|
6367
|
+
super(`Supabase database operation returned no row: ${operation}`);
|
|
6368
|
+
this.operation = operation;
|
|
6369
|
+
}
|
|
6370
|
+
};
|
|
6371
|
+
const throwSupabaseError = (operation, error) => {
|
|
6372
|
+
if (error !== null) throw new SupabaseDatabaseError(operation, error);
|
|
6373
|
+
};
|
|
6374
|
+
//#endregion
|
|
6375
|
+
//#region src/supabaseDatabase.ts
|
|
6376
|
+
const isForeignKeyViolation = (error) => typeof error === "object" && error !== null && Reflect.get(error, "code") === "23503";
|
|
6377
|
+
const createSupabaseImplementation = (supabase) => {
|
|
6378
|
+
const implementation = {
|
|
6379
|
+
async create(input) {
|
|
6380
|
+
switch (input.model) {
|
|
6381
|
+
case "bundles": {
|
|
6382
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).insert(input.data).select("*").single();
|
|
6383
|
+
throwSupabaseError("create bundles", error);
|
|
6384
|
+
if (data === null) throw new SupabaseMissingDataError("create bundles");
|
|
6385
|
+
return data;
|
|
6386
|
+
}
|
|
6387
|
+
case "bundle_patches": {
|
|
6388
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).insert(input.data).select("*").single();
|
|
6389
|
+
throwSupabaseError("create bundle_patches", error);
|
|
6390
|
+
if (data === null) throw new SupabaseMissingDataError("create bundle_patches");
|
|
6391
|
+
return data;
|
|
6392
|
+
}
|
|
6393
|
+
case "channels": {
|
|
6394
|
+
if (input.onConflict === "ignore") {
|
|
6395
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).upsert(input.data, {
|
|
6396
|
+
onConflict: "name",
|
|
6397
|
+
ignoreDuplicates: true
|
|
6398
|
+
}).select("*").maybeSingle();
|
|
6399
|
+
throwSupabaseError("create channels", error);
|
|
6400
|
+
return data ?? input.data;
|
|
6401
|
+
}
|
|
6402
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).insert(input.data).select("*").single();
|
|
6403
|
+
throwSupabaseError("create channels", error);
|
|
6404
|
+
if (data === null) throw new SupabaseMissingDataError("create channels");
|
|
6405
|
+
return data;
|
|
6406
|
+
}
|
|
6407
|
+
case "bundle_events": {
|
|
6408
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundleEvents).insert(input.data).select("*").single();
|
|
6409
|
+
throwSupabaseError("create bundle_events", error);
|
|
6410
|
+
if (data === null) throw new SupabaseMissingDataError("create bundle_events");
|
|
6411
|
+
return data;
|
|
6412
|
+
}
|
|
6413
|
+
case "releases": {
|
|
6414
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.releases).insert(input.data).select("*").single();
|
|
6415
|
+
throwSupabaseError("create releases", error);
|
|
6416
|
+
if (data === null) throw new SupabaseMissingDataError("create releases");
|
|
6417
|
+
return data;
|
|
6418
|
+
}
|
|
6419
|
+
case "release_catalogs": {
|
|
6420
|
+
const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).insert(input.data).select("*").single();
|
|
6421
|
+
throwSupabaseError("create release_catalogs", error);
|
|
6422
|
+
if (data === null) throw new SupabaseMissingDataError("create release_catalogs");
|
|
6423
|
+
return data;
|
|
6424
|
+
}
|
|
6425
|
+
case "api_keys": {
|
|
6426
|
+
const { data, error } = await (input.onConflict === "ignore" ? supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).upsert(input.data, {
|
|
6427
|
+
onConflict: "hash",
|
|
6428
|
+
ignoreDuplicates: true
|
|
6429
|
+
}) : supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).insert(input.data)).select("*").maybeSingle();
|
|
6430
|
+
throwSupabaseError("create api_keys", error);
|
|
6431
|
+
if (data === null && input.onConflict !== "ignore") throw new SupabaseMissingDataError("create api_keys");
|
|
6432
|
+
return data ?? input.data;
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
},
|
|
6436
|
+
async update(input) {
|
|
6437
|
+
const filter = buildSupabaseFilter(input.where);
|
|
6438
|
+
if (input.model === "api_keys") {
|
|
6439
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).update(input.update);
|
|
6440
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6441
|
+
const { data, error } = await query.select("*").maybeSingle();
|
|
6442
|
+
throwSupabaseError("update api_keys", error);
|
|
6443
|
+
return data;
|
|
6444
|
+
}
|
|
6445
|
+
if (input.model === "releases") {
|
|
6446
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).update(input.update);
|
|
6447
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6448
|
+
const { data, error } = await query.select("*").maybeSingle();
|
|
6449
|
+
throwSupabaseError("update releases", error);
|
|
6450
|
+
return data;
|
|
6451
|
+
}
|
|
6452
|
+
if (input.model === "release_catalogs") {
|
|
6453
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).update(input.update);
|
|
6454
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6455
|
+
const { data, error } = await query.select("*").maybeSingle();
|
|
6456
|
+
throwSupabaseError("update release_catalogs", error);
|
|
6457
|
+
return data;
|
|
6458
|
+
}
|
|
6459
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).update(input.update);
|
|
6460
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6461
|
+
const { data, error } = await query.select("*").maybeSingle();
|
|
6462
|
+
throwSupabaseError("update bundles", error);
|
|
6463
|
+
return data;
|
|
6464
|
+
},
|
|
6465
|
+
async delete(input) {
|
|
6466
|
+
const filter = buildSupabaseFilter(input.where);
|
|
6467
|
+
switch (input.model) {
|
|
6468
|
+
case "bundles": {
|
|
6469
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).delete();
|
|
6470
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6471
|
+
const { error } = await query;
|
|
6472
|
+
throwSupabaseError("delete bundles", error);
|
|
6473
|
+
return;
|
|
6474
|
+
}
|
|
6475
|
+
case "bundle_patches": {
|
|
6476
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).delete();
|
|
6477
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6478
|
+
const { error } = await query;
|
|
6479
|
+
throwSupabaseError("delete bundle_patches", error);
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
case "channels": {
|
|
6483
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).delete();
|
|
6484
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6485
|
+
const { error } = await query;
|
|
6486
|
+
if (isForeignKeyViolation(error)) throw new DatabaseRowReferencedError();
|
|
6487
|
+
throwSupabaseError("delete channels", error);
|
|
6488
|
+
}
|
|
6489
|
+
case "releases": {
|
|
6490
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).delete();
|
|
6491
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6492
|
+
const { error } = await query;
|
|
6493
|
+
throwSupabaseError("delete releases", error);
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
},
|
|
6497
|
+
async count(input) {
|
|
6498
|
+
if (input.distinct !== void 0) throw new DatabasePluginInputError("invalid-operation");
|
|
6499
|
+
const filter = buildSupabaseFilter(input.where);
|
|
6500
|
+
switch (input.model) {
|
|
6501
|
+
case "bundles": {
|
|
6502
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*", {
|
|
6503
|
+
count: "exact",
|
|
6504
|
+
head: true
|
|
6505
|
+
});
|
|
6506
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6507
|
+
const { count, error } = await query;
|
|
6508
|
+
throwSupabaseError("count bundles", error);
|
|
6509
|
+
return count ?? 0;
|
|
6510
|
+
}
|
|
6511
|
+
case "bundle_patches": {
|
|
6512
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*", {
|
|
6513
|
+
count: "exact",
|
|
6514
|
+
head: true
|
|
6515
|
+
});
|
|
6516
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6517
|
+
const { count, error } = await query;
|
|
6518
|
+
throwSupabaseError("count bundle_patches", error);
|
|
6519
|
+
return count ?? 0;
|
|
6520
|
+
}
|
|
6521
|
+
case "releases": {
|
|
6522
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*", {
|
|
6523
|
+
count: "exact",
|
|
6524
|
+
head: true
|
|
6525
|
+
});
|
|
6526
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6527
|
+
const { count, error } = await query;
|
|
6528
|
+
throwSupabaseError("count releases", error);
|
|
6529
|
+
return count ?? 0;
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
},
|
|
6533
|
+
async findOne(input) {
|
|
6534
|
+
const filter = buildSupabaseFilter(input.where);
|
|
6535
|
+
switch (input.model) {
|
|
6536
|
+
case "bundles": {
|
|
6537
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*");
|
|
6538
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6539
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6540
|
+
throwSupabaseError("findOne bundles", error);
|
|
6541
|
+
return data;
|
|
6542
|
+
}
|
|
6543
|
+
case "api_keys": {
|
|
6544
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).select("*");
|
|
6545
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6546
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6547
|
+
throwSupabaseError("findOne api_keys", error);
|
|
6548
|
+
return data;
|
|
6549
|
+
}
|
|
6550
|
+
case "channels": {
|
|
6551
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*");
|
|
6552
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6553
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6554
|
+
throwSupabaseError("findOne channels", error);
|
|
6555
|
+
return data;
|
|
6556
|
+
}
|
|
6557
|
+
case "bundle_patches": {
|
|
6558
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*");
|
|
6559
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6560
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6561
|
+
throwSupabaseError("findOne bundle_patches", error);
|
|
6562
|
+
return data;
|
|
6563
|
+
}
|
|
6564
|
+
case "releases": {
|
|
6565
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*");
|
|
6566
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6567
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6568
|
+
throwSupabaseError("findOne releases", error);
|
|
6569
|
+
return data;
|
|
6570
|
+
}
|
|
6571
|
+
case "release_catalogs": {
|
|
6572
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).select("*");
|
|
6573
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6574
|
+
const { data, error } = await query.limit(1).maybeSingle();
|
|
6575
|
+
throwSupabaseError("findOne release_catalogs", error);
|
|
6576
|
+
return data;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
},
|
|
6580
|
+
async findMany(input) {
|
|
6581
|
+
if (input.distinctOn !== void 0) throw new DatabasePluginInputError("invalid-operation");
|
|
6582
|
+
if (input.limit === 0) return [];
|
|
6583
|
+
const filter = buildSupabaseFilter(input.where);
|
|
6584
|
+
const rangeEnd = input.offset + input.limit - 1;
|
|
6585
|
+
const orderBy = input.orderBy ?? [];
|
|
6586
|
+
switch (input.model) {
|
|
6587
|
+
case "bundles": {
|
|
6588
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*");
|
|
6589
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6590
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6591
|
+
ascending: clause.direction === "asc",
|
|
6592
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6593
|
+
});
|
|
6594
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6595
|
+
throwSupabaseError("findMany bundles", error);
|
|
6596
|
+
return data ?? [];
|
|
6597
|
+
}
|
|
6598
|
+
case "bundle_events": {
|
|
6599
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundleEvents).select("*");
|
|
6600
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6601
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6602
|
+
ascending: clause.direction === "asc",
|
|
6603
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6604
|
+
});
|
|
6605
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6606
|
+
throwSupabaseError("findMany bundle_events", error);
|
|
6607
|
+
return data ?? [];
|
|
6608
|
+
}
|
|
6609
|
+
case "api_keys": {
|
|
6610
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).select("*");
|
|
6611
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6612
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6613
|
+
ascending: clause.direction === "asc",
|
|
6614
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6615
|
+
});
|
|
6616
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6617
|
+
throwSupabaseError("findMany api_keys", error);
|
|
6618
|
+
return data ?? [];
|
|
6619
|
+
}
|
|
6620
|
+
case "bundle_patches": {
|
|
6621
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*");
|
|
6622
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6623
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6624
|
+
ascending: clause.direction === "asc",
|
|
6625
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6626
|
+
});
|
|
6627
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6628
|
+
throwSupabaseError("findMany bundle_patches", error);
|
|
6629
|
+
return data ?? [];
|
|
6630
|
+
}
|
|
6631
|
+
case "channels": {
|
|
6632
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*");
|
|
6633
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6634
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6635
|
+
ascending: clause.direction === "asc",
|
|
6636
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6637
|
+
});
|
|
6638
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6639
|
+
throwSupabaseError("findMany channels", error);
|
|
6640
|
+
return data ?? [];
|
|
6641
|
+
}
|
|
6642
|
+
case "releases": {
|
|
6643
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*");
|
|
6644
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6645
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6646
|
+
ascending: clause.direction === "asc",
|
|
6647
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6648
|
+
});
|
|
6649
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6650
|
+
throwSupabaseError("findMany releases", error);
|
|
6651
|
+
return data ?? [];
|
|
6652
|
+
}
|
|
6653
|
+
case "release_catalogs": {
|
|
6654
|
+
let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).select("*");
|
|
6655
|
+
if (filter !== void 0) query = query.or(filter);
|
|
6656
|
+
for (const clause of orderBy) query = query.order(clause.field, {
|
|
6657
|
+
ascending: clause.direction === "asc",
|
|
6658
|
+
...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
|
|
6659
|
+
});
|
|
6660
|
+
const { data, error } = await query.range(input.offset, rangeEnd);
|
|
6661
|
+
throwSupabaseError("findMany release_catalogs", error);
|
|
6662
|
+
return data ?? [];
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
},
|
|
6666
|
+
async insertChannel(input) {
|
|
6667
|
+
const { data: inserted, error: insertError } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).upsert(input.row, {
|
|
6668
|
+
onConflict: "name",
|
|
6669
|
+
ignoreDuplicates: true
|
|
6670
|
+
}).select("*").maybeSingle();
|
|
6671
|
+
throwSupabaseError("insert channel", insertError);
|
|
6672
|
+
if (inserted !== null) return {
|
|
6673
|
+
row: inserted,
|
|
6674
|
+
inserted: true
|
|
6675
|
+
};
|
|
6676
|
+
const { data: existing, error: findError } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*").eq("name", input.row.name).single();
|
|
6677
|
+
throwSupabaseError("find canonical channel", findError);
|
|
6678
|
+
if (existing === null) throw new SupabaseMissingDataError("find canonical channel");
|
|
6679
|
+
return {
|
|
6680
|
+
row: existing,
|
|
6681
|
+
inserted: false
|
|
6682
|
+
};
|
|
6683
|
+
},
|
|
6684
|
+
async deleteChannel(input) {
|
|
6685
|
+
const { data, error } = await supabase.rpc(SUPABASE_V1_FUNCTION_NAMES.deleteChannel, { p_id: input.id });
|
|
6686
|
+
throwSupabaseError("delete channel", error);
|
|
6687
|
+
if (data === null || typeof data !== "object" || !("deleted" in data) || typeof data.deleted !== "boolean") throw new SupabaseMissingDataError("delete channel");
|
|
6688
|
+
return data;
|
|
6689
|
+
}
|
|
6690
|
+
};
|
|
6691
|
+
implementation.commit = async (input) => {
|
|
6692
|
+
const { data, error } = await supabase.rpc(SUPABASE_V1_FUNCTION_NAMES.commit, { p_commit: input });
|
|
6693
|
+
throwSupabaseError("commit database changes", error);
|
|
6694
|
+
if (data === null || typeof data !== "object" || !("committed" in data) || typeof data.committed !== "boolean") throw new SupabaseMissingDataError("commit database changes");
|
|
6695
|
+
return data;
|
|
6696
|
+
};
|
|
6697
|
+
return implementation;
|
|
6698
|
+
};
|
|
6699
|
+
const supabaseDatabase = (config) => {
|
|
6700
|
+
const adapter = createDatabasePluginAdapter("supabaseDatabase", createSupabaseImplementation(createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))));
|
|
6701
|
+
return createDatabasePlugin({
|
|
6702
|
+
name: "supabaseDatabase",
|
|
6703
|
+
models: adapter.models,
|
|
6704
|
+
commit: adapter.commit
|
|
6705
|
+
});
|
|
6706
|
+
};
|
|
6707
|
+
//#endregion
|
|
6274
6708
|
//#region iac/init/index.ts
|
|
6275
6709
|
const SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY = "HOT_UPDATER_SUPABASE_DB_PASSWORD_PROJECT_ID";
|
|
6276
6710
|
const SUPABASE_REGION_VALUES = [
|
|
@@ -6357,9 +6791,9 @@ const initProvider = {
|
|
|
6357
6791
|
envKey: "HOT_UPDATER_SUPABASE_FUNCTION_NAME",
|
|
6358
6792
|
help: "Edge Function name",
|
|
6359
6793
|
prompt: {
|
|
6360
|
-
defaultValue:
|
|
6794
|
+
defaultValue: SUPABASE_V1_FUNCTION_NAME,
|
|
6361
6795
|
message: "Enter a name for the edge function",
|
|
6362
|
-
placeholder:
|
|
6796
|
+
placeholder: SUPABASE_V1_FUNCTION_NAME,
|
|
6363
6797
|
type: "text"
|
|
6364
6798
|
},
|
|
6365
6799
|
validate: isSupabaseFunctionName
|
|
@@ -6381,6 +6815,12 @@ const initProvider = {
|
|
|
6381
6815
|
const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
|
|
6382
6816
|
const supabase = createClient(supabaseUrl, supabaseServiceRoleKey);
|
|
6383
6817
|
return {
|
|
6818
|
+
getInfrastructureState: async () => {
|
|
6819
|
+
const marker = await supabase.from(SUPABASE_V1_TABLE_NAMES.settings).select("value").eq("key", "schema.core").maybeSingle();
|
|
6820
|
+
if (!marker.error) return marker.data?.value === "1.0.0" ? "v1" : "incompatible";
|
|
6821
|
+
if (marker.error.code === "42P01" || marker.error.code === "PGRST205") return "fresh";
|
|
6822
|
+
throw marker.error;
|
|
6823
|
+
},
|
|
6384
6824
|
listBuckets: async () => {
|
|
6385
6825
|
const { data, error } = await supabase.storage.listBuckets();
|
|
6386
6826
|
if (error) throw error;
|
|
@@ -6395,10 +6835,6 @@ const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
|
|
|
6395
6835
|
const { data, error } = await supabase.storage.createBucket(bucketName, options);
|
|
6396
6836
|
if (error) throw error;
|
|
6397
6837
|
return data;
|
|
6398
|
-
},
|
|
6399
|
-
updateBucket: async (bucketId, options) => {
|
|
6400
|
-
const { error } = await supabase.storage.updateBucket(bucketId, options);
|
|
6401
|
-
if (error) throw error;
|
|
6402
6838
|
}
|
|
6403
6839
|
};
|
|
6404
6840
|
};
|
|
@@ -6484,30 +6920,9 @@ const inputSupabaseAccessToken = async (accessToken) => {
|
|
|
6484
6920
|
};
|
|
6485
6921
|
//#endregion
|
|
6486
6922
|
//#region iac/supabaseBucketPrivacy.ts
|
|
6487
|
-
|
|
6488
|
-
name = "PublicSupabaseBucketError";
|
|
6489
|
-
constructor(bucketName) {
|
|
6490
|
-
super([
|
|
6491
|
-
`Supabase bucket "${bucketName}" is public.`,
|
|
6492
|
-
"Make the bucket private in Supabase Storage, then rerun init.",
|
|
6493
|
-
"Alternatively, rerun without --from-env-file to approve the change interactively."
|
|
6494
|
-
].join("\n"));
|
|
6495
|
-
this.bucketName = bucketName;
|
|
6496
|
-
}
|
|
6497
|
-
};
|
|
6498
|
-
const ensureSupabaseBucketPrivate = async ({ api, nonInteractive, selection }) => {
|
|
6923
|
+
const preserveSupabaseBucketPrivacy = ({ selection }) => {
|
|
6499
6924
|
if (selection.create || !selection.isPublic) return;
|
|
6500
|
-
|
|
6501
|
-
const confirmed = await p.confirm({
|
|
6502
|
-
message: `Bucket "${selection.name}" is public. Make it private?`,
|
|
6503
|
-
initialValue: true
|
|
6504
|
-
});
|
|
6505
|
-
if (p.isCancel(confirmed) || !confirmed) {
|
|
6506
|
-
p.log.info("Init cancelled.");
|
|
6507
|
-
process.exit(1);
|
|
6508
|
-
}
|
|
6509
|
-
await api.updateBucket(selection.id, { public: false });
|
|
6510
|
-
p.log.success(`Bucket "${selection.name}" is now private.`);
|
|
6925
|
+
p.log.warn(`Bucket "${selection.name}" is public. Its access level will be preserved for existing apps.`);
|
|
6511
6926
|
};
|
|
6512
6927
|
//#endregion
|
|
6513
6928
|
//#region iac/supabaseCli.ts
|
|
@@ -6576,12 +6991,26 @@ const linkSupabase = async (workdir, { accessToken, projectId, dbPassword }) =>
|
|
|
6576
6991
|
};
|
|
6577
6992
|
const pushDB = async (workdir, { accessToken, dbPassword }) => {
|
|
6578
6993
|
try {
|
|
6994
|
+
await execa("npx", [
|
|
6995
|
+
"supabase",
|
|
6996
|
+
"migration",
|
|
6997
|
+
"fetch",
|
|
6998
|
+
"--linked",
|
|
6999
|
+
"--yes",
|
|
7000
|
+
"--workdir",
|
|
7001
|
+
workdir
|
|
7002
|
+
], {
|
|
7003
|
+
cwd: workdir,
|
|
7004
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword)
|
|
7005
|
+
});
|
|
6579
7006
|
const dbPush = await execa("npx", [
|
|
6580
7007
|
"supabase",
|
|
6581
7008
|
"db",
|
|
6582
7009
|
"push",
|
|
6583
7010
|
"--include-all",
|
|
6584
|
-
"--yes"
|
|
7011
|
+
"--yes",
|
|
7012
|
+
"--workdir",
|
|
7013
|
+
workdir
|
|
6585
7014
|
], {
|
|
6586
7015
|
cwd: workdir,
|
|
6587
7016
|
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
@@ -6599,6 +7028,20 @@ const pushDB = async (workdir, { accessToken, dbPassword }) => {
|
|
|
6599
7028
|
}
|
|
6600
7029
|
};
|
|
6601
7030
|
//#endregion
|
|
7031
|
+
//#region iac/supabaseInfrastructureState.ts
|
|
7032
|
+
const assertSupabaseInfrastructureCanInitialize = async (api, projectId) => {
|
|
7033
|
+
if (await api.getInfrastructureState() === "incompatible") throw new InitError(`Supabase v1 infrastructure in project ${projectId} is incomplete or uses an unsupported database version.`);
|
|
7034
|
+
};
|
|
7035
|
+
const assertSupabaseFunctionCanInitialize = async ({ fetchImpl, functionName, functionSlugs, projectId }) => {
|
|
7036
|
+
if (!functionSlugs.includes(functionName)) return;
|
|
7037
|
+
await assertInfrastructureGenerationAtUrl({
|
|
7038
|
+
fetchImpl,
|
|
7039
|
+
provider: "Supabase",
|
|
7040
|
+
resource: `Edge Function ${functionName}`,
|
|
7041
|
+
versionUrl: `https://${projectId}.supabase.co/functions/v1/${encodeURIComponent(functionName)}/version`
|
|
7042
|
+
});
|
|
7043
|
+
};
|
|
7044
|
+
//#endregion
|
|
6602
7045
|
//#region iac/supabaseDeploymentInputs.ts
|
|
6603
7046
|
const assertSupabaseNonInteractiveInputs = async (inputs, nonInteractive, validateCliLogin = hasValidSupabaseCliLogin) => {
|
|
6604
7047
|
assertInitProviderInputs({
|
|
@@ -6740,6 +7183,14 @@ const supabaseCliManagementApi = () => ({
|
|
|
6740
7183
|
slug: organization.slug
|
|
6741
7184
|
}] : []);
|
|
6742
7185
|
},
|
|
7186
|
+
listFunctions: async (projectId) => parseSupabaseFunctionSlugs(await runSupabaseCli([
|
|
7187
|
+
"functions",
|
|
7188
|
+
"list",
|
|
7189
|
+
"--project-ref",
|
|
7190
|
+
projectId,
|
|
7191
|
+
"--output",
|
|
7192
|
+
"json"
|
|
7193
|
+
])),
|
|
6743
7194
|
createProject: async ({ name, organizationSlug, region }) => {
|
|
6744
7195
|
await execa("npx", [
|
|
6745
7196
|
"-y",
|
|
@@ -6787,6 +7238,13 @@ const supabaseCliManagementApi = () => ({
|
|
|
6787
7238
|
const SUPABASE_MANAGEMENT_API_URL = "https://api.supabase.com/v1";
|
|
6788
7239
|
const SUPABASE_MANAGEMENT_API_TIMEOUT_MS = 3e4;
|
|
6789
7240
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
7241
|
+
const parseSupabaseFunctionSlugs = (body) => {
|
|
7242
|
+
if (!Array.isArray(body)) throw new Error("Supabase functions response was invalid.");
|
|
7243
|
+
return body.map((item) => {
|
|
7244
|
+
if (!isRecord(item) || typeof item.slug !== "string") throw new Error("Supabase functions response was invalid.");
|
|
7245
|
+
return item.slug;
|
|
7246
|
+
});
|
|
7247
|
+
};
|
|
6790
7248
|
var SupabaseManagementApiStatusError = class extends Error {};
|
|
6791
7249
|
const projectCreationMayHaveSucceededError = (message = "Supabase project creation could not be confirmed.", cause) => new Error(`${message} The request may have succeeded; check the organization's projects before retrying init.`, { cause });
|
|
6792
7250
|
const request = async (accessToken, path, init) => {
|
|
@@ -6827,6 +7285,7 @@ const supabaseManagementApi = (accessToken) => accessToken === void 0 ? supabase
|
|
|
6827
7285
|
slug: organization.slug
|
|
6828
7286
|
}] : []);
|
|
6829
7287
|
},
|
|
7288
|
+
listFunctions: async (projectId) => parseSupabaseFunctionSlugs(await request(accessToken, `/projects/${encodeURIComponent(projectId)}/functions`)),
|
|
6830
7289
|
createProject: async ({ databasePassword, name, organizationSlug, region }) => {
|
|
6831
7290
|
const body = await request(accessToken, "/projects", {
|
|
6832
7291
|
body: {
|
|
@@ -6859,6 +7318,11 @@ const SUPABASE_PROJECT_READY_STATUS = "ACTIVE_HEALTHY";
|
|
|
6859
7318
|
const SUPABASE_PROJECT_PROVISIONING_STATUS = "COMING_UP";
|
|
6860
7319
|
const SUPABASE_PROJECT_READINESS_MAX_ATTEMPTS = 300;
|
|
6861
7320
|
const SUPABASE_PROJECT_READINESS_POLL_INTERVAL_MS = 1e3;
|
|
7321
|
+
const SUPABASE_STORAGE_READINESS_MAX_ATTEMPTS = 300;
|
|
7322
|
+
const SUPABASE_STORAGE_READINESS_POLL_INTERVAL_MS = 1e3;
|
|
7323
|
+
const SUPABASE_SCHEMA_READINESS_MAX_ATTEMPTS = 60;
|
|
7324
|
+
const SUPABASE_SCHEMA_READINESS_POLL_INTERVAL_MS = 1e3;
|
|
7325
|
+
const LEGACY_SUPABASE_CATALOG_CDN_URL_ENV_KEY = "HOT_UPDATER_SUPABASE_CATALOG_CDN_URL";
|
|
6862
7326
|
const STATIC_IMPORT_SPECIFIER_PATTERN = /^\s*(?:import|export)\s+(?:type\s+)?(?:[^"'`]+?\s+from\s+)?["']([^"']+)["'];?/gm;
|
|
6863
7327
|
const DYNAMIC_IMPORT_SPECIFIER_PATTERN = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
6864
7328
|
const getConfigScaffold = (build) => {
|
|
@@ -6907,10 +7371,28 @@ function App() {
|
|
|
6907
7371
|
return null; // Replace with your app root
|
|
6908
7372
|
}
|
|
6909
7373
|
|
|
6910
|
-
|
|
7374
|
+
HotUpdater.init({
|
|
6911
7375
|
baseURL: "%%source%%",
|
|
6912
|
-
|
|
6913
|
-
|
|
7376
|
+
requestHeaders: {
|
|
7377
|
+
"x-api-key": %%apiKey%%,
|
|
7378
|
+
},
|
|
7379
|
+
});
|
|
7380
|
+
|
|
7381
|
+
// Call HotUpdater.checkForUpdate({ updateStrategy: "appVersion" })
|
|
7382
|
+
// when your app is ready to check.
|
|
7383
|
+
export default App;`;
|
|
7384
|
+
const getSupabaseReactNativeSource = ({ apiKey, functionName, projectId }) => transformTemplate(SOURCE_TEMPLATE, {
|
|
7385
|
+
apiKey: JSON.stringify(apiKey),
|
|
7386
|
+
source: `https://${projectId}.supabase.co/functions/v1/${functionName}`
|
|
7387
|
+
});
|
|
7388
|
+
const reportSupabaseOriginCatalogReady = () => {
|
|
7389
|
+
p.log.success("Release catalog endpoint is ready in origin-only mode.");
|
|
7390
|
+
p.log.info("Catalog checks still invoke the Supabase Edge Function.");
|
|
7391
|
+
};
|
|
7392
|
+
const reportSupabaseApiKey = (apiKey) => {
|
|
7393
|
+
p.note(apiKey, "API Key");
|
|
7394
|
+
p.log.message("Store this API key separately in a secure place.");
|
|
7395
|
+
};
|
|
6914
7396
|
const resolvePackageExportPath = async (packageName, exportName) => {
|
|
6915
7397
|
const packageJsonPath = require$1.resolve(`${packageName}/package.json`);
|
|
6916
7398
|
const exportTarget = JSON.parse(await fs.readFile(packageJsonPath, "utf-8")).exports?.[exportName];
|
|
@@ -7012,10 +7494,11 @@ const buildEdgeFunctionImports = async (targetDir) => {
|
|
|
7012
7494
|
for (const nestedSpecifier of nestedSpecifiers) {
|
|
7013
7495
|
if (imports[nestedSpecifier]) continue;
|
|
7014
7496
|
if (nestedSpecifier.startsWith(WORKSPACE_PACKAGE_PREFIX)) {
|
|
7497
|
+
const [scope, name, ...subpath] = nestedSpecifier.split("/");
|
|
7015
7498
|
await addWorkspacePackage({
|
|
7016
7499
|
importSpecifier: nestedSpecifier,
|
|
7017
|
-
packageName:
|
|
7018
|
-
exportName: "."
|
|
7500
|
+
packageName: `${scope}/${name}`,
|
|
7501
|
+
exportName: subpath.length === 0 ? "." : `./${subpath.join("/")}`
|
|
7019
7502
|
});
|
|
7020
7503
|
continue;
|
|
7021
7504
|
}
|
|
@@ -7029,7 +7512,7 @@ const buildEdgeFunctionImports = async (targetDir) => {
|
|
|
7029
7512
|
exportName: "."
|
|
7030
7513
|
});
|
|
7031
7514
|
const supabasePackage = await addWorkspacePackage({
|
|
7032
|
-
importSpecifier: "@hot-updater/supabase",
|
|
7515
|
+
importSpecifier: "@hot-updater/supabase/edge",
|
|
7033
7516
|
packageName: "@hot-updater/supabase",
|
|
7034
7517
|
exportName: "./edge"
|
|
7035
7518
|
});
|
|
@@ -7098,6 +7581,17 @@ const selectProject = async (preferredProjectId, nonInteractive = false, accessT
|
|
|
7098
7581
|
project: selectedProject
|
|
7099
7582
|
};
|
|
7100
7583
|
};
|
|
7584
|
+
const isMissingSupabaseStorageTenantError = (error) => error instanceof Error && /Missing tenant config for tenant/u.test(error.message) && (Reflect.get(error, "status") === 400 || Reflect.get(error, "statusCode") === "400");
|
|
7585
|
+
const retryWhileSupabaseStorageTenantIsProvisioning = async (operation) => {
|
|
7586
|
+
for (let attempt = 1; attempt <= SUPABASE_STORAGE_READINESS_MAX_ATTEMPTS; attempt++) try {
|
|
7587
|
+
return await operation();
|
|
7588
|
+
} catch (error) {
|
|
7589
|
+
if (!isMissingSupabaseStorageTenantError(error) || attempt === SUPABASE_STORAGE_READINESS_MAX_ATTEMPTS) throw error;
|
|
7590
|
+
if (attempt === 1) p.log.info("Waiting for Supabase Storage to become ready...");
|
|
7591
|
+
await delay(SUPABASE_STORAGE_READINESS_POLL_INTERVAL_MS);
|
|
7592
|
+
}
|
|
7593
|
+
throw new Error("Supabase Storage did not become ready.");
|
|
7594
|
+
};
|
|
7101
7595
|
const selectBucket = async (api, preferredBucketName, nonInteractive = false) => {
|
|
7102
7596
|
let buckets = [];
|
|
7103
7597
|
let retryCount = 0;
|
|
@@ -7170,18 +7664,21 @@ const selectBucket = async (api, preferredBucketName, nonInteractive = false) =>
|
|
|
7170
7664
|
};
|
|
7171
7665
|
const createSelectedBucket = async (api, selection) => {
|
|
7172
7666
|
if (!selection.create) return selection;
|
|
7173
|
-
await api.createBucket(selection.name, { public: false });
|
|
7667
|
+
await retryWhileSupabaseStorageTenantIsProvisioning(() => api.createBucket(selection.name, { public: false }));
|
|
7174
7668
|
p.log.success(`Bucket "${selection.name}" created successfully.`);
|
|
7175
|
-
const bucket = (await api.listBuckets()).find((item) => item.name === selection.name);
|
|
7669
|
+
const bucket = (await retryWhileSupabaseStorageTenantIsProvisioning(() => api.listBuckets())).find((item) => item.name === selection.name);
|
|
7176
7670
|
if (!bucket) throw new Error("Failed to create and select new bucket");
|
|
7177
7671
|
return {
|
|
7178
7672
|
id: bucket.id,
|
|
7179
7673
|
name: bucket.name
|
|
7180
7674
|
};
|
|
7181
7675
|
};
|
|
7182
|
-
const deployEdgeFunction = async (accessToken, workdir, projectId, functionName) => {
|
|
7676
|
+
const deployEdgeFunction = async (accessToken, workdir, projectId, functionName, bucketName) => {
|
|
7183
7677
|
const edgeFunctionsLibPath = path.join(workdir, "supabase", "edge-functions");
|
|
7184
|
-
const edgeFunctionsCode = transformEnv(path.join(edgeFunctionsLibPath, "index.ts"), {
|
|
7678
|
+
const edgeFunctionsCode = transformEnv(path.join(edgeFunctionsLibPath, "index.ts"), {
|
|
7679
|
+
BUCKET_NAME: bucketName,
|
|
7680
|
+
FUNCTION_NAME: functionName
|
|
7681
|
+
});
|
|
7185
7682
|
if (!isSupabaseFunctionName(functionName)) throw new Error("Invalid Supabase Edge Function name.");
|
|
7186
7683
|
const functionsDir = path.resolve(workdir, "supabase", "functions");
|
|
7187
7684
|
const targetDir = path.resolve(functionsDir, functionName);
|
|
@@ -7228,6 +7725,15 @@ const waitForSupabaseProjectReady = async ({ getProjectStatus, maxAttempts = SUP
|
|
|
7228
7725
|
}
|
|
7229
7726
|
throw new Error(`Timed out while waiting for the Supabase project. Last status: ${lastStatus ?? "unknown"}.`);
|
|
7230
7727
|
};
|
|
7728
|
+
const waitForSupabaseSchemaReady = async ({ getInfrastructureState, maxAttempts = SUPABASE_SCHEMA_READINESS_MAX_ATTEMPTS, pollIntervalMs = SUPABASE_SCHEMA_READINESS_POLL_INTERVAL_MS }) => {
|
|
7729
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
7730
|
+
const state = await getInfrastructureState();
|
|
7731
|
+
if (state === "v1") return;
|
|
7732
|
+
if (state === "incompatible") throw new Error("Supabase database schema is incompatible after migration.");
|
|
7733
|
+
if (attempt < maxAttempts - 1) await delay(pollIntervalMs);
|
|
7734
|
+
}
|
|
7735
|
+
throw new Error("Timed out while waiting for the Supabase database schema cache.");
|
|
7736
|
+
};
|
|
7231
7737
|
const getSupabaseProjectAccess = async ({ accessToken, managementApi, project, waitForProject }) => {
|
|
7232
7738
|
const getServiceRoleApiKey = async () => {
|
|
7233
7739
|
const keysProcess = await execa("npx", [
|
|
@@ -7274,11 +7780,30 @@ const getSupabaseProjectAccess = async ({ accessToken, managementApi, project, w
|
|
|
7274
7780
|
serviceRoleApiKey
|
|
7275
7781
|
};
|
|
7276
7782
|
};
|
|
7277
|
-
const
|
|
7783
|
+
const withSupabaseCliMetadataCleanup = async (cwd, operation) => {
|
|
7784
|
+
const cliDirectory = path.join(cwd, "supabase");
|
|
7785
|
+
let shouldCleanup = false;
|
|
7786
|
+
try {
|
|
7787
|
+
await fs.access(cliDirectory);
|
|
7788
|
+
} catch (error) {
|
|
7789
|
+
if (!(error instanceof Error) || Reflect.get(error, "code") !== "ENOENT") throw error;
|
|
7790
|
+
shouldCleanup = true;
|
|
7791
|
+
}
|
|
7792
|
+
try {
|
|
7793
|
+
return await operation();
|
|
7794
|
+
} finally {
|
|
7795
|
+
if (shouldCleanup) await fs.rm(cliDirectory, {
|
|
7796
|
+
recursive: true,
|
|
7797
|
+
force: true
|
|
7798
|
+
});
|
|
7799
|
+
}
|
|
7800
|
+
};
|
|
7801
|
+
const runInitWithoutCliMetadata = async ({ build, envFile }) => {
|
|
7278
7802
|
const nonInteractive = envFile !== void 0;
|
|
7279
7803
|
const initEnvSources = await readHotUpdaterInitEnv(process.cwd(), envFile);
|
|
7280
7804
|
const { inputEnv, managedEnv } = initEnvSources;
|
|
7281
|
-
const
|
|
7805
|
+
const initInputEnv = getHotUpdaterInitInputEnv(initEnvSources, nonInteractive);
|
|
7806
|
+
const savedInputs = resolveSupabaseInitInputs(initInputEnv, {
|
|
7282
7807
|
inputEnv,
|
|
7283
7808
|
managedEnv
|
|
7284
7809
|
});
|
|
@@ -7317,11 +7842,15 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7317
7842
|
name: projectCreationInputs.bucketName
|
|
7318
7843
|
};
|
|
7319
7844
|
else throw new Error("Failed to plan the Supabase storage bucket.");
|
|
7320
|
-
if (projectAccess)
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7845
|
+
if (projectAccess) {
|
|
7846
|
+
await assertSupabaseInfrastructureCanInitialize(projectAccess.api, project.id);
|
|
7847
|
+
await assertSupabaseFunctionCanInitialize({
|
|
7848
|
+
functionName,
|
|
7849
|
+
functionSlugs: await managementApi.listFunctions(project.id),
|
|
7850
|
+
projectId: project.id
|
|
7851
|
+
});
|
|
7852
|
+
preserveSupabaseBucketPrivacy({ selection: bucketSelection });
|
|
7853
|
+
}
|
|
7325
7854
|
const inputsBeforeProvisioning = {
|
|
7326
7855
|
...savedInputs,
|
|
7327
7856
|
...projectCreationInputs,
|
|
@@ -7371,7 +7900,7 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7371
7900
|
});
|
|
7372
7901
|
const persistDatabasePassword = persistCredentialInputs && dbPassword !== "";
|
|
7373
7902
|
if (persistDatabasePassword) providerEnv[SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY] = project.id;
|
|
7374
|
-
await makeEnv(providerEnv, ".env.hotupdater", { removeKeys: persistDatabasePassword ? [] : [databasePasswordKey, SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY] });
|
|
7903
|
+
await makeEnv(providerEnv, ".env.hotupdater", { removeKeys: [LEGACY_SUPABASE_CATALOG_CDN_URL_ENV_KEY, ...persistDatabasePassword ? [] : [databasePasswordKey, SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY]] });
|
|
7375
7904
|
const bucket = await createSelectedBucket(projectAccess.api, bucketSelection);
|
|
7376
7905
|
await makeEnv({
|
|
7377
7906
|
[initProvider.inputs.projectId.envKey]: project.id,
|
|
@@ -7396,7 +7925,23 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7396
7925
|
accessToken,
|
|
7397
7926
|
dbPassword
|
|
7398
7927
|
});
|
|
7399
|
-
await
|
|
7928
|
+
await waitForSupabaseSchemaReady({ getInfrastructureState: projectAccess.api.getInfrastructureState });
|
|
7929
|
+
const databasePlugin = supabaseDatabase({
|
|
7930
|
+
supabaseServiceRoleKey: projectAccess.serviceRoleApiKey,
|
|
7931
|
+
supabaseUrl: `https://${project.id}.supabase.co`
|
|
7932
|
+
});
|
|
7933
|
+
let apiKey;
|
|
7934
|
+
try {
|
|
7935
|
+
apiKey = (await provisionApiKey({
|
|
7936
|
+
apiKeys: databasePlugin.models.apiKeys,
|
|
7937
|
+
existingApiKey: initInputEnv.HOT_UPDATER_API_KEY,
|
|
7938
|
+
name: "Supabase init"
|
|
7939
|
+
})).apiKey;
|
|
7940
|
+
await makeEnv({ HOT_UPDATER_API_KEY: apiKey });
|
|
7941
|
+
} finally {
|
|
7942
|
+
await databasePlugin.dispose?.();
|
|
7943
|
+
}
|
|
7944
|
+
await deployEdgeFunction(accessToken, tmpDir, project.id, functionName, bucket.name);
|
|
7400
7945
|
await removeTmpDir();
|
|
7401
7946
|
const configWriteResult = await writeHotUpdaterConfig(getConfigScaffold(build));
|
|
7402
7947
|
await assertSkippedConfigDoesNotUseLegacySupabaseKey(configWriteResult);
|
|
@@ -7404,9 +7949,16 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7404
7949
|
if (configWriteResult.status === "created") p.log.success("Generated 'hot-updater.config.ts' file with Supabase settings.");
|
|
7405
7950
|
else if (configWriteResult.status === "merged") p.log.success("Updated 'hot-updater.config.ts' file with Supabase settings.");
|
|
7406
7951
|
else p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
|
|
7407
|
-
p.note(
|
|
7952
|
+
p.note(getSupabaseReactNativeSource({
|
|
7953
|
+
apiKey,
|
|
7954
|
+
functionName,
|
|
7955
|
+
projectId: project.id
|
|
7956
|
+
}));
|
|
7957
|
+
reportSupabaseApiKey(apiKey);
|
|
7958
|
+
reportSupabaseOriginCatalogReady();
|
|
7408
7959
|
p.log.message(`Next step: ${link("https://hot-updater.dev/docs/managed/supabase#step-4-add-hotupdater-to-your-project")}`);
|
|
7409
7960
|
p.log.success("Done! 🎉");
|
|
7410
7961
|
};
|
|
7962
|
+
const runInit = (options) => withSupabaseCliMetadataCleanup(process.cwd(), () => runInitWithoutCliMetadata(options));
|
|
7411
7963
|
//#endregion
|
|
7412
|
-
export { createSelectedBucket, getLegacySupabaseConfigReference, getSupabaseProjectAccess, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject, waitForSupabaseProjectReady };
|
|
7964
|
+
export { createSelectedBucket, getLegacySupabaseConfigReference, getSupabaseProjectAccess, getSupabaseReactNativeSource, reportSupabaseApiKey, reportSupabaseOriginCatalogReady, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject, waitForSupabaseProjectReady, waitForSupabaseSchemaReady, withSupabaseCliMetadataCleanup };
|