@hot-updater/supabase 0.35.8 → 0.35.9
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/iac/index.cjs +733 -85
- package/dist/iac/index.d.cts +78 -13
- package/dist/iac/index.d.mts +78 -13
- package/dist/iac/index.mjs +732 -87
- package/dist/init/index.cjs +112 -0
- package/dist/init/index.d.cts +89 -0
- package/dist/init/index.d.mts +89 -0
- package/dist/init/index.mjs +107 -0
- package/package.json +14 -9
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +25 -21
package/dist/iac/index.cjs
CHANGED
|
@@ -6276,6 +6276,112 @@ createExeca(mapNode);
|
|
|
6276
6276
|
createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
|
|
6277
6277
|
const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
|
|
6278
6278
|
//#endregion
|
|
6279
|
+
//#region iac/init/index.ts
|
|
6280
|
+
const SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY = "HOT_UPDATER_SUPABASE_DB_PASSWORD_PROJECT_ID";
|
|
6281
|
+
const SUPABASE_REGION_VALUES = [
|
|
6282
|
+
"ap-east-1",
|
|
6283
|
+
"ap-northeast-1",
|
|
6284
|
+
"ap-northeast-2",
|
|
6285
|
+
"ap-south-1",
|
|
6286
|
+
"ap-southeast-1",
|
|
6287
|
+
"ap-southeast-2",
|
|
6288
|
+
"ca-central-1",
|
|
6289
|
+
"eu-central-1",
|
|
6290
|
+
"eu-central-2",
|
|
6291
|
+
"eu-north-1",
|
|
6292
|
+
"eu-west-1",
|
|
6293
|
+
"eu-west-2",
|
|
6294
|
+
"eu-west-3",
|
|
6295
|
+
"sa-east-1",
|
|
6296
|
+
"us-east-1",
|
|
6297
|
+
"us-east-2",
|
|
6298
|
+
"us-west-1",
|
|
6299
|
+
"us-west-2"
|
|
6300
|
+
];
|
|
6301
|
+
const isSupabaseRegion = (value) => value !== void 0 && SUPABASE_REGION_VALUES.some((region) => region === value);
|
|
6302
|
+
const isSupabaseFunctionName = (value) => value !== void 0 && /^[A-Za-z][A-Za-z0-9_-]*$/.test(value);
|
|
6303
|
+
const initProvider = {
|
|
6304
|
+
label: "Supabase",
|
|
6305
|
+
inputs: {
|
|
6306
|
+
projectId: {
|
|
6307
|
+
envKey: "HOT_UPDATER_SUPABASE_PROJECT_ID",
|
|
6308
|
+
help: "Supabase project reference"
|
|
6309
|
+
},
|
|
6310
|
+
projectName: {
|
|
6311
|
+
envKey: "HOT_UPDATER_SUPABASE_PROJECT_NAME",
|
|
6312
|
+
help: "Project name used when creating a Supabase project",
|
|
6313
|
+
optional: true,
|
|
6314
|
+
prompt: {
|
|
6315
|
+
defaultValue: "hot-updater",
|
|
6316
|
+
message: "Enter a name for the new Supabase project",
|
|
6317
|
+
placeholder: "hot-updater",
|
|
6318
|
+
type: "text"
|
|
6319
|
+
}
|
|
6320
|
+
},
|
|
6321
|
+
organizationSlug: {
|
|
6322
|
+
envKey: "HOT_UPDATER_SUPABASE_ORGANIZATION_SLUG",
|
|
6323
|
+
help: "Organization slug used when creating a Supabase project",
|
|
6324
|
+
optional: true,
|
|
6325
|
+
prompt: {
|
|
6326
|
+
message: "Select a Supabase organization",
|
|
6327
|
+
type: "select"
|
|
6328
|
+
}
|
|
6329
|
+
},
|
|
6330
|
+
region: {
|
|
6331
|
+
envKey: "HOT_UPDATER_SUPABASE_REGION",
|
|
6332
|
+
help: "Region used when creating a Supabase project",
|
|
6333
|
+
optional: true,
|
|
6334
|
+
prompt: {
|
|
6335
|
+
defaultValue: "us-east-1",
|
|
6336
|
+
message: "Select a region for the new Supabase project",
|
|
6337
|
+
type: "select"
|
|
6338
|
+
},
|
|
6339
|
+
validate: isSupabaseRegion
|
|
6340
|
+
},
|
|
6341
|
+
accessToken: {
|
|
6342
|
+
envKey: "SUPABASE_ACCESS_TOKEN",
|
|
6343
|
+
help: "Supabase personal access token",
|
|
6344
|
+
persistence: "with-consent",
|
|
6345
|
+
preflight: false,
|
|
6346
|
+
prompt: {
|
|
6347
|
+
message: "Enter your Supabase personal access token",
|
|
6348
|
+
type: "password"
|
|
6349
|
+
}
|
|
6350
|
+
},
|
|
6351
|
+
bucketName: {
|
|
6352
|
+
envKey: "HOT_UPDATER_SUPABASE_BUCKET_NAME",
|
|
6353
|
+
help: "Storage bucket name",
|
|
6354
|
+
prompt: {
|
|
6355
|
+
defaultValue: "hot-updater-storage",
|
|
6356
|
+
message: "Enter a name for the new storage bucket",
|
|
6357
|
+
placeholder: "hot-updater-storage",
|
|
6358
|
+
type: "text"
|
|
6359
|
+
}
|
|
6360
|
+
},
|
|
6361
|
+
functionName: {
|
|
6362
|
+
envKey: "HOT_UPDATER_SUPABASE_FUNCTION_NAME",
|
|
6363
|
+
help: "Edge Function name",
|
|
6364
|
+
prompt: {
|
|
6365
|
+
defaultValue: "update-server",
|
|
6366
|
+
message: "Enter a name for the edge function",
|
|
6367
|
+
placeholder: "update-server",
|
|
6368
|
+
type: "text"
|
|
6369
|
+
},
|
|
6370
|
+
validate: isSupabaseFunctionName
|
|
6371
|
+
},
|
|
6372
|
+
databasePassword: {
|
|
6373
|
+
envKey: "HOT_UPDATER_SUPABASE_DB_PASSWORD",
|
|
6374
|
+
help: "Database password, when required by the linked project",
|
|
6375
|
+
optional: true,
|
|
6376
|
+
persistence: "with-consent",
|
|
6377
|
+
prompt: {
|
|
6378
|
+
message: "Enter your Supabase database password (press Enter to skip if none)",
|
|
6379
|
+
type: "password"
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
};
|
|
6384
|
+
//#endregion
|
|
6279
6385
|
//#region iac/supabaseApi.ts
|
|
6280
6386
|
const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
|
|
6281
6387
|
const supabase = (0, _supabase_supabase_js.createClient)(supabaseUrl, supabaseServiceRoleKey);
|
|
@@ -6294,10 +6400,121 @@ const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
|
|
|
6294
6400
|
const { data, error } = await supabase.storage.createBucket(bucketName, options);
|
|
6295
6401
|
if (error) throw error;
|
|
6296
6402
|
return data;
|
|
6403
|
+
},
|
|
6404
|
+
updateBucket: async (bucketId, options) => {
|
|
6405
|
+
const { error } = await supabase.storage.updateBucket(bucketId, options);
|
|
6406
|
+
if (error) throw error;
|
|
6297
6407
|
}
|
|
6298
6408
|
};
|
|
6299
6409
|
};
|
|
6300
6410
|
//#endregion
|
|
6411
|
+
//#region iac/supabaseAuthentication.ts
|
|
6412
|
+
const SUPABASE_AUTH_METHOD = {
|
|
6413
|
+
accessToken: "access-token",
|
|
6414
|
+
cliLogin: "cli-login"
|
|
6415
|
+
};
|
|
6416
|
+
const SUPABASE_LOGIN_URL_PATTERN = /https:\/\/supabase\.com\/dashboard\/cli\/login\?[A-Za-z0-9._~!$&'()*+,;=:@/?%-]+/;
|
|
6417
|
+
const openBrowser = async (url) => {
|
|
6418
|
+
if (process.platform === "darwin") {
|
|
6419
|
+
await execa("open", [url]);
|
|
6420
|
+
return;
|
|
6421
|
+
}
|
|
6422
|
+
if (process.platform === "win32") {
|
|
6423
|
+
await execa("rundll32.exe", ["url.dll,FileProtocolHandler", url]);
|
|
6424
|
+
return;
|
|
6425
|
+
}
|
|
6426
|
+
await execa("xdg-open", [url]);
|
|
6427
|
+
};
|
|
6428
|
+
const hasValidSupabaseCliLogin = async () => {
|
|
6429
|
+
return (await execa("npx", [
|
|
6430
|
+
"-y",
|
|
6431
|
+
"supabase",
|
|
6432
|
+
"projects",
|
|
6433
|
+
"list",
|
|
6434
|
+
"--output",
|
|
6435
|
+
"json",
|
|
6436
|
+
"--agent",
|
|
6437
|
+
"no"
|
|
6438
|
+
], { reject: false })).exitCode === 0;
|
|
6439
|
+
};
|
|
6440
|
+
const getSupabaseCliEnv = (accessToken) => accessToken ? { [initProvider.inputs.accessToken.envKey]: accessToken } : void 0;
|
|
6441
|
+
const inputSupabaseAccessToken = async (accessToken) => {
|
|
6442
|
+
if (accessToken) return accessToken;
|
|
6443
|
+
const authMethod = await _hot_updater_cli_tools.p.select({
|
|
6444
|
+
message: "How do you want to authenticate with Supabase?",
|
|
6445
|
+
options: [{
|
|
6446
|
+
label: "Use Supabase CLI login",
|
|
6447
|
+
value: SUPABASE_AUTH_METHOD.cliLogin
|
|
6448
|
+
}, {
|
|
6449
|
+
label: "Enter a personal access token",
|
|
6450
|
+
value: SUPABASE_AUTH_METHOD.accessToken
|
|
6451
|
+
}]
|
|
6452
|
+
});
|
|
6453
|
+
if (_hot_updater_cli_tools.p.isCancel(authMethod)) process.exit(0);
|
|
6454
|
+
if (authMethod === SUPABASE_AUTH_METHOD.cliLogin) {
|
|
6455
|
+
if (await hasValidSupabaseCliLogin()) return;
|
|
6456
|
+
const loginProcess = execa("npx", [
|
|
6457
|
+
"-y",
|
|
6458
|
+
"supabase",
|
|
6459
|
+
"login",
|
|
6460
|
+
"--no-browser",
|
|
6461
|
+
"--agent",
|
|
6462
|
+
"no"
|
|
6463
|
+
], {
|
|
6464
|
+
stdin: "inherit",
|
|
6465
|
+
stderr: "inherit",
|
|
6466
|
+
stdout: "pipe"
|
|
6467
|
+
});
|
|
6468
|
+
let output = "";
|
|
6469
|
+
let browserOpenPromise;
|
|
6470
|
+
loginProcess.stdout?.on("data", (chunk) => {
|
|
6471
|
+
process.stdout.write(chunk);
|
|
6472
|
+
output += chunk.toString();
|
|
6473
|
+
const loginUrl = output.match(SUPABASE_LOGIN_URL_PATTERN)?.[0];
|
|
6474
|
+
if (loginUrl && !browserOpenPromise) browserOpenPromise = openBrowser(loginUrl).catch(() => {
|
|
6475
|
+
_hot_updater_cli_tools.p.log.warn("Could not open the Supabase login page automatically.");
|
|
6476
|
+
});
|
|
6477
|
+
});
|
|
6478
|
+
await loginProcess;
|
|
6479
|
+
await browserOpenPromise;
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
_hot_updater_cli_tools.p.log.step(`Personal access token: ${(0, _hot_updater_cli_tools.link)("https://supabase.com/dashboard/account/tokens")}`);
|
|
6483
|
+
const selectedAccessToken = await _hot_updater_cli_tools.p.password({
|
|
6484
|
+
message: "Enter your Supabase personal access token",
|
|
6485
|
+
validate: (value) => value ? void 0 : "Supabase access token is required"
|
|
6486
|
+
});
|
|
6487
|
+
if (_hot_updater_cli_tools.p.isCancel(selectedAccessToken)) process.exit(0);
|
|
6488
|
+
return selectedAccessToken;
|
|
6489
|
+
};
|
|
6490
|
+
//#endregion
|
|
6491
|
+
//#region iac/supabaseBucketPrivacy.ts
|
|
6492
|
+
var PublicSupabaseBucketError = class extends _hot_updater_cli_tools.InitError {
|
|
6493
|
+
name = "PublicSupabaseBucketError";
|
|
6494
|
+
constructor(bucketName) {
|
|
6495
|
+
super([
|
|
6496
|
+
`Supabase bucket "${bucketName}" is public.`,
|
|
6497
|
+
"Make the bucket private in Supabase Storage, then rerun init.",
|
|
6498
|
+
"Alternatively, rerun without --env-file to approve the change interactively."
|
|
6499
|
+
].join("\n"));
|
|
6500
|
+
this.bucketName = bucketName;
|
|
6501
|
+
}
|
|
6502
|
+
};
|
|
6503
|
+
const ensureSupabaseBucketPrivate = async ({ api, nonInteractive, selection }) => {
|
|
6504
|
+
if (selection.create || !selection.isPublic) return;
|
|
6505
|
+
if (nonInteractive) throw new PublicSupabaseBucketError(selection.name);
|
|
6506
|
+
const confirmed = await _hot_updater_cli_tools.p.confirm({
|
|
6507
|
+
message: `Bucket "${selection.name}" is public. Make it private?`,
|
|
6508
|
+
initialValue: true
|
|
6509
|
+
});
|
|
6510
|
+
if (_hot_updater_cli_tools.p.isCancel(confirmed) || !confirmed) {
|
|
6511
|
+
_hot_updater_cli_tools.p.log.info("Init cancelled.");
|
|
6512
|
+
process.exit(1);
|
|
6513
|
+
}
|
|
6514
|
+
await api.updateBucket(selection.id, { public: false });
|
|
6515
|
+
_hot_updater_cli_tools.p.log.success(`Bucket "${selection.name}" is now private.`);
|
|
6516
|
+
};
|
|
6517
|
+
//#endregion
|
|
6301
6518
|
//#region iac/supabaseCli.ts
|
|
6302
6519
|
const SUPABASE_CONFIG_TEMPLATE = `
|
|
6303
6520
|
project_id = "%%projectId%%"
|
|
@@ -6312,6 +6529,10 @@ const SUPABASE_DATABASE_AUTH_ERROR_PATTERNS = [
|
|
|
6312
6529
|
/SQLSTATE 28P01/i,
|
|
6313
6530
|
/invalid SCRAM server-final-message/i
|
|
6314
6531
|
];
|
|
6532
|
+
const getSupabaseCommandEnv = (accessToken, dbPassword) => accessToken || dbPassword ? {
|
|
6533
|
+
...getSupabaseCliEnv(accessToken),
|
|
6534
|
+
...dbPassword ? { SUPABASE_DB_PASSWORD: dbPassword } : {}
|
|
6535
|
+
} : void 0;
|
|
6315
6536
|
const isSupabaseDatabaseAuthError = (err) => {
|
|
6316
6537
|
const stderr = err.stderr;
|
|
6317
6538
|
return typeof stderr === "string" && SUPABASE_DATABASE_AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(stderr));
|
|
@@ -6319,11 +6540,18 @@ const isSupabaseDatabaseAuthError = (err) => {
|
|
|
6319
6540
|
const handleSupabaseDatabaseCommandError = (err, { dbPassword, stderrInherited = false }) => {
|
|
6320
6541
|
if (err instanceof ExecaError) if (dbPassword && isSupabaseDatabaseAuthError(err)) _hot_updater_cli_tools.p.log.error(SUPABASE_DATABASE_CONNECTION_ERROR);
|
|
6321
6542
|
else if (!stderrInherited && err.stderr) _hot_updater_cli_tools.p.log.error(err.stderr);
|
|
6322
|
-
else
|
|
6543
|
+
else _hot_updater_cli_tools.p.log.error(err.message);
|
|
6323
6544
|
else console.error(err);
|
|
6324
6545
|
process.exit(1);
|
|
6325
6546
|
};
|
|
6326
|
-
const
|
|
6547
|
+
const confirmSupabaseDatabaseMigrations = async ({ nonInteractive }) => {
|
|
6548
|
+
if (nonInteractive) return true;
|
|
6549
|
+
return await _hot_updater_cli_tools.p.confirm({
|
|
6550
|
+
message: "Apply Hot Updater database migrations to the selected Supabase project?",
|
|
6551
|
+
initialValue: true
|
|
6552
|
+
}) === true;
|
|
6553
|
+
};
|
|
6554
|
+
const linkSupabase = async (workdir, { accessToken, projectId, dbPassword }) => {
|
|
6327
6555
|
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
6328
6556
|
try {
|
|
6329
6557
|
await fs_promises.default.writeFile(path.default.join(workdir, "supabase", "config.toml"), (0, _hot_updater_cli_tools.transformTemplate)(SUPABASE_CONFIG_TEMPLATE, { projectId }));
|
|
@@ -6337,7 +6565,7 @@ const linkSupabase = async (workdir, { projectId, dbPassword }) => {
|
|
|
6337
6565
|
workdir
|
|
6338
6566
|
], {
|
|
6339
6567
|
cwd: workdir,
|
|
6340
|
-
env:
|
|
6568
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6341
6569
|
input: "",
|
|
6342
6570
|
stdio: [
|
|
6343
6571
|
"pipe",
|
|
@@ -6348,19 +6576,20 @@ const linkSupabase = async (workdir, { projectId, dbPassword }) => {
|
|
|
6348
6576
|
spinner.stop("Supabase linked ✔");
|
|
6349
6577
|
} catch (err) {
|
|
6350
6578
|
spinner.stop();
|
|
6351
|
-
handleSupabaseDatabaseCommandError(err, { dbPassword });
|
|
6579
|
+
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), { dbPassword });
|
|
6352
6580
|
}
|
|
6353
6581
|
};
|
|
6354
|
-
const pushDB = async (workdir, { dbPassword }) => {
|
|
6582
|
+
const pushDB = async (workdir, { accessToken, dbPassword }) => {
|
|
6355
6583
|
try {
|
|
6356
6584
|
const dbPush = await execa("npx", [
|
|
6357
6585
|
"supabase",
|
|
6358
6586
|
"db",
|
|
6359
6587
|
"push",
|
|
6360
|
-
"--include-all"
|
|
6588
|
+
"--include-all",
|
|
6589
|
+
"--yes"
|
|
6361
6590
|
], {
|
|
6362
6591
|
cwd: workdir,
|
|
6363
|
-
env:
|
|
6592
|
+
env: getSupabaseCommandEnv(accessToken, dbPassword),
|
|
6364
6593
|
stderr: ["pipe", "inherit"],
|
|
6365
6594
|
stdin: "inherit",
|
|
6366
6595
|
stdout: "inherit"
|
|
@@ -6368,17 +6597,273 @@ const pushDB = async (workdir, { dbPassword }) => {
|
|
|
6368
6597
|
_hot_updater_cli_tools.p.log.success("DB pushed ✔");
|
|
6369
6598
|
return dbPush.stdout;
|
|
6370
6599
|
} catch (err) {
|
|
6371
|
-
handleSupabaseDatabaseCommandError(err, {
|
|
6600
|
+
handleSupabaseDatabaseCommandError(err instanceof Error ? err : new Error(String(err)), {
|
|
6372
6601
|
dbPassword,
|
|
6373
6602
|
stderrInherited: true
|
|
6374
6603
|
});
|
|
6375
6604
|
}
|
|
6376
6605
|
};
|
|
6377
6606
|
//#endregion
|
|
6607
|
+
//#region iac/supabaseDeploymentInputs.ts
|
|
6608
|
+
const assertSupabaseNonInteractiveInputs = async (inputs, nonInteractive, validateCliLogin = hasValidSupabaseCliLogin) => {
|
|
6609
|
+
(0, _hot_updater_cli_tools.assertInitProviderInputs)({
|
|
6610
|
+
inputs: nonInteractive && inputs.accessToken === void 0 && await validateCliLogin() ? {
|
|
6611
|
+
...inputs,
|
|
6612
|
+
accessToken: "supabase-cli-login"
|
|
6613
|
+
} : inputs,
|
|
6614
|
+
provider: initProvider,
|
|
6615
|
+
strict: nonInteractive
|
|
6616
|
+
});
|
|
6617
|
+
};
|
|
6618
|
+
const inputSupabaseDeploymentInputs = async ({ accessToken, functionName, nonInteractive }) => {
|
|
6619
|
+
const { inputs } = initProvider;
|
|
6620
|
+
(0, _hot_updater_cli_tools.assertInitInputs)({
|
|
6621
|
+
inputs: { [inputs.functionName.envKey]: functionName },
|
|
6622
|
+
strict: nonInteractive
|
|
6623
|
+
});
|
|
6624
|
+
if (nonInteractive && functionName) return {
|
|
6625
|
+
accessToken,
|
|
6626
|
+
functionName
|
|
6627
|
+
};
|
|
6628
|
+
const savedFunctionName = isSupabaseFunctionName(functionName) ? functionName : void 0;
|
|
6629
|
+
return _hot_updater_cli_tools.p.group({
|
|
6630
|
+
accessToken: () => nonInteractive && accessToken ? Promise.resolve(accessToken) : inputSupabaseAccessToken(),
|
|
6631
|
+
functionName: () => nonInteractive && savedFunctionName ? Promise.resolve(savedFunctionName) : _hot_updater_cli_tools.p.text({
|
|
6632
|
+
...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(inputs.functionName.prompt, savedFunctionName),
|
|
6633
|
+
message: inputs.functionName.prompt.message,
|
|
6634
|
+
validate: (value) => isSupabaseFunctionName(value) ? void 0 : "Start with a letter and use only letters, numbers, underscores, or hyphens"
|
|
6635
|
+
})
|
|
6636
|
+
}, { onCancel: () => process.exit(0) });
|
|
6637
|
+
};
|
|
6638
|
+
//#endregion
|
|
6639
|
+
//#region iac/supabaseInitInputs.ts
|
|
6640
|
+
const resolveSupabaseInitInputs = (existingEnv, sources = {}) => {
|
|
6641
|
+
const { inputs } = initProvider;
|
|
6642
|
+
const managedEnv = sources.managedEnv ?? existingEnv;
|
|
6643
|
+
const projectId = (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.projectId) ?? (0, _hot_updater_cli_tools.getHotUpdaterEnvValue)(existingEnv, "HOT_UPDATER_SUPABASE_URL")?.match(/^https:\/\/([^.]+)\.supabase\.co/)?.[1];
|
|
6644
|
+
const databasePasswordKey = inputs.databasePassword.envKey;
|
|
6645
|
+
const hasProcessDatabasePassword = process.env[databasePasswordKey] !== void 0;
|
|
6646
|
+
const hasInputDatabasePassword = sources.inputEnv !== void 0 && Object.hasOwn(sources.inputEnv, databasePasswordKey);
|
|
6647
|
+
const managedDatabasePasswordProjectId = managedEnv[SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY]?.trim();
|
|
6648
|
+
const region = (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.region);
|
|
6649
|
+
const databasePassword = hasProcessDatabasePassword ? process.env[databasePasswordKey]?.trim() || void 0 : hasInputDatabasePassword ? sources.inputEnv?.[databasePasswordKey]?.trim() || void 0 : managedDatabasePasswordProjectId === projectId ? managedEnv[databasePasswordKey]?.trim() || void 0 : void 0;
|
|
6650
|
+
return {
|
|
6651
|
+
accessToken: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.accessToken),
|
|
6652
|
+
bucketName: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.bucketName),
|
|
6653
|
+
databasePassword,
|
|
6654
|
+
functionName: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.functionName),
|
|
6655
|
+
organizationSlug: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.organizationSlug),
|
|
6656
|
+
projectId,
|
|
6657
|
+
projectName: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.projectName),
|
|
6658
|
+
region: isSupabaseRegion(region) ? region : void 0
|
|
6659
|
+
};
|
|
6660
|
+
};
|
|
6661
|
+
const inputSupabaseDatabasePassword = async ({ cliHandlesPrompt = false, databasePassword, nonInteractive, required = false }) => {
|
|
6662
|
+
if (cliHandlesPrompt) return "";
|
|
6663
|
+
if (nonInteractive) {
|
|
6664
|
+
if (required && !databasePassword) throw new _hot_updater_cli_tools.MissingInitInputsError([initProvider.inputs.databasePassword.envKey]);
|
|
6665
|
+
return databasePassword ?? "";
|
|
6666
|
+
}
|
|
6667
|
+
const password = await _hot_updater_cli_tools.p.password({
|
|
6668
|
+
message: initProvider.inputs.databasePassword.prompt.message,
|
|
6669
|
+
validate: (value) => required && !value ? "A database password is required to create a Supabase project" : void 0
|
|
6670
|
+
});
|
|
6671
|
+
if (_hot_updater_cli_tools.p.isCancel(password)) process.exit(0);
|
|
6672
|
+
return password;
|
|
6673
|
+
};
|
|
6674
|
+
const inputSupabaseProjectCreationInputs = async ({ bucketName, organizationSlug, organizations, projectName, region }) => {
|
|
6675
|
+
if (organizations.length === 0) throw new Error("No Supabase organization is available for project creation.");
|
|
6676
|
+
const savedOrganization = organizations.find((organization) => organization.slug === organizationSlug);
|
|
6677
|
+
if (organizationSlug && !savedOrganization) _hot_updater_cli_tools.p.log.warn("Saved Supabase organization was not found. Select an organization again.");
|
|
6678
|
+
const defaultRegion = initProvider.inputs.region.prompt.defaultValue;
|
|
6679
|
+
const selectedProjectName = await _hot_updater_cli_tools.p.text({
|
|
6680
|
+
...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(initProvider.inputs.projectName.prompt, projectName),
|
|
6681
|
+
message: initProvider.inputs.projectName.prompt.message,
|
|
6682
|
+
validate: (value) => value ? void 0 : "Supabase project name is required"
|
|
6683
|
+
});
|
|
6684
|
+
if (_hot_updater_cli_tools.p.isCancel(selectedProjectName)) process.exit(0);
|
|
6685
|
+
const selectedOrganizationSlug = await _hot_updater_cli_tools.p.select({
|
|
6686
|
+
initialValue: savedOrganization?.slug ?? organizations[0]?.slug,
|
|
6687
|
+
message: initProvider.inputs.organizationSlug.prompt.message,
|
|
6688
|
+
options: organizations.map((organization) => ({
|
|
6689
|
+
label: organization.name,
|
|
6690
|
+
value: organization.slug
|
|
6691
|
+
}))
|
|
6692
|
+
});
|
|
6693
|
+
if (_hot_updater_cli_tools.p.isCancel(selectedOrganizationSlug)) process.exit(0);
|
|
6694
|
+
const selectedRegion = await _hot_updater_cli_tools.p.select({
|
|
6695
|
+
message: initProvider.inputs.region.prompt.message,
|
|
6696
|
+
initialValue: region ?? (isSupabaseRegion(defaultRegion) ? defaultRegion : void 0),
|
|
6697
|
+
options: SUPABASE_REGION_VALUES.map((value) => ({
|
|
6698
|
+
label: value,
|
|
6699
|
+
value
|
|
6700
|
+
}))
|
|
6701
|
+
});
|
|
6702
|
+
if (_hot_updater_cli_tools.p.isCancel(selectedRegion)) process.exit(0);
|
|
6703
|
+
const selectedBucketName = await _hot_updater_cli_tools.p.text({
|
|
6704
|
+
...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(initProvider.inputs.bucketName.prompt, bucketName),
|
|
6705
|
+
message: initProvider.inputs.bucketName.prompt.message,
|
|
6706
|
+
validate: (value) => value ? void 0 : "Storage bucket name is required"
|
|
6707
|
+
});
|
|
6708
|
+
if (_hot_updater_cli_tools.p.isCancel(selectedBucketName)) process.exit(0);
|
|
6709
|
+
return {
|
|
6710
|
+
bucketName: selectedBucketName,
|
|
6711
|
+
organizationSlug: selectedOrganizationSlug,
|
|
6712
|
+
projectName: selectedProjectName,
|
|
6713
|
+
region: selectedRegion
|
|
6714
|
+
};
|
|
6715
|
+
};
|
|
6716
|
+
//#endregion
|
|
6717
|
+
//#region iac/supabaseCliManagementApi.ts
|
|
6718
|
+
var SupabaseCliResponseError = class extends Error {
|
|
6719
|
+
constructor(message) {
|
|
6720
|
+
super(message);
|
|
6721
|
+
this.name = "SupabaseCliResponseError";
|
|
6722
|
+
}
|
|
6723
|
+
};
|
|
6724
|
+
const isRecord$1 = (value) => typeof value === "object" && value !== null;
|
|
6725
|
+
const runSupabaseCli = async (args) => {
|
|
6726
|
+
const result = await execa("npx", [
|
|
6727
|
+
"-y",
|
|
6728
|
+
"supabase",
|
|
6729
|
+
...args
|
|
6730
|
+
], { env: void 0 });
|
|
6731
|
+
return JSON.parse(result.stdout);
|
|
6732
|
+
};
|
|
6733
|
+
const supabaseCliManagementApi = () => ({
|
|
6734
|
+
listOrganizations: async () => {
|
|
6735
|
+
const body = await runSupabaseCli([
|
|
6736
|
+
"orgs",
|
|
6737
|
+
"list",
|
|
6738
|
+
"--output",
|
|
6739
|
+
"json"
|
|
6740
|
+
]);
|
|
6741
|
+
if (!Array.isArray(body)) throw new SupabaseCliResponseError("Supabase organizations response was invalid.");
|
|
6742
|
+
return body.flatMap((organization) => isRecord$1(organization) && typeof organization.id === "string" && typeof organization.name === "string" && typeof organization.slug === "string" ? [{
|
|
6743
|
+
id: organization.id,
|
|
6744
|
+
name: organization.name,
|
|
6745
|
+
slug: organization.slug
|
|
6746
|
+
}] : []);
|
|
6747
|
+
},
|
|
6748
|
+
createProject: async ({ name, organizationSlug, region }) => {
|
|
6749
|
+
await execa("npx", [
|
|
6750
|
+
"-y",
|
|
6751
|
+
"supabase",
|
|
6752
|
+
"projects",
|
|
6753
|
+
"create",
|
|
6754
|
+
name,
|
|
6755
|
+
"--org-id",
|
|
6756
|
+
organizationSlug,
|
|
6757
|
+
"--region",
|
|
6758
|
+
region,
|
|
6759
|
+
"--agent",
|
|
6760
|
+
"no"
|
|
6761
|
+
], { stdio: "inherit" });
|
|
6762
|
+
const body = await runSupabaseCli([
|
|
6763
|
+
"projects",
|
|
6764
|
+
"list",
|
|
6765
|
+
"--output",
|
|
6766
|
+
"json"
|
|
6767
|
+
]);
|
|
6768
|
+
if (!Array.isArray(body)) throw new SupabaseCliResponseError("Supabase projects response was invalid after project creation.");
|
|
6769
|
+
const project = body.find((candidate) => isRecord$1(candidate) && candidate.name === name && candidate.organization_slug === organizationSlug && candidate.region === region);
|
|
6770
|
+
if (!isRecord$1(project) || typeof project.id !== "string" || typeof project.name !== "string" || typeof project.region !== "string") throw new SupabaseCliResponseError("Created Supabase project was not found.");
|
|
6771
|
+
return {
|
|
6772
|
+
id: project.id,
|
|
6773
|
+
name: project.name,
|
|
6774
|
+
region: project.region
|
|
6775
|
+
};
|
|
6776
|
+
},
|
|
6777
|
+
getProjectStatus: async (projectId) => {
|
|
6778
|
+
const body = await runSupabaseCli([
|
|
6779
|
+
"projects",
|
|
6780
|
+
"list",
|
|
6781
|
+
"--output",
|
|
6782
|
+
"json"
|
|
6783
|
+
]);
|
|
6784
|
+
if (!Array.isArray(body)) throw new SupabaseCliResponseError("Supabase projects response was invalid.");
|
|
6785
|
+
const project = body.find((candidate) => isRecord$1(candidate) && (candidate.id === projectId || candidate.ref === projectId));
|
|
6786
|
+
if (!isRecord$1(project) || typeof project.status !== "string") throw new SupabaseCliResponseError("Supabase project status was not found.");
|
|
6787
|
+
return project.status;
|
|
6788
|
+
}
|
|
6789
|
+
});
|
|
6790
|
+
//#endregion
|
|
6791
|
+
//#region iac/supabaseManagementApi.ts
|
|
6792
|
+
const SUPABASE_MANAGEMENT_API_URL = "https://api.supabase.com/v1";
|
|
6793
|
+
const SUPABASE_MANAGEMENT_API_TIMEOUT_MS = 3e4;
|
|
6794
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
6795
|
+
var SupabaseManagementApiStatusError = class extends Error {};
|
|
6796
|
+
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 });
|
|
6797
|
+
const request = async (accessToken, path, init) => {
|
|
6798
|
+
const method = init?.method ?? "GET";
|
|
6799
|
+
const controller = new AbortController();
|
|
6800
|
+
const timeout = setTimeout(() => controller.abort(), SUPABASE_MANAGEMENT_API_TIMEOUT_MS);
|
|
6801
|
+
timeout.unref();
|
|
6802
|
+
try {
|
|
6803
|
+
const response = await fetch(`${SUPABASE_MANAGEMENT_API_URL}${path}`, {
|
|
6804
|
+
body: init?.body ? JSON.stringify(init.body) : void 0,
|
|
6805
|
+
headers: {
|
|
6806
|
+
Authorization: `Bearer ${accessToken}`,
|
|
6807
|
+
"Content-Type": "application/json"
|
|
6808
|
+
},
|
|
6809
|
+
method,
|
|
6810
|
+
signal: controller.signal
|
|
6811
|
+
});
|
|
6812
|
+
if (!response.ok) throw new SupabaseManagementApiStatusError(`Supabase Management API request failed with status ${response.status}.`);
|
|
6813
|
+
return await response.json();
|
|
6814
|
+
} catch (error) {
|
|
6815
|
+
if (!controller.signal.aborted) {
|
|
6816
|
+
if (method === "POST" && !(error instanceof SupabaseManagementApiStatusError)) throw projectCreationMayHaveSucceededError(void 0, error);
|
|
6817
|
+
throw error;
|
|
6818
|
+
}
|
|
6819
|
+
if (method === "POST") throw projectCreationMayHaveSucceededError("Supabase project creation timed out.", error);
|
|
6820
|
+
throw new Error("Supabase Management API request timed out.");
|
|
6821
|
+
} finally {
|
|
6822
|
+
clearTimeout(timeout);
|
|
6823
|
+
}
|
|
6824
|
+
};
|
|
6825
|
+
const supabaseManagementApi = (accessToken) => accessToken === void 0 ? supabaseCliManagementApi() : {
|
|
6826
|
+
listOrganizations: async () => {
|
|
6827
|
+
const body = await request(accessToken, "/organizations");
|
|
6828
|
+
if (!Array.isArray(body)) throw new Error("Supabase organizations response was invalid.");
|
|
6829
|
+
return body.flatMap((organization) => isRecord(organization) && typeof organization.id === "string" && typeof organization.name === "string" && typeof organization.slug === "string" ? [{
|
|
6830
|
+
id: organization.id,
|
|
6831
|
+
name: organization.name,
|
|
6832
|
+
slug: organization.slug
|
|
6833
|
+
}] : []);
|
|
6834
|
+
},
|
|
6835
|
+
createProject: async ({ databasePassword, name, organizationSlug, region }) => {
|
|
6836
|
+
const body = await request(accessToken, "/projects", {
|
|
6837
|
+
body: {
|
|
6838
|
+
db_pass: databasePassword,
|
|
6839
|
+
name,
|
|
6840
|
+
organization_slug: organizationSlug,
|
|
6841
|
+
region
|
|
6842
|
+
},
|
|
6843
|
+
method: "POST"
|
|
6844
|
+
});
|
|
6845
|
+
if (!isRecord(body) || typeof body.ref !== "string" || typeof body.name !== "string" || typeof body.region !== "string") throw projectCreationMayHaveSucceededError("Supabase project creation response was invalid.");
|
|
6846
|
+
return {
|
|
6847
|
+
id: body.ref,
|
|
6848
|
+
name: body.name,
|
|
6849
|
+
region: body.region
|
|
6850
|
+
};
|
|
6851
|
+
},
|
|
6852
|
+
getProjectStatus: async (projectId) => {
|
|
6853
|
+
const body = await request(accessToken, `/projects/${encodeURIComponent(projectId)}`);
|
|
6854
|
+
if (!isRecord(body) || typeof body.status !== "string") throw new Error("Supabase project response was invalid.");
|
|
6855
|
+
return body.status;
|
|
6856
|
+
}
|
|
6857
|
+
};
|
|
6858
|
+
//#endregion
|
|
6378
6859
|
//#region iac/index.ts
|
|
6379
6860
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
6380
6861
|
const EDGE_VENDOR_DIR = "_hot-updater";
|
|
6381
6862
|
const WORKSPACE_PACKAGE_PREFIX = "@hot-updater/";
|
|
6863
|
+
const SUPABASE_PROJECT_READY_STATUS = "ACTIVE_HEALTHY";
|
|
6864
|
+
const SUPABASE_PROJECT_PROVISIONING_STATUS = "COMING_UP";
|
|
6865
|
+
const SUPABASE_PROJECT_READINESS_MAX_ATTEMPTS = 300;
|
|
6866
|
+
const SUPABASE_PROJECT_READINESS_POLL_INTERVAL_MS = 1e3;
|
|
6382
6867
|
const STATIC_IMPORT_SPECIFIER_PATTERN = /^\s*(?:import|export)\s+(?:type\s+)?(?:[^"'`]+?\s+from\s+)?["']([^"']+)["'];?/gm;
|
|
6383
6868
|
const DYNAMIC_IMPORT_SPECIFIER_PATTERN = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
6384
6869
|
const getConfigScaffold = (build) => {
|
|
@@ -6556,7 +7041,7 @@ const buildEdgeFunctionImports = async (targetDir) => {
|
|
|
6556
7041
|
const resolveEdgeFunctionDenoConfig = async (targetDir) => {
|
|
6557
7042
|
return { imports: await buildEdgeFunctionImports(targetDir) };
|
|
6558
7043
|
};
|
|
6559
|
-
const selectProject = async () => {
|
|
7044
|
+
const selectProject = async (preferredProjectId, nonInteractive = false, accessToken) => {
|
|
6560
7045
|
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
6561
7046
|
spinner.start("Fetching Supabase projects...");
|
|
6562
7047
|
let projectsProcess = [];
|
|
@@ -6568,16 +7053,28 @@ const selectProject = async () => {
|
|
|
6568
7053
|
"list",
|
|
6569
7054
|
"--output",
|
|
6570
7055
|
"json"
|
|
6571
|
-
], {});
|
|
7056
|
+
], { env: accessToken ? { [initProvider.inputs.accessToken.envKey]: accessToken } : void 0 });
|
|
6572
7057
|
projectsProcess = listProjects.stdout === "null" ? [] : JSON.parse(listProjects?.stdout ?? "[]");
|
|
6573
7058
|
} catch (err) {
|
|
6574
7059
|
spinner.stop();
|
|
6575
|
-
|
|
7060
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7061
|
+
console.error(`Failed to fetch Supabase projects: ${message}`);
|
|
6576
7062
|
process.exit(1);
|
|
6577
7063
|
}
|
|
6578
7064
|
spinner.stop();
|
|
7065
|
+
const preferredProject = projectsProcess.find((project) => project.id === preferredProjectId);
|
|
7066
|
+
if (nonInteractive && preferredProject) {
|
|
7067
|
+
_hot_updater_cli_tools.p.log.info(`Using saved Supabase project: ${preferredProject.name}`);
|
|
7068
|
+
return {
|
|
7069
|
+
create: false,
|
|
7070
|
+
project: preferredProject
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
if (preferredProjectId && !preferredProject) _hot_updater_cli_tools.p.log.warn("Saved Supabase project was not found. Select a project again.");
|
|
7074
|
+
if (nonInteractive) throw new _hot_updater_cli_tools.MissingInitInputsError(["HOT_UPDATER_SUPABASE_PROJECT_ID"]);
|
|
6579
7075
|
const createProjectOption = `create/${Math.random().toString(36).substring(2, 15)}`;
|
|
6580
7076
|
const selectedProjectId = await _hot_updater_cli_tools.p.select({
|
|
7077
|
+
initialValue: preferredProject?.id ?? projectsProcess[0]?.id,
|
|
6581
7078
|
message: "Select a Supabase project",
|
|
6582
7079
|
options: [...projectsProcess.map((project) => ({
|
|
6583
7080
|
label: `${project.name} (${project.region})`,
|
|
@@ -6588,29 +7085,15 @@ const selectProject = async () => {
|
|
|
6588
7085
|
}]
|
|
6589
7086
|
});
|
|
6590
7087
|
if (_hot_updater_cli_tools.p.isCancel(selectedProjectId)) process.exit(0);
|
|
6591
|
-
if (selectedProjectId === createProjectOption) {
|
|
6592
|
-
try {
|
|
6593
|
-
await execa("npx", [
|
|
6594
|
-
"-y",
|
|
6595
|
-
"supabase",
|
|
6596
|
-
"projects",
|
|
6597
|
-
"create"
|
|
6598
|
-
], {
|
|
6599
|
-
stdio: "inherit",
|
|
6600
|
-
shell: true
|
|
6601
|
-
});
|
|
6602
|
-
} catch (err) {
|
|
6603
|
-
if (err instanceof ExecaError) console.error(err.stderr);
|
|
6604
|
-
else console.error(err);
|
|
6605
|
-
process.exit(1);
|
|
6606
|
-
}
|
|
6607
|
-
return selectProject();
|
|
6608
|
-
}
|
|
7088
|
+
if (selectedProjectId === createProjectOption) return { create: true };
|
|
6609
7089
|
const selectedProject = projectsProcess.find((project) => project.id === selectedProjectId);
|
|
6610
7090
|
if (!selectedProject) throw new Error("Project not found");
|
|
6611
|
-
return
|
|
7091
|
+
return {
|
|
7092
|
+
create: false,
|
|
7093
|
+
project: selectedProject
|
|
7094
|
+
};
|
|
6612
7095
|
};
|
|
6613
|
-
const selectBucket = async (api) => {
|
|
7096
|
+
const selectBucket = async (api, preferredBucketName, nonInteractive = false) => {
|
|
6614
7097
|
let buckets = [];
|
|
6615
7098
|
let retryCount = 0;
|
|
6616
7099
|
await _hot_updater_cli_tools.p.tasks([{
|
|
@@ -6628,15 +7111,31 @@ const selectBucket = async (api) => {
|
|
|
6628
7111
|
process.exit(1);
|
|
6629
7112
|
}
|
|
6630
7113
|
}]);
|
|
7114
|
+
const preferredBucket = buckets.find((bucket) => bucket.name === preferredBucketName);
|
|
7115
|
+
if (nonInteractive && preferredBucket) {
|
|
7116
|
+
_hot_updater_cli_tools.p.log.info(`Using saved Supabase bucket: ${preferredBucket.name}`);
|
|
7117
|
+
return {
|
|
7118
|
+
create: false,
|
|
7119
|
+
id: preferredBucket.id,
|
|
7120
|
+
isPublic: preferredBucket.isPublic,
|
|
7121
|
+
name: preferredBucket.name
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7124
|
+
if (preferredBucketName && !preferredBucket) {
|
|
7125
|
+
if (nonInteractive) return {
|
|
7126
|
+
create: true,
|
|
7127
|
+
name: preferredBucketName
|
|
7128
|
+
};
|
|
7129
|
+
_hot_updater_cli_tools.p.log.warn("Saved Supabase bucket was not found. Select a bucket again.");
|
|
7130
|
+
}
|
|
7131
|
+
if (nonInteractive) throw new _hot_updater_cli_tools.MissingInitInputsError(["HOT_UPDATER_SUPABASE_BUCKET_NAME"]);
|
|
6631
7132
|
const createBucketOption = `create/${Math.random().toString(36).substring(2, 15)}`;
|
|
6632
7133
|
const selectedBucketId = await _hot_updater_cli_tools.p.select({
|
|
7134
|
+
initialValue: preferredBucket?.id ?? buckets[0]?.id,
|
|
6633
7135
|
message: "Select a storage bucket",
|
|
6634
7136
|
options: [...buckets.map((bucket) => ({
|
|
6635
7137
|
label: bucket.name,
|
|
6636
|
-
value:
|
|
6637
|
-
id: bucket.id,
|
|
6638
|
-
name: bucket.name
|
|
6639
|
-
})
|
|
7138
|
+
value: bucket.id
|
|
6640
7139
|
})), {
|
|
6641
7140
|
label: "Create a new private bucket",
|
|
6642
7141
|
value: createBucketOption
|
|
@@ -6644,34 +7143,44 @@ const selectBucket = async (api) => {
|
|
|
6644
7143
|
});
|
|
6645
7144
|
if (_hot_updater_cli_tools.p.isCancel(selectedBucketId)) process.exit(0);
|
|
6646
7145
|
if (selectedBucketId === createBucketOption) {
|
|
6647
|
-
const
|
|
7146
|
+
const prompt = initProvider.inputs.bucketName.prompt;
|
|
7147
|
+
const bucketName = await _hot_updater_cli_tools.p.text({
|
|
7148
|
+
...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(prompt, preferredBucketName),
|
|
7149
|
+
message: prompt.message
|
|
7150
|
+
});
|
|
6648
7151
|
if (_hot_updater_cli_tools.p.isCancel(bucketName)) process.exit(0);
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
if (!newBucket) throw new Error("Failed to create and select new bucket");
|
|
6654
|
-
return {
|
|
6655
|
-
id: newBucket.id,
|
|
6656
|
-
name: newBucket.name
|
|
6657
|
-
};
|
|
6658
|
-
} catch (err) {
|
|
6659
|
-
_hot_updater_cli_tools.p.log.error(`Failed to create new bucket: ${err}`);
|
|
6660
|
-
process.exit(1);
|
|
6661
|
-
}
|
|
7152
|
+
return {
|
|
7153
|
+
create: true,
|
|
7154
|
+
name: bucketName
|
|
7155
|
+
};
|
|
6662
7156
|
}
|
|
6663
|
-
|
|
7157
|
+
const selectedBucket = buckets.find((bucket) => bucket.id === selectedBucketId);
|
|
7158
|
+
if (!selectedBucket) throw new Error("Selected Supabase bucket was not found.");
|
|
7159
|
+
return {
|
|
7160
|
+
create: false,
|
|
7161
|
+
id: selectedBucket.id,
|
|
7162
|
+
isPublic: selectedBucket.isPublic,
|
|
7163
|
+
name: selectedBucket.name
|
|
7164
|
+
};
|
|
6664
7165
|
};
|
|
6665
|
-
const
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
7166
|
+
const createSelectedBucket = async (api, selection) => {
|
|
7167
|
+
if (!selection.create) return selection;
|
|
7168
|
+
await api.createBucket(selection.name, { public: false });
|
|
7169
|
+
_hot_updater_cli_tools.p.log.success(`Bucket "${selection.name}" created successfully.`);
|
|
7170
|
+
const bucket = (await api.listBuckets()).find((item) => item.name === selection.name);
|
|
7171
|
+
if (!bucket) throw new Error("Failed to create and select new bucket");
|
|
7172
|
+
return {
|
|
7173
|
+
id: bucket.id,
|
|
7174
|
+
name: bucket.name
|
|
7175
|
+
};
|
|
7176
|
+
};
|
|
7177
|
+
const deployEdgeFunction = async (accessToken, workdir, projectId, functionName) => {
|
|
6672
7178
|
const edgeFunctionsLibPath = path.default.join(workdir, "supabase", "edge-functions");
|
|
6673
7179
|
const edgeFunctionsCode = (0, _hot_updater_cli_tools.transformEnv)(path.default.join(edgeFunctionsLibPath, "index.ts"), { FUNCTION_NAME: functionName });
|
|
6674
|
-
|
|
7180
|
+
if (!isSupabaseFunctionName(functionName)) throw new Error("Invalid Supabase Edge Function name.");
|
|
7181
|
+
const functionsDir = path.default.resolve(workdir, "supabase", "functions");
|
|
7182
|
+
const targetDir = path.default.resolve(functionsDir, functionName);
|
|
7183
|
+
if (!targetDir.startsWith(`${functionsDir}${path.default.sep}`)) throw new Error("Supabase Edge Function path escaped its output directory.");
|
|
6675
7184
|
await fs_promises.default.mkdir(targetDir, { recursive: true });
|
|
6676
7185
|
const denoConfig = await resolveEdgeFunctionDenoConfig(targetDir);
|
|
6677
7186
|
const targetPath = path.default.join(targetDir, "index.ts");
|
|
@@ -6691,21 +7200,31 @@ const deployEdgeFunction = async (workdir, projectId) => {
|
|
|
6691
7200
|
"--no-verify-jwt",
|
|
6692
7201
|
"--workdir",
|
|
6693
7202
|
workdir
|
|
6694
|
-
], {
|
|
7203
|
+
], {
|
|
7204
|
+
cwd: workdir,
|
|
7205
|
+
env: getSupabaseCliEnv(accessToken)
|
|
7206
|
+
})).stdout;
|
|
6695
7207
|
} catch (err) {
|
|
6696
7208
|
if (err instanceof ExecaError && err.stderr) _hot_updater_cli_tools.p.log.error(err.stderr);
|
|
6697
|
-
else
|
|
7209
|
+
else if (err instanceof Error) _hot_updater_cli_tools.p.log.error(err.message);
|
|
6698
7210
|
process.exit(1);
|
|
6699
7211
|
}
|
|
6700
7212
|
}
|
|
6701
7213
|
}]);
|
|
6702
7214
|
};
|
|
6703
|
-
const
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
7215
|
+
const waitForSupabaseProjectReady = async ({ getProjectStatus, maxAttempts = SUPABASE_PROJECT_READINESS_MAX_ATTEMPTS, onLongWait, pollIntervalMs = SUPABASE_PROJECT_READINESS_POLL_INTERVAL_MS }) => {
|
|
7216
|
+
let lastStatus;
|
|
7217
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
7218
|
+
lastStatus = await getProjectStatus();
|
|
7219
|
+
if (lastStatus === SUPABASE_PROJECT_READY_STATUS) return;
|
|
7220
|
+
if (lastStatus !== SUPABASE_PROJECT_PROVISIONING_STATUS) throw new Error(`Supabase project entered unexpected status: ${lastStatus}.`);
|
|
7221
|
+
if (attempt === 5) onLongWait();
|
|
7222
|
+
if (attempt < maxAttempts - 1) await delay(pollIntervalMs);
|
|
7223
|
+
}
|
|
7224
|
+
throw new Error(`Timed out while waiting for the Supabase project. Last status: ${lastStatus ?? "unknown"}.`);
|
|
7225
|
+
};
|
|
7226
|
+
const getSupabaseProjectAccess = async ({ accessToken, managementApi, project, waitForProject }) => {
|
|
7227
|
+
const getServiceRoleApiKey = async () => {
|
|
6709
7228
|
const keysProcess = await execa("npx", [
|
|
6710
7229
|
"-y",
|
|
6711
7230
|
"supabase",
|
|
@@ -6715,17 +7234,146 @@ const runInit = async ({ build }) => {
|
|
|
6715
7234
|
project.id,
|
|
6716
7235
|
"--output",
|
|
6717
7236
|
"json"
|
|
6718
|
-
]);
|
|
6719
|
-
|
|
6720
|
-
}
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
7237
|
+
], { env: getSupabaseCliEnv(accessToken) });
|
|
7238
|
+
return JSON.parse(keysProcess.stdout ?? "[]").find((key) => key.name === "service_role")?.api_key;
|
|
7239
|
+
};
|
|
7240
|
+
let serviceRoleApiKey;
|
|
7241
|
+
if (waitForProject) await _hot_updater_cli_tools.p.tasks([{
|
|
7242
|
+
title: `Waiting for ${project.name} to become ready...`,
|
|
7243
|
+
task: async (message) => {
|
|
7244
|
+
await waitForSupabaseProjectReady({
|
|
7245
|
+
getProjectStatus: () => managementApi.getProjectStatus(project.id),
|
|
7246
|
+
onLongWait: () => {
|
|
7247
|
+
message("Supabase project is still provisioning. This might take a few minutes.");
|
|
7248
|
+
}
|
|
7249
|
+
});
|
|
7250
|
+
serviceRoleApiKey = await getServiceRoleApiKey();
|
|
7251
|
+
return "Supabase project is ready.";
|
|
7252
|
+
}
|
|
7253
|
+
}]);
|
|
7254
|
+
else {
|
|
7255
|
+
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
7256
|
+
spinner.start(`Getting API keys for ${project.name}...`);
|
|
7257
|
+
try {
|
|
7258
|
+
serviceRoleApiKey = await getServiceRoleApiKey();
|
|
7259
|
+
} catch (error) {
|
|
7260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7261
|
+
throw new Error(`Failed to get Supabase API keys: ${message}`);
|
|
7262
|
+
} finally {
|
|
7263
|
+
spinner.stop();
|
|
7264
|
+
}
|
|
6724
7265
|
}
|
|
6725
|
-
spinner.stop();
|
|
6726
|
-
const serviceRoleApiKey = apiKeys.find((key) => key.name === "service_role");
|
|
6727
7266
|
if (!serviceRoleApiKey) throw new Error("Service role key not found, is your project paused?");
|
|
6728
|
-
|
|
7267
|
+
return {
|
|
7268
|
+
api: supabaseApi(`https://${project.id}.supabase.co`, serviceRoleApiKey),
|
|
7269
|
+
serviceRoleApiKey
|
|
7270
|
+
};
|
|
7271
|
+
};
|
|
7272
|
+
const runInit = async ({ build, envFile }) => {
|
|
7273
|
+
const nonInteractive = envFile !== void 0;
|
|
7274
|
+
const initEnvSources = await (0, _hot_updater_cli_tools.readHotUpdaterInitEnv)(process.cwd(), envFile);
|
|
7275
|
+
const { inputEnv, managedEnv } = initEnvSources;
|
|
7276
|
+
const savedInputs = resolveSupabaseInitInputs((0, _hot_updater_cli_tools.getHotUpdaterInitInputEnv)(initEnvSources, nonInteractive), {
|
|
7277
|
+
inputEnv,
|
|
7278
|
+
managedEnv
|
|
7279
|
+
});
|
|
7280
|
+
await assertSupabaseNonInteractiveInputs(savedInputs, nonInteractive);
|
|
7281
|
+
const initInputs = await inputSupabaseDeploymentInputs({
|
|
7282
|
+
...savedInputs,
|
|
7283
|
+
nonInteractive
|
|
7284
|
+
});
|
|
7285
|
+
const { accessToken, functionName } = initInputs;
|
|
7286
|
+
const projectSelection = await selectProject(savedInputs.projectId, nonInteractive, accessToken);
|
|
7287
|
+
let project = projectSelection.create ? void 0 : projectSelection.project;
|
|
7288
|
+
const dbPassword = await inputSupabaseDatabasePassword({
|
|
7289
|
+
cliHandlesPrompt: projectSelection.create && initInputs.accessToken === void 0,
|
|
7290
|
+
databasePassword: savedInputs.databasePassword,
|
|
7291
|
+
nonInteractive,
|
|
7292
|
+
required: projectSelection.create
|
|
7293
|
+
});
|
|
7294
|
+
const managementApi = supabaseManagementApi(accessToken);
|
|
7295
|
+
const projectCreationInputs = projectSelection.create ? await inputSupabaseProjectCreationInputs({
|
|
7296
|
+
bucketName: savedInputs.bucketName,
|
|
7297
|
+
organizationSlug: savedInputs.organizationSlug,
|
|
7298
|
+
organizations: await managementApi.listOrganizations(),
|
|
7299
|
+
projectName: savedInputs.projectName,
|
|
7300
|
+
region: savedInputs.region
|
|
7301
|
+
}) : void 0;
|
|
7302
|
+
let projectAccess = project === void 0 ? void 0 : await getSupabaseProjectAccess({
|
|
7303
|
+
accessToken,
|
|
7304
|
+
managementApi,
|
|
7305
|
+
project,
|
|
7306
|
+
waitForProject: false
|
|
7307
|
+
});
|
|
7308
|
+
let bucketSelection;
|
|
7309
|
+
if (project && projectAccess) bucketSelection = await selectBucket(projectAccess.api, savedInputs.bucketName, nonInteractive);
|
|
7310
|
+
else if (projectCreationInputs) bucketSelection = {
|
|
7311
|
+
create: true,
|
|
7312
|
+
name: projectCreationInputs.bucketName
|
|
7313
|
+
};
|
|
7314
|
+
else throw new Error("Failed to plan the Supabase storage bucket.");
|
|
7315
|
+
if (projectAccess) await ensureSupabaseBucketPrivate({
|
|
7316
|
+
api: projectAccess.api,
|
|
7317
|
+
nonInteractive,
|
|
7318
|
+
selection: bucketSelection
|
|
7319
|
+
});
|
|
7320
|
+
const inputsBeforeProvisioning = {
|
|
7321
|
+
...savedInputs,
|
|
7322
|
+
...projectCreationInputs,
|
|
7323
|
+
accessToken,
|
|
7324
|
+
bucketName: bucketSelection.name,
|
|
7325
|
+
databasePassword: dbPassword,
|
|
7326
|
+
functionName,
|
|
7327
|
+
projectId: project?.id
|
|
7328
|
+
};
|
|
7329
|
+
const databasePasswordKey = initProvider.inputs.databasePassword.envKey;
|
|
7330
|
+
const persistCredentialInputs = await (0, _hot_updater_cli_tools.confirmInitInputPersistence)({
|
|
7331
|
+
existingEnv: dbPassword !== "" && (projectSelection.create || managedEnv["HOT_UPDATER_SUPABASE_DB_PASSWORD_PROJECT_ID"] !== project?.id) ? {
|
|
7332
|
+
...managedEnv,
|
|
7333
|
+
[databasePasswordKey]: ""
|
|
7334
|
+
} : managedEnv,
|
|
7335
|
+
inputs: inputsBeforeProvisioning,
|
|
7336
|
+
nonInteractive,
|
|
7337
|
+
provider: initProvider
|
|
7338
|
+
});
|
|
7339
|
+
if (!await confirmSupabaseDatabaseMigrations({ nonInteractive })) {
|
|
7340
|
+
_hot_updater_cli_tools.p.log.info("Init cancelled.");
|
|
7341
|
+
process.exit(1);
|
|
7342
|
+
}
|
|
7343
|
+
if (projectSelection.create) {
|
|
7344
|
+
if (!projectCreationInputs) throw new Error("Supabase project creation inputs were not resolved.");
|
|
7345
|
+
project = await managementApi.createProject({
|
|
7346
|
+
databasePassword: dbPassword,
|
|
7347
|
+
name: projectCreationInputs.projectName,
|
|
7348
|
+
organizationSlug: projectCreationInputs.organizationSlug,
|
|
7349
|
+
region: projectCreationInputs.region
|
|
7350
|
+
});
|
|
7351
|
+
projectAccess = await getSupabaseProjectAccess({
|
|
7352
|
+
accessToken,
|
|
7353
|
+
managementApi,
|
|
7354
|
+
project,
|
|
7355
|
+
waitForProject: true
|
|
7356
|
+
});
|
|
7357
|
+
}
|
|
7358
|
+
if (!project || !projectAccess) throw new Error("Failed to resolve the Supabase project.");
|
|
7359
|
+
const providerEnv = (0, _hot_updater_cli_tools.getInitProviderEnvVars)({
|
|
7360
|
+
includeConsentInputs: persistCredentialInputs,
|
|
7361
|
+
inputs: {
|
|
7362
|
+
...inputsBeforeProvisioning,
|
|
7363
|
+
projectId: project.id
|
|
7364
|
+
},
|
|
7365
|
+
provider: initProvider
|
|
7366
|
+
});
|
|
7367
|
+
const persistDatabasePassword = persistCredentialInputs && dbPassword !== "";
|
|
7368
|
+
if (persistDatabasePassword) providerEnv[SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY] = project.id;
|
|
7369
|
+
await (0, _hot_updater_cli_tools.makeEnv)(providerEnv, ".env.hotupdater", { removeKeys: persistDatabasePassword ? [] : [databasePasswordKey, SUPABASE_DATABASE_PASSWORD_PROJECT_ID_ENV_KEY] });
|
|
7370
|
+
const bucket = await createSelectedBucket(projectAccess.api, bucketSelection);
|
|
7371
|
+
await (0, _hot_updater_cli_tools.makeEnv)({
|
|
7372
|
+
[initProvider.inputs.projectId.envKey]: project.id,
|
|
7373
|
+
HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY: projectAccess.serviceRoleApiKey,
|
|
7374
|
+
[initProvider.inputs.bucketName.envKey]: bucket.name,
|
|
7375
|
+
HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
|
|
7376
|
+
});
|
|
6729
7377
|
const { tmpDir, removeTmpDir } = await (0, _hot_updater_cli_tools.copyDirToTmp)(path.default.dirname(path.default.resolve(require$1.resolve("@hot-updater/supabase/scaffold"))), "supabase");
|
|
6730
7378
|
const migrationPath = await path.default.join(tmpDir, "supabase", "migrations");
|
|
6731
7379
|
const migrationFiles = await fs_promises.default.readdir(migrationPath);
|
|
@@ -6734,33 +7382,33 @@ const runInit = async ({ build }) => {
|
|
|
6734
7382
|
const content = await fs_promises.default.readFile(filePath, "utf-8");
|
|
6735
7383
|
await fs_promises.default.writeFile(filePath, (0, _hot_updater_cli_tools.transformTemplate)(content, { BUCKET_NAME: bucket.name }));
|
|
6736
7384
|
}
|
|
6737
|
-
const dbPassword = await _hot_updater_cli_tools.p.password({ message: "Enter your Supabase database password (press Enter to skip if none)" });
|
|
6738
|
-
if (_hot_updater_cli_tools.p.isCancel(dbPassword)) process.exit(0);
|
|
6739
7385
|
await linkSupabase(tmpDir, {
|
|
7386
|
+
accessToken,
|
|
6740
7387
|
projectId: project.id,
|
|
6741
7388
|
dbPassword
|
|
6742
7389
|
});
|
|
6743
|
-
await pushDB(tmpDir, {
|
|
6744
|
-
|
|
7390
|
+
await pushDB(tmpDir, {
|
|
7391
|
+
accessToken,
|
|
7392
|
+
dbPassword
|
|
7393
|
+
});
|
|
7394
|
+
await deployEdgeFunction(accessToken, tmpDir, project.id, functionName);
|
|
6745
7395
|
await removeTmpDir();
|
|
6746
7396
|
const configWriteResult = await (0, _hot_updater_cli_tools.writeHotUpdaterConfig)(getConfigScaffold(build));
|
|
6747
7397
|
await assertSkippedConfigDoesNotUseLegacySupabaseKey(configWriteResult);
|
|
6748
|
-
await (0, _hot_updater_cli_tools.makeEnv)({
|
|
6749
|
-
HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY: serviceRoleApiKey.api_key,
|
|
6750
|
-
HOT_UPDATER_SUPABASE_BUCKET_NAME: bucket.name,
|
|
6751
|
-
HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
|
|
6752
|
-
});
|
|
6753
7398
|
_hot_updater_cli_tools.p.log.success("Generated '.env.hotupdater' file with Supabase settings.");
|
|
6754
7399
|
if (configWriteResult.status === "created") _hot_updater_cli_tools.p.log.success("Generated 'hot-updater.config.ts' file with Supabase settings.");
|
|
6755
7400
|
else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Updated 'hot-updater.config.ts' file with Supabase settings.");
|
|
6756
7401
|
else _hot_updater_cli_tools.p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
|
|
6757
|
-
_hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, { source: `https://${project.id}.supabase.co/functions/v1
|
|
7402
|
+
_hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, { source: `https://${project.id}.supabase.co/functions/v1/${functionName}` }));
|
|
6758
7403
|
_hot_updater_cli_tools.p.log.message(`Next step: ${(0, _hot_updater_cli_tools.link)("https://hot-updater.dev/docs/managed/supabase#step-4-add-hotupdater-to-your-project")}`);
|
|
6759
7404
|
_hot_updater_cli_tools.p.log.success("Done! 🎉");
|
|
6760
7405
|
};
|
|
6761
7406
|
//#endregion
|
|
7407
|
+
exports.createSelectedBucket = createSelectedBucket;
|
|
6762
7408
|
exports.getLegacySupabaseConfigReference = getLegacySupabaseConfigReference;
|
|
7409
|
+
exports.getSupabaseProjectAccess = getSupabaseProjectAccess;
|
|
6763
7410
|
exports.resolveEdgeFunctionDenoConfig = resolveEdgeFunctionDenoConfig;
|
|
6764
7411
|
exports.runInit = runInit;
|
|
6765
7412
|
exports.selectBucket = selectBucket;
|
|
6766
7413
|
exports.selectProject = selectProject;
|
|
7414
|
+
exports.waitForSupabaseProjectReady = waitForSupabaseProjectReady;
|