@hot-updater/firebase 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.
@@ -39,6 +39,7 @@ let node_path = require("node:path");
39
39
  node_path = __toESM(node_path);
40
40
  let node_timers_promises = require("node:timers/promises");
41
41
  let node_os = require("node:os");
42
+ node_os = __toESM(node_os);
42
43
  let node_events = require("node:events");
43
44
  let node_v8 = require("node:v8");
44
45
  let node_fs = require("node:fs");
@@ -1423,7 +1424,7 @@ var require_isexe = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1423
1424
  //#region ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
1424
1425
  var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1425
1426
  const isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
1426
- const path$10 = require("path");
1427
+ const path$11 = require("path");
1427
1428
  const COLON = isWindows ? ";" : ":";
1428
1429
  const isexe = require_isexe();
1429
1430
  const getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -1453,7 +1454,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1453
1454
  if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
1454
1455
  const ppRaw = pathEnv[i];
1455
1456
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1456
- const pCmd = path$10.join(pathPart, cmd);
1457
+ const pCmd = path$11.join(pathPart, cmd);
1457
1458
  resolve(subStep(!pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd, i, 0));
1458
1459
  });
1459
1460
  const subStep = (p, i, ii) => new Promise((resolve, reject) => {
@@ -1474,7 +1475,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1474
1475
  for (let i = 0; i < pathEnv.length; i++) {
1475
1476
  const ppRaw = pathEnv[i];
1476
1477
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1477
- const pCmd = path$10.join(pathPart, cmd);
1478
+ const pCmd = path$11.join(pathPart, cmd);
1478
1479
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
1479
1480
  for (let j = 0; j < pathExt.length; j++) {
1480
1481
  const cur = p + pathExt[j];
@@ -1505,7 +1506,7 @@ var require_path_key = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1505
1506
  //#endregion
1506
1507
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
1507
1508
  var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1508
- const path$9 = require("path");
1509
+ const path$10 = require("path");
1509
1510
  const which = require_which();
1510
1511
  const getPathKey = require_path_key();
1511
1512
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -1520,12 +1521,12 @@ var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) =>
1520
1521
  try {
1521
1522
  resolved = which.sync(parsed.command, {
1522
1523
  path: env[getPathKey({ env })],
1523
- pathExt: withoutPathExt ? path$9.delimiter : void 0
1524
+ pathExt: withoutPathExt ? path$10.delimiter : void 0
1524
1525
  });
1525
1526
  } catch (e) {} finally {
1526
1527
  if (shouldSwitchCwd) process.chdir(cwd);
1527
1528
  }
1528
- if (resolved) resolved = path$9.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1529
+ if (resolved) resolved = path$10.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1529
1530
  return resolved;
1530
1531
  }
1531
1532
  function resolveCommand(parsed) {
@@ -1592,7 +1593,7 @@ var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1592
1593
  //#endregion
1593
1594
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
1594
1595
  var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1595
- const path$8 = require("path");
1596
+ const path$9 = require("path");
1596
1597
  const resolveCommand = require_resolveCommand();
1597
1598
  const escape = require_escape();
1598
1599
  const readShebang = require_readShebang();
@@ -1615,7 +1616,7 @@ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1615
1616
  const needsShell = !isExecutableRegExp.test(commandFile);
1616
1617
  if (parsed.options.forceShell || needsShell) {
1617
1618
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
1618
- parsed.command = path$8.normalize(parsed.command);
1619
+ parsed.command = path$9.normalize(parsed.command);
1619
1620
  parsed.command = escape.command(parsed.command);
1620
1621
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
1621
1622
  parsed.args = [
@@ -6711,6 +6712,212 @@ createExeca(mapNode);
6711
6712
  createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6712
6713
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
6713
6714
  //#endregion
6715
+ //#region iac/init/index.ts
6716
+ const isFirebaseRegion = (value) => value !== void 0 && /^[a-z]+(?:-[a-z]+)+[0-9]+$/.test(value);
6717
+ const isFirebaseProjectId = (value) => value !== void 0 && /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value);
6718
+ const initProvider = {
6719
+ label: "Firebase",
6720
+ inputs: {
6721
+ projectId: {
6722
+ envKey: "HOT_UPDATER_FIREBASE_PROJECT_ID",
6723
+ help: "Firebase project ID",
6724
+ prompt: {
6725
+ message: "Enter the Firebase project ID:",
6726
+ placeholder: "hot-updater-app",
6727
+ type: "text"
6728
+ },
6729
+ validate: isFirebaseProjectId
6730
+ },
6731
+ region: {
6732
+ envKey: "HOT_UPDATER_FIREBASE_REGION",
6733
+ help: "Firebase Functions region",
6734
+ prompt: {
6735
+ message: "Select Region",
6736
+ type: "select"
6737
+ },
6738
+ validate: isFirebaseRegion
6739
+ },
6740
+ applicationCredentials: {
6741
+ envKey: "GOOGLE_APPLICATION_CREDENTIALS",
6742
+ help: "Service account JSON path",
6743
+ optional: true,
6744
+ persistence: "with-consent",
6745
+ prompt: {
6746
+ message: "Enter the service account JSON path (press Enter to configure later)",
6747
+ placeholder: "~/Downloads/firebase-service-account.json",
6748
+ type: "text"
6749
+ }
6750
+ }
6751
+ }
6752
+ };
6753
+ //#endregion
6754
+ //#region iac/firebaseApplicationCredentials.ts
6755
+ const inputFirebaseApplicationCredentials = async ({ applicationCredentials, nonInteractive, projectId }) => {
6756
+ if (nonInteractive) return applicationCredentials;
6757
+ const prompt = initProvider.inputs.applicationCredentials.prompt;
6758
+ _hot_updater_cli_tools.p.log.step(`Service account JSON: ${(0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${projectId}/settings/serviceaccounts/adminsdk`)}`);
6759
+ _hot_updater_cli_tools.p.log.step("Project settings > Service accounts > Generate new private key");
6760
+ const credentialsPath = await _hot_updater_cli_tools.p.text({
6761
+ ...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(prompt, applicationCredentials),
6762
+ message: prompt.message
6763
+ });
6764
+ if (_hot_updater_cli_tools.p.isCancel(credentialsPath)) process.exit(1);
6765
+ return credentialsPath || void 0;
6766
+ };
6767
+ //#endregion
6768
+ //#region iac/firebaseInitInputs.ts
6769
+ const resolveFirebaseCredentialsPath = (credentialsPath, { cwd = process.cwd(), homeDir = node_os.default.homedir(), pathApi = node_path.default }) => {
6770
+ const homeRelativePath = credentialsPath === "~" ? "" : credentialsPath.startsWith("~/") || credentialsPath.startsWith("~\\") ? credentialsPath.slice(2) : void 0;
6771
+ return homeRelativePath === void 0 ? pathApi.resolve(cwd, credentialsPath) : pathApi.resolve(homeDir, homeRelativePath);
6772
+ };
6773
+ const getFirebaseCliEnv = (applicationCredentials, pathContext = {}) => {
6774
+ if (!applicationCredentials) return;
6775
+ const resolvedCredentialsPath = resolveFirebaseCredentialsPath(applicationCredentials, pathContext);
6776
+ return { [initProvider.inputs.applicationCredentials.envKey]: resolvedCredentialsPath };
6777
+ };
6778
+ const resolveFirebaseInitInputs = (existingEnv) => {
6779
+ const { inputs } = initProvider;
6780
+ const applicationCredentials = (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.applicationCredentials);
6781
+ return {
6782
+ applicationCredentials: applicationCredentials === "your-credentials.json" ? void 0 : applicationCredentials,
6783
+ projectId: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.projectId),
6784
+ region: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.region)
6785
+ };
6786
+ };
6787
+ const assertFirebaseNonInteractiveInputs = (inputs, nonInteractive = false) => {
6788
+ (0, _hot_updater_cli_tools.assertInitProviderInputs)({
6789
+ inputs,
6790
+ provider: initProvider,
6791
+ strict: nonInteractive
6792
+ });
6793
+ };
6794
+ //#endregion
6795
+ //#region iac/firebaseRegion.ts
6796
+ const REGIONS = [
6797
+ {
6798
+ value: "us-central1",
6799
+ label: "US Central (Iowa)"
6800
+ },
6801
+ {
6802
+ value: "us-east1",
6803
+ label: "US East (South Carolina)"
6804
+ },
6805
+ {
6806
+ value: "us-east4",
6807
+ label: "US East (Northern Virginia)"
6808
+ },
6809
+ {
6810
+ value: "us-west1",
6811
+ label: "US West (Oregon)"
6812
+ },
6813
+ {
6814
+ value: "us-west2",
6815
+ label: "US West (Los Angeles)"
6816
+ },
6817
+ {
6818
+ value: "us-west3",
6819
+ label: "US West (Salt Lake City)"
6820
+ },
6821
+ {
6822
+ value: "us-west4",
6823
+ label: "US West (Las Vegas)"
6824
+ },
6825
+ {
6826
+ value: "europe-west1",
6827
+ label: "Europe West (Belgium)"
6828
+ },
6829
+ {
6830
+ value: "europe-west2",
6831
+ label: "Europe West (London)"
6832
+ },
6833
+ {
6834
+ value: "europe-west3",
6835
+ label: "Europe West (Frankfurt)"
6836
+ },
6837
+ {
6838
+ value: "europe-west6",
6839
+ label: "Europe West (Zurich)"
6840
+ },
6841
+ {
6842
+ value: "asia-east1",
6843
+ label: "Asia East (Taiwan)"
6844
+ },
6845
+ {
6846
+ value: "asia-east2",
6847
+ label: "Asia East (Hong Kong)"
6848
+ },
6849
+ {
6850
+ value: "asia-northeast1",
6851
+ label: "Asia Northeast (Tokyo)"
6852
+ },
6853
+ {
6854
+ value: "asia-northeast2",
6855
+ label: "Asia Northeast (Osaka)"
6856
+ },
6857
+ {
6858
+ value: "asia-northeast3",
6859
+ label: "Asia Northeast (Seoul)"
6860
+ },
6861
+ {
6862
+ value: "asia-south1",
6863
+ label: "Asia South (Mumbai)"
6864
+ },
6865
+ {
6866
+ value: "asia-southeast1",
6867
+ label: "Asia Southeast (Singapore)"
6868
+ },
6869
+ {
6870
+ value: "asia-southeast2",
6871
+ label: "Asia Southeast (Jakarta)"
6872
+ },
6873
+ {
6874
+ value: "australia-southeast1",
6875
+ label: "Australia Southeast (Sydney)"
6876
+ }
6877
+ ];
6878
+ const resolveFirebaseRegion = async ({ cwd, discoverExistingProject = true, nonInteractive, savedRegion, cliEnv }) => {
6879
+ if (nonInteractive) {
6880
+ if (isFirebaseRegion(savedRegion)) return savedRegion;
6881
+ throw new _hot_updater_cli_tools.MissingInitInputsError(["HOT_UPDATER_FIREBASE_REGION"]);
6882
+ }
6883
+ let discoveredRegion;
6884
+ if (discoverExistingProject) {
6885
+ const functionsList = await execa("npx", [
6886
+ "firebase",
6887
+ "functions:list",
6888
+ "--json"
6889
+ ], {
6890
+ cwd,
6891
+ env: cliEnv,
6892
+ reject: false
6893
+ });
6894
+ if (functionsList.exitCode === 0) {
6895
+ const parsed = JSON.parse(functionsList.stdout);
6896
+ if (typeof parsed === "object" && parsed !== null && "result" in parsed && Array.isArray(parsed.result)) {
6897
+ for (const entry of parsed.result) if (typeof entry === "object" && entry !== null && "id" in entry && entry.id === "hot-updater" && "region" in entry && typeof entry.region === "string") {
6898
+ discoveredRegion = entry.region;
6899
+ break;
6900
+ }
6901
+ }
6902
+ }
6903
+ }
6904
+ const initialRegion = isFirebaseRegion(savedRegion) ? savedRegion : isFirebaseRegion(discoveredRegion) ? discoveredRegion : REGIONS[0].value;
6905
+ const options = REGIONS.some((region) => region.value === initialRegion) ? REGIONS : [{
6906
+ value: initialRegion,
6907
+ label: `Existing (${initialRegion})`
6908
+ }, ...REGIONS];
6909
+ const selectedRegion = await _hot_updater_cli_tools.p.select({
6910
+ initialValue: initialRegion,
6911
+ message: initProvider.inputs.region.prompt.message,
6912
+ options
6913
+ });
6914
+ if (_hot_updater_cli_tools.p.isCancel(selectedRegion)) {
6915
+ _hot_updater_cli_tools.p.cancel("Operation cancelled.");
6916
+ process.exit(1);
6917
+ }
6918
+ return selectedRegion;
6919
+ };
6920
+ //#endregion
6714
6921
  //#region iac/prepareTemplate.ts
6715
6922
  const ensureExists = async (targetPath, description) => {
6716
6923
  try {
@@ -6783,15 +6990,16 @@ const credential = admin.credential.applicationDefault();`.trim()
6783
6990
  defaultOrNamespace: "admin"
6784
6991
  }).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n")), { helperStatements });
6785
6992
  };
6786
- const setEnv = async ({ projectId, storageBucket, build }) => {
6993
+ const setEnv = async ({ applicationCredentials, projectId, storageBucket, build, region }) => {
6787
6994
  await (0, _hot_updater_cli_tools.makeEnv)({
6788
- GOOGLE_APPLICATION_CREDENTIALS: {
6995
+ [initProvider.inputs.applicationCredentials.envKey]: {
6789
6996
  comment: "Project Settings > Service Accounts > New Private Key > Download JSON",
6790
- value: "your-credentials.json"
6997
+ value: applicationCredentials ?? "your-credentials.json"
6791
6998
  },
6792
- HOT_UPDATER_FIREBASE_PROJECT_ID: projectId,
6999
+ [initProvider.inputs.projectId.envKey]: projectId,
7000
+ [initProvider.inputs.region.envKey]: region,
6793
7001
  HOT_UPDATER_FIREBASE_STORAGE_BUCKET: storageBucket
6794
- }, ".env.hotupdater", { preserveKeys: ["GOOGLE_APPLICATION_CREDENTIALS"] });
7002
+ }, ".env.hotupdater", { preserveKeys: applicationCredentials ? [] : [initProvider.inputs.applicationCredentials.envKey] });
6795
7003
  _hot_updater_cli_tools.p.log.success("Firebase credentials have been successfully configured.");
6796
7004
  try {
6797
7005
  const configWriteResult = await (0, _hot_updater_cli_tools.writeHotUpdaterConfig)(getConfigScaffold(build));
@@ -6799,7 +7007,8 @@ const setEnv = async ({ projectId, storageBucket, build }) => {
6799
7007
  else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Configuration file 'hot-updater.config.ts' has been updated.");
6800
7008
  else _hot_updater_cli_tools.p.log.warn(`Existing 'hot-updater.config.ts' was left unchanged: ${configWriteResult.reason}`);
6801
7009
  } catch (error) {
6802
- console.error("Error writing configuration file:", error.message);
7010
+ const message = error instanceof Error ? error.message : String(error);
7011
+ console.error("Error writing configuration file:", message);
6803
7012
  }
6804
7013
  };
6805
7014
  const handleError = (err) => {
@@ -6807,43 +7016,81 @@ const handleError = (err) => {
6807
7016
  else if (err instanceof Error) _hot_updater_cli_tools.p.log.error(`Error occurred: ${err.message}`);
6808
7017
  process.exit(1);
6809
7018
  };
6810
- const listProjects = async () => {
7019
+ const createFirebaseProject = async ({ cliEnv, projectId }) => {
7020
+ if (!isFirebaseProjectId(projectId)) throw new Error(`Invalid Firebase project ID: ${projectId}`);
7021
+ try {
7022
+ await execa("npx", [
7023
+ "firebase",
7024
+ "projects:create",
7025
+ `--display-name=${projectId}`,
7026
+ "--non-interactive",
7027
+ "--",
7028
+ projectId
7029
+ ], {
7030
+ env: cliEnv,
7031
+ stdio: "inherit"
7032
+ });
7033
+ } catch (error) {
7034
+ handleError(error instanceof Error ? error : new Error(String(error)));
7035
+ }
7036
+ _hot_updater_cli_tools.p.log.success("Firebase project created successfully");
7037
+ _hot_updater_cli_tools.p.log.step("Enable Firestore, Storage, and Billing before running init again:");
7038
+ _hot_updater_cli_tools.p.log.step((0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${projectId}/firestore`));
7039
+ _hot_updater_cli_tools.p.log.step((0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${projectId}/storage`));
7040
+ };
7041
+ const listProjects = async (nonInteractive = false, cliEnv) => {
6811
7042
  try {
6812
7043
  const projects = await execa("npx", [
6813
7044
  "firebase",
6814
7045
  "projects:list",
6815
- "--json"
6816
- ], { shell: true });
7046
+ "--json",
7047
+ ...nonInteractive ? ["--non-interactive"] : []
7048
+ ], { env: cliEnv });
6817
7049
  return JSON.parse(projects.stdout)?.result ?? [];
6818
- } catch {
6819
- return [];
7050
+ } catch (error) {
7051
+ if (nonInteractive) throw new _hot_updater_cli_tools.MissingInitInputsError(["Firebase CLI authentication (`firebase login`)"]);
7052
+ throw error;
6820
7053
  }
6821
7054
  };
6822
- const initFirebaseUser = async (cwd) => {
6823
- try {
6824
- await execa("npx", ["firebase", "login"], {
6825
- stdio: "inherit",
6826
- shell: true
6827
- });
6828
- } catch (err) {
6829
- handleError(err);
6830
- }
6831
- try {
7055
+ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false, cliEnv, resolveCliEnv) => {
7056
+ if (!cliEnv) try {
6832
7057
  const authList = await execa("gcloud", [
6833
7058
  "auth",
6834
7059
  "list",
6835
7060
  "--format=json"
6836
- ], { shell: true });
6837
- if (JSON.parse(authList.stdout).length === 0) await execa("gcloud", ["auth", "login"], {
6838
- stdio: "inherit",
6839
- shell: true
6840
- });
7061
+ ], { env: cliEnv });
7062
+ if (JSON.parse(authList.stdout).length === 0) {
7063
+ if (nonInteractive) throw new _hot_updater_cli_tools.MissingInitInputsError(["active gcloud authentication (`gcloud auth login`)"]);
7064
+ await execa("gcloud", ["auth", "login"], {
7065
+ env: cliEnv,
7066
+ stdio: "inherit"
7067
+ });
7068
+ }
6841
7069
  } catch (err) {
7070
+ if (err instanceof _hot_updater_cli_tools.MissingInitInputsError) throw err;
6842
7071
  handleError(err);
6843
7072
  }
6844
- const projects = await listProjects();
7073
+ let projects;
7074
+ try {
7075
+ projects = await listProjects(nonInteractive, cliEnv);
7076
+ } catch (error) {
7077
+ if (nonInteractive || cliEnv) throw error;
7078
+ try {
7079
+ await execa("npx", ["firebase", "login"], {
7080
+ env: cliEnv,
7081
+ stdio: "inherit"
7082
+ });
7083
+ projects = await listProjects(false, cliEnv);
7084
+ } catch (loginError) {
7085
+ handleError(loginError instanceof Error ? loginError : new Error(String(loginError)));
7086
+ }
7087
+ }
6845
7088
  const createKey = `create/${Math.random().toString(36).substring(2, 15)}`;
6846
- const projectId = await _hot_updater_cli_tools.p.select({
7089
+ const preferredProject = projects.find((project) => project.projectId === preferredProjectId);
7090
+ if (preferredProjectId && !preferredProject) _hot_updater_cli_tools.p.log.warn("Saved Firebase project was not found. Select a project again.");
7091
+ if (nonInteractive && !preferredProject) throw new _hot_updater_cli_tools.MissingInitInputsError(["HOT_UPDATER_FIREBASE_PROJECT_ID"]);
7092
+ const projectId = nonInteractive && preferredProject ? preferredProject.projectId : await _hot_updater_cli_tools.p.select({
7093
+ initialValue: preferredProject?.projectId ?? projects[0]?.projectId,
6847
7094
  message: "Select a Firebase project",
6848
7095
  options: [...projects.map((project) => ({
6849
7096
  label: project.displayName,
@@ -6858,29 +7105,22 @@ const initFirebaseUser = async (cwd) => {
6858
7105
  process.exit(1);
6859
7106
  }
6860
7107
  if (projectId === createKey) {
6861
- const newProjectId = await _hot_updater_cli_tools.p.text({ message: "Enter the Firebase project ID:" });
7108
+ const prompt = initProvider.inputs.projectId.prompt;
7109
+ const newProjectId = await _hot_updater_cli_tools.p.text({
7110
+ ...(0, _hot_updater_cli_tools.getInitProviderTextPromptValues)(prompt, preferredProjectId),
7111
+ message: prompt.message,
7112
+ validate: (value) => isFirebaseProjectId(value) ? void 0 : "Use 6-30 lowercase letters, numbers, or hyphens; start with a letter and end with a letter or number."
7113
+ });
6862
7114
  if (_hot_updater_cli_tools.p.isCancel(newProjectId)) {
6863
7115
  _hot_updater_cli_tools.p.log.error("Project ID is required");
6864
7116
  process.exit(1);
6865
7117
  }
6866
- try {
6867
- await execa("npx", [
6868
- "firebase",
6869
- "projects:create",
6870
- newProjectId
6871
- ], {
6872
- stdio: "inherit",
6873
- shell: true
6874
- });
6875
- _hot_updater_cli_tools.p.log.success("Firebase project created successfully");
6876
- _hot_updater_cli_tools.p.log.step("Please Go to the following links to enable Firestore and Storage and Billing");
6877
- _hot_updater_cli_tools.p.log.step((0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${newProjectId}/firestore`));
6878
- _hot_updater_cli_tools.p.log.step((0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${newProjectId}/storage`));
6879
- } catch (err) {
6880
- handleError(err);
6881
- }
6882
- process.exit(0);
7118
+ return {
7119
+ status: "create",
7120
+ projectId: newProjectId
7121
+ };
6883
7122
  }
7123
+ const selectedProjectCliEnv = resolveCliEnv ? await resolveCliEnv(projectId) : cliEnv;
6884
7124
  await _hot_updater_cli_tools.p.tasks([{
6885
7125
  title: `Select Firebase project (${projectId})...`,
6886
7126
  task: async () => {
@@ -6888,11 +7128,12 @@ const initFirebaseUser = async (cwd) => {
6888
7128
  await execa("npx", [
6889
7129
  "firebase",
6890
7130
  "use",
6891
- "--add",
6892
- projectId
7131
+ ...nonInteractive ? [] : ["--add"],
7132
+ projectId,
7133
+ ...nonInteractive ? ["--non-interactive"] : []
6893
7134
  ], {
6894
7135
  cwd,
6895
- shell: true
7136
+ env: selectedProjectCliEnv
6896
7137
  });
6897
7138
  } catch (error) {
6898
7139
  if (error instanceof ExecaError) _hot_updater_cli_tools.p.log.error(error.stderr || error.stdout || error.message);
@@ -6909,7 +7150,7 @@ const initFirebaseUser = async (cwd) => {
6909
7150
  `--project=${projectId}`,
6910
7151
  "--format=json"
6911
7152
  ], {
6912
- shell: true,
7153
+ env: selectedProjectCliEnv,
6913
7154
  input: "N\n"
6914
7155
  });
6915
7156
  if (JSON.parse(databases.stdout).length === 0) {
@@ -6920,7 +7161,7 @@ const initFirebaseUser = async (cwd) => {
6920
7161
  process.exit(1);
6921
7162
  }
6922
7163
  } catch (err) {
6923
- handleError(err);
7164
+ handleError(err instanceof Error ? err : new Error(String(err)));
6924
7165
  }
6925
7166
  let storageBucket = null;
6926
7167
  await _hot_updater_cli_tools.p.tasks([{
@@ -6932,7 +7173,7 @@ const initFirebaseUser = async (cwd) => {
6932
7173
  "list",
6933
7174
  `--project=${projectId}`,
6934
7175
  "--format=json"
6935
- ], { shell: true });
7176
+ ], { env: selectedProjectCliEnv });
6936
7177
  storageBucket = JSON.parse(buckets.stdout).find((bucket) => bucket.name === `${projectId}.firebasestorage.app` || bucket.name === `${projectId}.appspot.com`)?.name;
6937
7178
  if (!storageBucket) {
6938
7179
  _hot_updater_cli_tools.p.log.error("Storage Bucket not found");
@@ -6952,7 +7193,7 @@ const initFirebaseUser = async (cwd) => {
6952
7193
  "describe",
6953
7194
  projectId,
6954
7195
  "--format=json"
6955
- ], { shell: true });
7196
+ ], { env: selectedProjectCliEnv });
6956
7197
  const projectJson = JSON.parse(project.stdout);
6957
7198
  const projectNumber = Number(projectJson.projectNumber);
6958
7199
  if (Number.isNaN(projectNumber)) {
@@ -6960,6 +7201,7 @@ const initFirebaseUser = async (cwd) => {
6960
7201
  process.exit(1);
6961
7202
  }
6962
7203
  return {
7204
+ status: "ready",
6963
7205
  storageBucket,
6964
7206
  projectNumber,
6965
7207
  projectId
@@ -6978,88 +7220,6 @@ export default HotUpdater.wrap({
6978
7220
  baseURL: "%%source%%",
6979
7221
  updateStrategy: "appVersion", // or "fingerprint"
6980
7222
  })(App);`;
6981
- const REGIONS = [
6982
- {
6983
- value: "us-central1",
6984
- label: "US Central (Iowa)"
6985
- },
6986
- {
6987
- value: "us-east1",
6988
- label: "US East (South Carolina)"
6989
- },
6990
- {
6991
- value: "us-east4",
6992
- label: "US East (Northern Virginia)"
6993
- },
6994
- {
6995
- value: "us-west1",
6996
- label: "US West (Oregon)"
6997
- },
6998
- {
6999
- value: "us-west2",
7000
- label: "US West (Los Angeles)"
7001
- },
7002
- {
7003
- value: "us-west3",
7004
- label: "US West (Salt Lake City)"
7005
- },
7006
- {
7007
- value: "us-west4",
7008
- label: "US West (Las Vegas)"
7009
- },
7010
- {
7011
- value: "europe-west1",
7012
- label: "Europe West (Belgium)"
7013
- },
7014
- {
7015
- value: "europe-west2",
7016
- label: "Europe West (London)"
7017
- },
7018
- {
7019
- value: "europe-west3",
7020
- label: "Europe West (Frankfurt)"
7021
- },
7022
- {
7023
- value: "europe-west6",
7024
- label: "Europe West (Zurich)"
7025
- },
7026
- {
7027
- value: "asia-east1",
7028
- label: "Asia East (Taiwan)"
7029
- },
7030
- {
7031
- value: "asia-east2",
7032
- label: "Asia East (Hong Kong)"
7033
- },
7034
- {
7035
- value: "asia-northeast1",
7036
- label: "Asia Northeast (Tokyo)"
7037
- },
7038
- {
7039
- value: "asia-northeast2",
7040
- label: "Asia Northeast (Osaka)"
7041
- },
7042
- {
7043
- value: "asia-northeast3",
7044
- label: "Asia Northeast (Seoul)"
7045
- },
7046
- {
7047
- value: "asia-south1",
7048
- label: "Asia South (Mumbai)"
7049
- },
7050
- {
7051
- value: "asia-southeast1",
7052
- label: "Asia Southeast (Singapore)"
7053
- },
7054
- {
7055
- value: "asia-southeast2",
7056
- label: "Asia Southeast (Jakarta)"
7057
- },
7058
- {
7059
- value: "australia-southeast1",
7060
- label: "Australia Southeast (Sydney)"
7061
- }
7062
- ];
7063
7223
  const getFirebaseRuntimePackageInfo = () => {
7064
7224
  const firebasePackageRoot = path.default.dirname(require.resolve("@hot-updater/firebase/package.json"));
7065
7225
  return {
@@ -7093,10 +7253,14 @@ const mergeIndexes = (originalIndexes, newIndexes) => {
7093
7253
  fieldOverrides: merge(originalIndexes.fieldOverrides, newIndexes.fieldOverrides)
7094
7254
  };
7095
7255
  };
7096
- const deployFirestore = async (cwd) => {
7097
- const original = await execa("npx", ["firebase", "firestore:indexes"], {
7256
+ const deployFirestore = async (cwd, nonInteractive = false, cliEnv) => {
7257
+ const original = await execa("npx", [
7258
+ "firebase",
7259
+ "firestore:indexes",
7260
+ ...nonInteractive ? ["--non-interactive"] : []
7261
+ ], {
7098
7262
  cwd,
7099
- shell: true
7263
+ env: cliEnv
7100
7264
  });
7101
7265
  let originalIndexes = {
7102
7266
  indexes: [],
@@ -7107,7 +7271,12 @@ const deployFirestore = async (cwd) => {
7107
7271
  indexes: [],
7108
7272
  fieldOverrides: []
7109
7273
  };
7110
- } catch {}
7274
+ } catch {
7275
+ originalIndexes = {
7276
+ indexes: [],
7277
+ fieldOverrides: []
7278
+ };
7279
+ }
7111
7280
  const newIndexes = JSON.parse(await fs.default.promises.readFile(path.default.join(cwd, "firestore.indexes.json"), "utf-8"));
7112
7281
  const mergedIndexes = mergeIndexes(originalIndexes, newIndexes);
7113
7282
  await fs.default.promises.writeFile(path.default.join(cwd, "firestore.indexes.json"), JSON.stringify(mergedIndexes, null, 2));
@@ -7116,11 +7285,12 @@ const deployFirestore = async (cwd) => {
7116
7285
  "firebase",
7117
7286
  "deploy",
7118
7287
  "--only",
7119
- "firestore"
7288
+ "firestore",
7289
+ ...nonInteractive ? ["--non-interactive"] : []
7120
7290
  ], {
7121
7291
  cwd,
7122
- stdio: "inherit",
7123
- shell: true
7292
+ env: cliEnv,
7293
+ stdio: "inherit"
7124
7294
  });
7125
7295
  } catch (e) {
7126
7296
  if (e instanceof ExecaError) _hot_updater_cli_tools.p.log.error(e.stderr || e.stdout || e.message);
@@ -7128,17 +7298,18 @@ const deployFirestore = async (cwd) => {
7128
7298
  process.exit(1);
7129
7299
  }
7130
7300
  };
7131
- const deployFunctions = async (cwd) => {
7301
+ const deployFunctions = async (cwd, nonInteractive = false, cliEnv) => {
7132
7302
  try {
7133
7303
  await execa("npx", [
7134
7304
  "firebase",
7135
7305
  "deploy",
7136
7306
  "--only",
7137
- "functions"
7307
+ "functions",
7308
+ ...nonInteractive ? ["--non-interactive"] : []
7138
7309
  ], {
7139
7310
  cwd,
7140
- stdio: "inherit",
7141
- shell: true
7311
+ env: cliEnv,
7312
+ stdio: "inherit"
7142
7313
  });
7143
7314
  } catch (e) {
7144
7315
  if (e instanceof ExecaError) _hot_updater_cli_tools.p.log.error(e.stderr || e.stdout || e.message);
@@ -7146,7 +7317,7 @@ const deployFunctions = async (cwd) => {
7146
7317
  process.exit(1);
7147
7318
  }
7148
7319
  };
7149
- const printTemplate = async (projectId, region) => {
7320
+ const printTemplate = async (projectId, region, cliEnv) => {
7150
7321
  try {
7151
7322
  const { stdout } = await execa("gcloud", [
7152
7323
  "functions",
@@ -7157,7 +7328,7 @@ const printTemplate = async (projectId, region) => {
7157
7328
  "--region",
7158
7329
  region,
7159
7330
  "--format=json"
7160
- ], { shell: true });
7331
+ ], { env: cliEnv });
7161
7332
  const parsedData = JSON.parse(stdout);
7162
7333
  const functionUrl = `${parsedData?.serviceConfig?.uri ?? parsedData.url}/api/check-update`;
7163
7334
  _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, { source: functionUrl }));
@@ -7169,13 +7340,20 @@ const printTemplate = async (projectId, region) => {
7169
7340
  };
7170
7341
  const checkIfGcloudCliInstalled = async () => {
7171
7342
  try {
7172
- await execa("gcloud", ["--version"], { shell: true });
7343
+ await execa("gcloud", ["--version"]);
7173
7344
  return true;
7174
7345
  } catch {
7175
7346
  return false;
7176
7347
  }
7177
7348
  };
7178
- const runInit = async ({ build }) => {
7349
+ const runInit = async ({ build, envFile }) => {
7350
+ const nonInteractive = envFile !== void 0;
7351
+ const initEnvSources = await (0, _hot_updater_cli_tools.readHotUpdaterInitEnv)(process.cwd(), envFile);
7352
+ const { managedEnv } = initEnvSources;
7353
+ const savedInputs = resolveFirebaseInitInputs((0, _hot_updater_cli_tools.getHotUpdaterInitInputEnv)(initEnvSources, nonInteractive));
7354
+ assertFirebaseNonInteractiveInputs(savedInputs, nonInteractive);
7355
+ let applicationCredentials = savedInputs.applicationCredentials;
7356
+ const cliEnv = nonInteractive ? getFirebaseCliEnv(applicationCredentials) : void 0;
7179
7357
  if (!await checkIfGcloudCliInstalled()) {
7180
7358
  _hot_updater_cli_tools.p.log.error("gcloud CLI is not installed");
7181
7359
  _hot_updater_cli_tools.p.log.step("Please go to the following link to install the gcloud CLI");
@@ -7185,22 +7363,61 @@ const runInit = async ({ build }) => {
7185
7363
  const { tmpDir, removeTmpDir, functionsDir } = await prepareFirebaseTemplate(path.default.dirname(path.default.dirname(require.resolve("@hot-updater/firebase/functions"))));
7186
7364
  const functionsIndexPath = path.default.join(functionsDir, "index.cjs");
7187
7365
  const runtimePackageInfo = await syncFunctionsPackageJson(functionsDir);
7188
- const initializeVariable = await initFirebaseUser(tmpDir);
7189
- let currentRegion;
7366
+ const initializeVariable = await initFirebaseUser(tmpDir, savedInputs.projectId, nonInteractive, cliEnv, async (projectId) => {
7367
+ applicationCredentials = await inputFirebaseApplicationCredentials({
7368
+ applicationCredentials,
7369
+ nonInteractive,
7370
+ projectId
7371
+ });
7372
+ return cliEnv;
7373
+ });
7374
+ const currentRegion = await resolveFirebaseRegion({
7375
+ cwd: tmpDir,
7376
+ discoverExistingProject: initializeVariable.status === "ready",
7377
+ nonInteractive,
7378
+ savedRegion: savedInputs.region,
7379
+ cliEnv
7380
+ });
7381
+ const resolvedInputs = {
7382
+ ...savedInputs,
7383
+ applicationCredentials: applicationCredentials || void 0,
7384
+ projectId: initializeVariable.projectId,
7385
+ region: currentRegion
7386
+ };
7387
+ const persistedInputs = (0, _hot_updater_cli_tools.getInitProviderEnvVars)({
7388
+ includeConsentInputs: await (0, _hot_updater_cli_tools.confirmInitInputPersistence)({
7389
+ existingEnv: managedEnv,
7390
+ inputs: resolvedInputs,
7391
+ nonInteractive,
7392
+ provider: initProvider
7393
+ }),
7394
+ inputs: resolvedInputs,
7395
+ provider: initProvider
7396
+ });
7397
+ if (initializeVariable.status === "create") {
7398
+ await createFirebaseProject({
7399
+ cliEnv,
7400
+ projectId: initializeVariable.projectId
7401
+ });
7402
+ await (0, _hot_updater_cli_tools.makeEnv)(persistedInputs);
7403
+ await removeTmpDir();
7404
+ return;
7405
+ }
7406
+ const functionsCode = (0, _hot_updater_cli_tools.transformEnv)(functionsIndexPath, { REGION: currentRegion });
7407
+ await fs.default.promises.writeFile(functionsIndexPath, functionsCode);
7190
7408
  await setEnv({
7191
7409
  projectId: initializeVariable.projectId,
7192
7410
  storageBucket: initializeVariable.storageBucket,
7193
- build
7411
+ build,
7412
+ region: currentRegion,
7413
+ applicationCredentials: persistedInputs[initProvider.inputs.applicationCredentials.envKey]
7194
7414
  });
7195
7415
  if (runtimePackageInfo.serverPackageVersion !== runtimePackageInfo.currentPackageVersion) _hot_updater_cli_tools.p.note(`Using ${_hot_updater_cli_tools.HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV}=${runtimePackageInfo.serverPackageVersion} for Firebase functions deploy.`);
7196
7416
  await _hot_updater_cli_tools.p.tasks([{
7197
7417
  title: "Installing dependencies...",
7198
7418
  task: async () => {
7199
7419
  try {
7200
- await execa("npm", ["install"], {
7201
- cwd: functionsDir,
7202
- shell: true
7203
- });
7420
+ await execa("npm", ["install"], { cwd: functionsDir });
7204
7421
  return "Installed dependencies";
7205
7422
  } catch (error) {
7206
7423
  if (error instanceof ExecaError) _hot_updater_cli_tools.p.log.error(error.stderr || error.stdout || error.message);
@@ -7208,59 +7425,20 @@ const runInit = async ({ build }) => {
7208
7425
  process.exit(1);
7209
7426
  }
7210
7427
  }
7211
- }, {
7212
- title: "Checking existing functions and setting region",
7213
- task: async () => {
7214
- let isFunctionsExist = false;
7215
- try {
7216
- const { stdout } = await execa("npx", [
7217
- "firebase",
7218
- "functions:list",
7219
- "--json"
7220
- ], {
7221
- cwd: tmpDir,
7222
- shell: true
7223
- });
7224
- const hotUpdater = (JSON.parse(stdout).result || []).find((fn) => fn.id === "hot-updater");
7225
- if (hotUpdater?.region) {
7226
- currentRegion = hotUpdater.region;
7227
- isFunctionsExist = true;
7228
- }
7229
- } catch {}
7230
- if (!isFunctionsExist) {
7231
- const selectedRegion = await _hot_updater_cli_tools.p.select({
7232
- message: "Select Region",
7233
- options: REGIONS,
7234
- initialValue: REGIONS[0].value
7235
- });
7236
- if (_hot_updater_cli_tools.p.isCancel(selectedRegion)) {
7237
- _hot_updater_cli_tools.p.cancel("Operation cancelled.");
7238
- process.exit(1);
7239
- }
7240
- currentRegion = selectedRegion;
7241
- }
7242
- if (!currentRegion) {
7243
- _hot_updater_cli_tools.p.log.error("Region is not set");
7244
- await removeTmpDir();
7245
- process.exit(1);
7246
- }
7247
- const code = (0, _hot_updater_cli_tools.transformEnv)(functionsIndexPath, { REGION: currentRegion });
7248
- await fs.default.promises.writeFile(functionsIndexPath, code);
7249
- return `Using ${isFunctionsExist ? "existing" : "new"} functions in region: ${currentRegion}`;
7250
- }
7251
7428
  }]);
7252
- await deployFirestore(tmpDir);
7253
- await deployFunctions(tmpDir);
7429
+ await deployFirestore(tmpDir, nonInteractive, cliEnv);
7430
+ await deployFunctions(tmpDir, nonInteractive, cliEnv);
7254
7431
  await _hot_updater_cli_tools.p.tasks([{
7255
7432
  title: "Check IAM policy",
7256
7433
  async task(message) {
7257
7434
  const functionsList = await execa("npx", [
7258
7435
  "firebase",
7259
7436
  "functions:list",
7260
- "--json"
7437
+ "--json",
7438
+ ...nonInteractive ? ["--non-interactive"] : []
7261
7439
  ], {
7262
7440
  cwd: tmpDir,
7263
- shell: true
7441
+ env: cliEnv
7264
7442
  });
7265
7443
  const account = (JSON.parse(functionsList.stdout).result || []).find((fn) => fn.id === "hot-updater")?.serviceAccount;
7266
7444
  if (!account) {
@@ -7273,7 +7451,7 @@ const runInit = async ({ build }) => {
7273
7451
  "get-iam-policy",
7274
7452
  initializeVariable.projectId,
7275
7453
  "--format=json"
7276
- ], { shell: true });
7454
+ ], { env: cliEnv });
7277
7455
  if (!JSON.parse(checkIam.stdout).bindings.some((binding) => binding.role === "roles/iam.serviceAccountTokenCreator" && binding.members.includes(`serviceAccount:${account}`))) try {
7278
7456
  message("Adding IAM Service Account Token Creator role to the service account");
7279
7457
  await execa("gcloud", [
@@ -7283,8 +7461,8 @@ const runInit = async ({ build }) => {
7283
7461
  `--member=serviceAccount:${account}`,
7284
7462
  "--role=roles/iam.serviceAccountTokenCreator"
7285
7463
  ], {
7286
- stdio: "inherit",
7287
- shell: true
7464
+ env: cliEnv,
7465
+ stdio: "inherit"
7288
7466
  });
7289
7467
  _hot_updater_cli_tools.p.log.success("IAM Service Account Token Creator role has been added to the service account");
7290
7468
  } catch {
@@ -7301,10 +7479,10 @@ const runInit = async ({ build }) => {
7301
7479
  await removeTmpDir();
7302
7480
  process.exit(1);
7303
7481
  }
7304
- await printTemplate(initializeVariable.projectId, currentRegion);
7482
+ await printTemplate(initializeVariable.projectId, currentRegion, cliEnv);
7305
7483
  await removeTmpDir();
7306
7484
  _hot_updater_cli_tools.p.log.message(`Next step: ${(0, _hot_updater_cli_tools.link)("https://hot-updater.dev/docs/managed/firebase#step-3-generated-configurations")}`);
7307
- _hot_updater_cli_tools.p.log.message("Next step: Change GOOGLE_APPLICATION_CREDENTIALS=your-credentials.json in .env file");
7485
+ if (!applicationCredentials) _hot_updater_cli_tools.p.log.message("Next step: Change GOOGLE_APPLICATION_CREDENTIALS=your-credentials.json in .env file");
7308
7486
  _hot_updater_cli_tools.p.log.success("Done! 🎉");
7309
7487
  };
7310
7488
  //#endregion