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