@hot-updater/firebase 0.36.7 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,7 @@ fs = __toESM(fs);
27
27
  let path = require("path");
28
28
  path = __toESM(path);
29
29
  let _hot_updater_cli_tools = require("@hot-updater/cli-tools");
30
+ let _hot_updater_server = require("@hot-updater/server");
30
31
  let node_url = require("node:url");
31
32
  let node_child_process = require("node:child_process");
32
33
  let node_string_decoder = require("node:string_decoder");
@@ -46,6 +47,12 @@ let node_fs = require("node:fs");
46
47
  let node_stream_promises = require("node:stream/promises");
47
48
  let node_stream = require("node:stream");
48
49
  let node_buffer = require("node:buffer");
50
+ let firebase_admin_app = require("firebase-admin/app");
51
+ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
52
+ let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
53
+ let firebase_admin_firestore = require("firebase-admin/firestore");
54
+ let fs_promises = require("fs/promises");
55
+ fs_promises = __toESM(fs_promises);
49
56
  let node_fs_promises = require("node:fs/promises");
50
57
  node_fs_promises = __toESM(node_fs_promises);
51
58
  //#region ../../node_modules/.pnpm/es-toolkit@1.47.0/node_modules/es-toolkit/dist/_internal/compareValues.mjs
@@ -1323,7 +1330,7 @@ const handleCommand = (filePath, rawArguments, rawOptions) => {
1323
1330
  var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1324
1331
  module.exports = isexe;
1325
1332
  isexe.sync = sync;
1326
- var fs$5 = require("fs");
1333
+ var fs$6 = require("fs");
1327
1334
  function checkPathExt(path, options) {
1328
1335
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
1329
1336
  if (!pathext) return true;
@@ -1340,12 +1347,12 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1340
1347
  return checkPathExt(path, options);
1341
1348
  }
1342
1349
  function isexe(path, options, cb) {
1343
- fs$5.stat(path, function(er, stat) {
1350
+ fs$6.stat(path, function(er, stat) {
1344
1351
  cb(er, er ? false : checkStat(stat, path, options));
1345
1352
  });
1346
1353
  }
1347
1354
  function sync(path, options) {
1348
- return checkStat(fs$5.statSync(path), path, options);
1355
+ return checkStat(fs$6.statSync(path), path, options);
1349
1356
  }
1350
1357
  }));
1351
1358
  //#endregion
@@ -1353,14 +1360,14 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1353
1360
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1354
1361
  module.exports = isexe;
1355
1362
  isexe.sync = sync;
1356
- var fs$4 = require("fs");
1363
+ var fs$5 = require("fs");
1357
1364
  function isexe(path, options, cb) {
1358
- fs$4.stat(path, function(er, stat) {
1365
+ fs$5.stat(path, function(er, stat) {
1359
1366
  cb(er, er ? false : checkStat(stat, options));
1360
1367
  });
1361
1368
  }
1362
1369
  function sync(path, options) {
1363
- return checkStat(fs$4.statSync(path), options);
1370
+ return checkStat(fs$5.statSync(path), options);
1364
1371
  }
1365
1372
  function checkStat(stat, options) {
1366
1373
  return stat.isFile() && checkMode(stat, options);
@@ -1575,16 +1582,16 @@ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) =
1575
1582
  //#endregion
1576
1583
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
1577
1584
  var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1578
- const fs$3 = require("fs");
1585
+ const fs$4 = require("fs");
1579
1586
  const shebangCommand = require_shebang_command();
1580
1587
  function readShebang(command) {
1581
1588
  const size = 150;
1582
1589
  const buffer = Buffer.alloc(size);
1583
1590
  let fd;
1584
1591
  try {
1585
- fd = fs$3.openSync(command, "r");
1586
- fs$3.readSync(fd, buffer, 0, size, 0);
1587
- fs$3.closeSync(fd);
1592
+ fd = fs$4.openSync(command, "r");
1593
+ fs$4.readSync(fd, buffer, 0, size, 0);
1594
+ fs$4.closeSync(fd);
1588
1595
  } catch (e) {}
1589
1596
  return shebangCommand(buffer.toString());
1590
1597
  }
@@ -6712,6 +6719,777 @@ createExeca(mapNode);
6712
6719
  createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6713
6720
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
6714
6721
  //#endregion
6722
+ //#region src/firebaseDatabaseParserShared.ts
6723
+ var FirebaseDatabaseDataError = class extends Error {
6724
+ name = "FirebaseDatabaseDataError";
6725
+ constructor(source) {
6726
+ super(`Invalid Firebase database data at "${source}".`);
6727
+ this.source = source;
6728
+ }
6729
+ };
6730
+ const record = (value, source) => {
6731
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new FirebaseDatabaseDataError(source);
6732
+ return value;
6733
+ };
6734
+ const property = (value, key) => Reflect.get(value, key);
6735
+ const string = (value, source) => {
6736
+ if (typeof value !== "string") throw new FirebaseDatabaseDataError(source);
6737
+ return value;
6738
+ };
6739
+ const nullableString = (value, source) => {
6740
+ if (value === null || value === void 0) return null;
6741
+ return string(value, source);
6742
+ };
6743
+ const boolean = (value, source) => {
6744
+ if (typeof value !== "boolean") throw new FirebaseDatabaseDataError(source);
6745
+ return value;
6746
+ };
6747
+ const number = (value, source) => {
6748
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new FirebaseDatabaseDataError(source);
6749
+ return value;
6750
+ };
6751
+ const byteSize = (value, source) => {
6752
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new FirebaseDatabaseDataError(source);
6753
+ return value;
6754
+ };
6755
+ const stringArray = (value, source) => {
6756
+ if (value === null || value === void 0) return null;
6757
+ if (!Array.isArray(value)) throw new FirebaseDatabaseDataError(source);
6758
+ return value.map((item) => string(item, source));
6759
+ };
6760
+ const platform = (value, source) => {
6761
+ if (value === "android" || value === "ios") return value;
6762
+ throw new FirebaseDatabaseDataError(source);
6763
+ };
6764
+ //#endregion
6765
+ //#region src/firebaseDatabaseParser.ts
6766
+ const metadata = (value, source) => {
6767
+ if (!(0, _hot_updater_plugin_core.isDatabaseMetadataObject)(value)) throw new FirebaseDatabaseDataError(source);
6768
+ return value;
6769
+ };
6770
+ const requiredNullableString = (input, key, source) => {
6771
+ if (!Object.hasOwn(input, key)) throw new FirebaseDatabaseDataError(source);
6772
+ return nullableString(property(input, key), source);
6773
+ };
6774
+ const parseFirebaseBundleRow = (value, source) => {
6775
+ const input = record(value, source);
6776
+ return {
6777
+ id: string(property(input, "id"), source),
6778
+ platform: platform(property(input, "platform"), source),
6779
+ file_hash: string(property(input, "file_hash"), source),
6780
+ git_commit_hash: nullableString(property(input, "git_commit_hash"), source),
6781
+ storage_uri: string(property(input, "storage_uri"), source),
6782
+ archive_byte_size: byteSize(property(input, "archive_byte_size"), source),
6783
+ metadata: metadata(property(input, "metadata"), source),
6784
+ manifest_storage_uri: nullableString(property(input, "manifest_storage_uri"), source),
6785
+ manifest_file_hash: nullableString(property(input, "manifest_file_hash"), source),
6786
+ asset_base_storage_uri: nullableString(property(input, "asset_base_storage_uri"), source)
6787
+ };
6788
+ };
6789
+ const parseFirebaseChannelRow = (value, source) => {
6790
+ const input = record(value, source);
6791
+ return {
6792
+ id: string(property(input, "id"), source),
6793
+ name: string(property(input, "name"), source)
6794
+ };
6795
+ };
6796
+ const parseFirebasePatchRow = (value, source) => {
6797
+ const input = record(value, source);
6798
+ return {
6799
+ id: string(property(input, "id"), source),
6800
+ bundle_id: string(property(input, "bundle_id"), source),
6801
+ base_bundle_id: string(property(input, "base_bundle_id"), source),
6802
+ base_file_hash: string(property(input, "base_file_hash"), source),
6803
+ patch_file_hash: string(property(input, "patch_file_hash"), source),
6804
+ patch_storage_uri: string(property(input, "patch_storage_uri"), source),
6805
+ byte_size: byteSize(property(input, "byte_size"), source),
6806
+ order_index: number(property(input, "order_index"), source)
6807
+ };
6808
+ };
6809
+ const parseFirebaseBundleEventRow = (value, source) => {
6810
+ const input = record(value, source);
6811
+ const type = string(property(input, "type"), source);
6812
+ const fromBundleId = requiredNullableString(input, "from_bundle_id", source);
6813
+ const updateStrategy = requiredNullableString(input, "update_strategy", source);
6814
+ if (!((type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new FirebaseDatabaseDataError(source);
6815
+ return {
6816
+ id: string(property(input, "id"), source),
6817
+ type,
6818
+ install_id: string(property(input, "install_id"), source),
6819
+ user_id: nullableString(property(input, "user_id"), source),
6820
+ username: nullableString(property(input, "username"), source),
6821
+ from_release_id: requiredNullableString(input, "from_release_id", source),
6822
+ from_bundle_id: fromBundleId,
6823
+ to_release_id: requiredNullableString(input, "to_release_id", source),
6824
+ to_bundle_id: string(property(input, "to_bundle_id"), source),
6825
+ platform: platform(property(input, "platform"), source),
6826
+ app_version: string(property(input, "app_version"), source),
6827
+ channel: string(property(input, "channel"), source),
6828
+ cohort: string(property(input, "cohort"), source),
6829
+ update_strategy: updateStrategy,
6830
+ fingerprint_hash: nullableString(property(input, "fingerprint_hash"), source),
6831
+ sdk_version: nullableString(property(input, "sdk_version"), source),
6832
+ received_at_ms: number(property(input, "received_at_ms"), source)
6833
+ };
6834
+ };
6835
+ const parseFirebaseApiKeyRow = (value, source) => {
6836
+ const input = record(value, source);
6837
+ const role = string(property(input, "role"), source);
6838
+ if (role !== "client") throw new FirebaseDatabaseDataError(source);
6839
+ const revokedAt = property(input, "revoked_at_ms");
6840
+ return {
6841
+ id: string(property(input, "id"), source),
6842
+ hash: string(property(input, "hash"), source),
6843
+ name: string(property(input, "name"), source),
6844
+ prefix: string(property(input, "prefix"), source),
6845
+ role,
6846
+ created_at_ms: number(property(input, "created_at_ms"), source),
6847
+ revoked_at_ms: revokedAt === null ? null : number(revokedAt, source)
6848
+ };
6849
+ };
6850
+ const parseFirebaseReleaseRow = (value, source) => {
6851
+ const input = record(value, source);
6852
+ const kind = string(property(input, "kind"), source);
6853
+ const strategy = string(property(input, "strategy"), source);
6854
+ const operation = string(property(input, "operation"), source);
6855
+ const targetCohorts = stringArray(property(input, "target_cohorts"), source);
6856
+ if (kind !== "BUNDLE" && kind !== "EMBEDDED" || strategy !== "APP_VERSION" && strategy !== "FINGERPRINT" || operation !== "DEPLOY" && operation !== "PROMOTE" && operation !== "ROLLBACK" || targetCohorts === null) throw new FirebaseDatabaseDataError(source);
6857
+ return {
6858
+ id: string(property(input, "id"), source),
6859
+ revision: number(property(input, "revision"), source),
6860
+ scope_key: string(property(input, "scope_key"), source),
6861
+ channel_id: string(property(input, "channel_id"), source),
6862
+ platform: platform(property(input, "platform"), source),
6863
+ kind,
6864
+ bundle_id: nullableString(property(input, "bundle_id"), source),
6865
+ strategy,
6866
+ target_app_version: nullableString(property(input, "target_app_version"), source),
6867
+ fingerprint_hash: nullableString(property(input, "fingerprint_hash"), source),
6868
+ enabled: boolean(property(input, "enabled"), source),
6869
+ should_force_update: boolean(property(input, "should_force_update"), source),
6870
+ message: nullableString(property(input, "message"), source),
6871
+ rollout_cohort_count: number(property(input, "rollout_cohort_count"), source),
6872
+ target_cohorts: targetCohorts,
6873
+ operation,
6874
+ source_release_id: nullableString(property(input, "source_release_id"), source),
6875
+ created_at_ms: number(property(input, "created_at_ms"), source),
6876
+ updated_at_ms: number(property(input, "updated_at_ms"), source)
6877
+ };
6878
+ };
6879
+ const parseFirebaseReleaseCatalogRow = (value, source) => {
6880
+ const input = record(value, source);
6881
+ const strategy = string(property(input, "strategy"), source);
6882
+ if (strategy !== "APP_VERSION" && strategy !== "FINGERPRINT") throw new FirebaseDatabaseDataError(source);
6883
+ return {
6884
+ scope_key: string(property(input, "scope_key"), source),
6885
+ catalog_id: string(property(input, "catalog_id"), source),
6886
+ strategy,
6887
+ channel_id: string(property(input, "channel_id"), source),
6888
+ channel_key: string(property(input, "channel_key"), source),
6889
+ platform: platform(property(input, "platform"), source),
6890
+ fingerprint_hash: nullableString(property(input, "fingerprint_hash"), source),
6891
+ generation: number(property(input, "generation"), source),
6892
+ payload: string(property(input, "payload"), source),
6893
+ catalog_hash: string(property(input, "catalog_hash"), source),
6894
+ byte_size: number(property(input, "byte_size"), source),
6895
+ is_tombstone: boolean(property(input, "is_tombstone"), source),
6896
+ updated_at_ms: number(property(input, "updated_at_ms"), source)
6897
+ };
6898
+ };
6899
+ //#endregion
6900
+ //#region src/firebaseDatabaseQuery.ts
6901
+ const compare = (left, right) => {
6902
+ if (typeof left === "number" && typeof right === "number") return left - right;
6903
+ return String(left).localeCompare(String(right));
6904
+ };
6905
+ const normalizeStringComparison = (actual, expected, mode) => {
6906
+ if (typeof actual !== "string") return null;
6907
+ return mode === "insensitive" ? [actual.toLocaleLowerCase(), expected.toLocaleLowerCase()] : [actual, expected];
6908
+ };
6909
+ const matchesCondition = (row, condition) => {
6910
+ const actual = Reflect.get(row, condition.field);
6911
+ const expected = condition.value;
6912
+ switch (condition.operator ?? "eq") {
6913
+ case "eq": {
6914
+ if (typeof expected !== "string") return actual === expected;
6915
+ const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
6916
+ return comparison !== null && comparison[0] === comparison[1];
6917
+ }
6918
+ case "ne": {
6919
+ if (actual === null || actual === void 0) return false;
6920
+ if (typeof expected !== "string") return actual !== expected;
6921
+ const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
6922
+ return comparison === null || comparison[0] !== comparison[1];
6923
+ }
6924
+ case "gt":
6925
+ if (actual === null || actual === void 0) return false;
6926
+ return compare(actual, expected) > 0;
6927
+ case "gte":
6928
+ if (actual === null || actual === void 0) return false;
6929
+ return compare(actual, expected) >= 0;
6930
+ case "lt":
6931
+ if (actual === null || actual === void 0) return false;
6932
+ return compare(actual, expected) < 0;
6933
+ case "lte":
6934
+ if (actual === null || actual === void 0) return false;
6935
+ return compare(actual, expected) <= 0;
6936
+ case "in":
6937
+ if (!Array.isArray(expected)) return false;
6938
+ return expected.some((candidate) => candidate === actual);
6939
+ case "not_in": {
6940
+ if (!Array.isArray(expected)) return false;
6941
+ const values = expected;
6942
+ return values.length === 0 || actual !== null && actual !== void 0 && values.every((candidate) => candidate !== actual);
6943
+ }
6944
+ case "contains": {
6945
+ if (typeof expected !== "string") return false;
6946
+ const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
6947
+ return comparison?.[0].includes(comparison[1]) ?? false;
6948
+ }
6949
+ case "starts_with": {
6950
+ if (typeof expected !== "string") return false;
6951
+ const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
6952
+ return comparison?.[0].startsWith(comparison[1]) ?? false;
6953
+ }
6954
+ case "ends_with": {
6955
+ if (typeof expected !== "string") return false;
6956
+ const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
6957
+ return comparison?.[0].endsWith(comparison[1]) ?? false;
6958
+ }
6959
+ }
6960
+ };
6961
+ const matchesFirebaseDatabaseWhere = (row, where) => {
6962
+ const first = where?.[0];
6963
+ if (!first) return true;
6964
+ let result = matchesCondition(row, first);
6965
+ for (const condition of where.slice(1)) {
6966
+ const current = matchesCondition(row, condition);
6967
+ result = condition.connector === "OR" ? result || current : result && current;
6968
+ }
6969
+ return result;
6970
+ };
6971
+ const queryFirebaseDatabaseRows = (rows, input) => {
6972
+ const filtered = rows.filter((row) => matchesFirebaseDatabaseWhere(row, input.where));
6973
+ const orderBy = input.orderBy;
6974
+ if (orderBy !== void 0) filtered.sort((left, right) => {
6975
+ for (const clause of orderBy) {
6976
+ const leftValue = Reflect.get(left, clause.field);
6977
+ const rightValue = Reflect.get(right, clause.field);
6978
+ if (leftValue === rightValue) continue;
6979
+ if (clause.nulls !== void 0 && (leftValue === null || rightValue === null)) {
6980
+ const nullComparison = leftValue === null ? -1 : 1;
6981
+ return clause.nulls === "first" ? nullComparison : -nullComparison;
6982
+ }
6983
+ const comparison = compare(leftValue, rightValue);
6984
+ if (comparison !== 0) return clause.direction === "asc" ? comparison : -comparison;
6985
+ }
6986
+ return 0;
6987
+ });
6988
+ const distinctOn = input.distinctOn;
6989
+ if (distinctOn === void 0) return filtered.slice(input.offset, input.offset + input.limit);
6990
+ const seen = /* @__PURE__ */ new Set();
6991
+ return filtered.filter((row) => {
6992
+ const key = JSON.stringify(distinctOn.fields.map((field) => Reflect.get(row, field)));
6993
+ if (seen.has(key)) return false;
6994
+ seen.add(key);
6995
+ return true;
6996
+ }).slice(input.offset, input.offset + input.limit);
6997
+ };
6998
+ //#endregion
6999
+ //#region src/firebaseDatabaseState.ts
7000
+ var FirebaseDatabaseConstraintError = class extends Error {
7001
+ name = "FirebaseDatabaseConstraintError";
7002
+ constructor(constraint) {
7003
+ super(`Firebase database constraint failed: ${constraint}`);
7004
+ this.constraint = constraint;
7005
+ }
7006
+ };
7007
+ const cloneFirebaseDatabaseSnapshot = (snapshot) => ({
7008
+ bundles: new Map(snapshot.bundles),
7009
+ bundlePatches: new Map(snapshot.bundlePatches),
7010
+ bundleEvents: new Map(snapshot.bundleEvents),
7011
+ channels: new Map(snapshot.channels),
7012
+ apiKeys: new Map(snapshot.apiKeys),
7013
+ releaseCatalogs: new Map(snapshot.releaseCatalogs),
7014
+ releases: new Map(snapshot.releases)
7015
+ });
7016
+ const requireUnique = (rows, id, model) => {
7017
+ if (rows.has(id)) throw new FirebaseDatabaseConstraintError(`${model}.id.unique`);
7018
+ };
7019
+ const distinctCount = (rows, fields) => {
7020
+ if (fields === void 0) return rows.length;
7021
+ return new Set(rows.map((row) => JSON.stringify(fields.map((field) => Reflect.get(row, field))))).size;
7022
+ };
7023
+ const createFirebaseDatabaseState = (snapshot) => ({
7024
+ async create(input) {
7025
+ switch (input.model) {
7026
+ case "bundles":
7027
+ requireUnique(snapshot.bundles, input.data.id, input.model);
7028
+ snapshot.bundles.set(input.data.id, input.data);
7029
+ return input.data;
7030
+ case "bundle_patches":
7031
+ requireUnique(snapshot.bundlePatches, input.data.id, input.model);
7032
+ if (!snapshot.bundles.has(input.data.bundle_id)) throw new FirebaseDatabaseConstraintError("bundle_patches.bundle_id.foreign-key");
7033
+ if (!snapshot.bundles.has(input.data.base_bundle_id)) throw new FirebaseDatabaseConstraintError("bundle_patches.base_bundle_id.foreign-key");
7034
+ snapshot.bundlePatches.set(input.data.id, input.data);
7035
+ return input.data;
7036
+ case "bundle_events":
7037
+ requireUnique(snapshot.bundleEvents, input.data.id, input.model);
7038
+ snapshot.bundleEvents.set(input.data.id, input.data);
7039
+ return input.data;
7040
+ case "releases":
7041
+ requireUnique(snapshot.releases, input.data.id, input.model);
7042
+ if (!snapshot.channels.has(input.data.channel_id)) throw new FirebaseDatabaseConstraintError("releases.channel_id.foreign-key");
7043
+ if (input.data.bundle_id !== null && !snapshot.bundles.has(input.data.bundle_id)) throw new FirebaseDatabaseConstraintError("releases.bundle_id.foreign-key");
7044
+ snapshot.releases.set(input.data.id, input.data);
7045
+ return input.data;
7046
+ case "release_catalogs":
7047
+ if (snapshot.releaseCatalogs.has(input.data.scope_key)) throw new FirebaseDatabaseConstraintError("release_catalogs.scope_key.unique");
7048
+ snapshot.releaseCatalogs.set(input.data.scope_key, input.data);
7049
+ return input.data;
7050
+ case "channels": {
7051
+ const existing = [...snapshot.channels.values()].find(({ name }) => name === input.data.name);
7052
+ if (existing && input.onConflict === "ignore") return existing;
7053
+ requireUnique(snapshot.channels, input.data.id, input.model);
7054
+ if (existing) throw new FirebaseDatabaseConstraintError("channels.name.unique");
7055
+ snapshot.channels.set(input.data.id, input.data);
7056
+ return input.data;
7057
+ }
7058
+ case "api_keys": {
7059
+ const existing = [...snapshot.apiKeys.values()].find(({ hash }) => hash === input.data.hash);
7060
+ if (existing && input.onConflict === "ignore") return existing;
7061
+ requireUnique(snapshot.apiKeys, input.data.id, input.model);
7062
+ if (existing) throw new FirebaseDatabaseConstraintError("api_keys.hash.unique");
7063
+ snapshot.apiKeys.set(input.data.id, input.data);
7064
+ return input.data;
7065
+ }
7066
+ }
7067
+ },
7068
+ async update(input) {
7069
+ if (input.model === "api_keys") {
7070
+ const current = [...snapshot.apiKeys.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
7071
+ if (!current) return null;
7072
+ const updated = {
7073
+ ...current,
7074
+ ...input.update
7075
+ };
7076
+ snapshot.apiKeys.set(current.id, updated);
7077
+ return updated;
7078
+ }
7079
+ if (input.model === "releases") {
7080
+ const current = [...snapshot.releases.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
7081
+ if (!current) return null;
7082
+ const updated = {
7083
+ ...current,
7084
+ ...input.update
7085
+ };
7086
+ snapshot.releases.set(current.id, updated);
7087
+ return updated;
7088
+ }
7089
+ if (input.model === "release_catalogs") {
7090
+ const current = [...snapshot.releaseCatalogs.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
7091
+ if (!current) return null;
7092
+ const updated = {
7093
+ ...current,
7094
+ ...input.update
7095
+ };
7096
+ snapshot.releaseCatalogs.set(current.scope_key, updated);
7097
+ return updated;
7098
+ }
7099
+ const current = [...snapshot.bundles.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
7100
+ if (!current) return null;
7101
+ const updated = {
7102
+ ...current,
7103
+ ...input.update
7104
+ };
7105
+ snapshot.bundles.set(current.id, updated);
7106
+ return updated;
7107
+ },
7108
+ async delete(input) {
7109
+ if (input.model === "channels") {
7110
+ for (const row of snapshot.channels.values()) if (matchesFirebaseDatabaseWhere(row, input.where)) snapshot.channels.delete(row.id);
7111
+ return;
7112
+ }
7113
+ if (input.model === "bundle_patches") {
7114
+ for (const row of snapshot.bundlePatches.values()) if (matchesFirebaseDatabaseWhere(row, input.where)) snapshot.bundlePatches.delete(row.id);
7115
+ return;
7116
+ }
7117
+ if (input.model === "releases") {
7118
+ for (const row of snapshot.releases.values()) if (matchesFirebaseDatabaseWhere(row, input.where)) snapshot.releases.delete(row.id);
7119
+ return;
7120
+ }
7121
+ const removedIds = new Set([...snapshot.bundles.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)).map(({ id }) => id));
7122
+ for (const id of removedIds) snapshot.bundles.delete(id);
7123
+ for (const patch of snapshot.bundlePatches.values()) if (removedIds.has(patch.bundle_id) || removedIds.has(patch.base_bundle_id)) snapshot.bundlePatches.delete(patch.id);
7124
+ },
7125
+ async count(input) {
7126
+ switch (input.model) {
7127
+ case "bundles": return distinctCount([...snapshot.bundles.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
7128
+ case "bundle_patches": return distinctCount([...snapshot.bundlePatches.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
7129
+ case "releases": return distinctCount([...snapshot.releases.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
7130
+ }
7131
+ },
7132
+ async findOne(input) {
7133
+ switch (input.model) {
7134
+ case "bundles": return [...snapshot.bundles.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7135
+ case "api_keys": return [...snapshot.apiKeys.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7136
+ case "channels": return [...snapshot.channels.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7137
+ case "bundle_patches": return [...snapshot.bundlePatches.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7138
+ case "releases": return [...snapshot.releases.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7139
+ case "release_catalogs": return [...snapshot.releaseCatalogs.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
7140
+ }
7141
+ },
7142
+ async findMany(input) {
7143
+ switch (input.model) {
7144
+ case "bundles": return queryFirebaseDatabaseRows([...snapshot.bundles.values()], input);
7145
+ case "bundle_patches": return queryFirebaseDatabaseRows([...snapshot.bundlePatches.values()], input);
7146
+ case "bundle_events": return queryFirebaseDatabaseRows([...snapshot.bundleEvents.values()], input);
7147
+ case "channels": return queryFirebaseDatabaseRows([...snapshot.channels.values()], input);
7148
+ case "api_keys": return queryFirebaseDatabaseRows([...snapshot.apiKeys.values()], input);
7149
+ case "releases": return queryFirebaseDatabaseRows([...snapshot.releases.values()], input);
7150
+ case "release_catalogs": return queryFirebaseDatabaseRows([...snapshot.releaseCatalogs.values()], input);
7151
+ }
7152
+ }
7153
+ });
7154
+ //#endregion
7155
+ //#region src/firebaseInfrastructureNames.ts
7156
+ const FIREBASE_V1_FUNCTION_NAME = "hot-updater-v1";
7157
+ const FIREBASE_V1_COLLECTION_NAMES = {
7158
+ apiKeys: "hot_updater_v1_api_keys",
7159
+ bundleEvents: "hot_updater_v1_bundle_events",
7160
+ bundlePatches: "hot_updater_v1_bundle_patches",
7161
+ bundles: "hot_updater_v1_bundles",
7162
+ channels: "hot_updater_v1_channels",
7163
+ releaseCatalogs: "hot_updater_v1_release_catalogs",
7164
+ releases: "hot_updater_v1_releases",
7165
+ settings: "hot_updater_v1_private_settings"
7166
+ };
7167
+ //#endregion
7168
+ //#region src/firebaseDatabasePersistence.ts
7169
+ var FirebaseDatabaseAdapterVersionError = class extends Error {
7170
+ name = "FirebaseDatabaseAdapterVersionError";
7171
+ constructor(version) {
7172
+ super(`Unsupported Firebase database adapter version: ${String(version)}`);
7173
+ this.version = version;
7174
+ }
7175
+ };
7176
+ const createFirebaseDatabaseCollections = (db) => ({
7177
+ bundles: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundles),
7178
+ bundlePatches: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundlePatches),
7179
+ bundleEvents: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundleEvents),
7180
+ channels: db.collection(FIREBASE_V1_COLLECTION_NAMES.channels),
7181
+ apiKeys: db.collection(FIREBASE_V1_COLLECTION_NAMES.apiKeys),
7182
+ releaseCatalogs: db.collection(FIREBASE_V1_COLLECTION_NAMES.releaseCatalogs),
7183
+ releases: db.collection(FIREBASE_V1_COLLECTION_NAMES.releases),
7184
+ settings: db.collection(FIREBASE_V1_COLLECTION_NAMES.settings)
7185
+ });
7186
+ const firebaseChannelDocumentId = (name) => `name_${Buffer.from(name, "utf8").toString("base64url")}`;
7187
+ const firebaseChannelIdDocumentId = (id) => `channel_id_${Buffer.from(id, "utf8").toString("base64url")}`;
7188
+ const requireFirebaseDocumentKey = (model, documentId, row) => {
7189
+ if (documentId !== ("id" in row ? row.id : row.scope_key)) throw new FirebaseDatabaseConstraintError(`${model}.id.document-key`);
7190
+ return row;
7191
+ };
7192
+ const documentMap = (model, documents) => {
7193
+ const rows = /* @__PURE__ */ new Map();
7194
+ for (const { row } of documents) {
7195
+ const key = "id" in row ? row.id : row.scope_key;
7196
+ if (rows.has(key)) throw new FirebaseDatabaseConstraintError(`${model}.id.unique`);
7197
+ rows.set(key, row);
7198
+ }
7199
+ for (const { document, row } of documents) requireFirebaseDocumentKey(model, document.id, row);
7200
+ return rows;
7201
+ };
7202
+ const bundleMap = (snapshot) => documentMap("bundles", snapshot.docs.map((document) => ({
7203
+ document,
7204
+ row: parseFirebaseBundleRow(document.data(), `bundles/${document.id}`)
7205
+ })));
7206
+ const patchMap = (snapshot) => documentMap("bundle_patches", snapshot.docs.map((document) => ({
7207
+ document,
7208
+ row: parseFirebasePatchRow(document.data(), `bundle_patches/${document.id}`)
7209
+ })));
7210
+ const eventMap = (snapshot) => documentMap("bundle_events", snapshot.docs.map((document) => ({
7211
+ document,
7212
+ row: parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)
7213
+ })));
7214
+ const channelMap = (snapshot) => {
7215
+ const rows = /* @__PURE__ */ new Map();
7216
+ const names = /* @__PURE__ */ new Set();
7217
+ for (const document of snapshot.docs) {
7218
+ const row = parseFirebaseChannelRow(document.data(), `channels/${document.id}`);
7219
+ if (document.id !== firebaseChannelDocumentId(row.name)) throw new FirebaseDatabaseConstraintError("channels.name.document-key");
7220
+ if (rows.has(row.id)) throw new FirebaseDatabaseConstraintError("channels.id.unique");
7221
+ if (names.has(row.name)) throw new FirebaseDatabaseConstraintError("channels.name.unique");
7222
+ rows.set(row.id, row);
7223
+ names.add(row.name);
7224
+ }
7225
+ return rows;
7226
+ };
7227
+ const loadFirebaseChannels = async (collections) => [...channelMap(await collections.channels.get()).values()];
7228
+ const apiKeyMap = (snapshot) => documentMap("api_keys", snapshot.docs.map((document) => ({
7229
+ document,
7230
+ row: parseFirebaseApiKeyRow(document.data(), `api_keys/${document.id}`)
7231
+ })));
7232
+ const releaseMap = (snapshot) => documentMap("releases", snapshot.docs.map((document) => ({
7233
+ document,
7234
+ row: parseFirebaseReleaseRow(document.data(), `releases/${document.id}`)
7235
+ })));
7236
+ const releaseCatalogMap = (snapshot) => documentMap("release_catalogs", snapshot.docs.map((document) => ({
7237
+ document,
7238
+ row: parseFirebaseReleaseCatalogRow(document.data(), `release_catalogs/${document.id}`)
7239
+ })));
7240
+ const toSnapshot = (documents) => {
7241
+ return {
7242
+ bundles: bundleMap(documents[0]),
7243
+ bundlePatches: patchMap(documents[1]),
7244
+ bundleEvents: eventMap(documents[2]),
7245
+ channels: channelMap(documents[3]),
7246
+ apiKeys: apiKeyMap(documents[4]),
7247
+ releases: releaseMap(documents[5]),
7248
+ releaseCatalogs: releaseCatalogMap(documents[6])
7249
+ };
7250
+ };
7251
+ const loadFirebaseDatabaseSnapshot = async (collections) => {
7252
+ const [bundles, patches, events, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
7253
+ collections.bundles.get(),
7254
+ collections.bundlePatches.get(),
7255
+ collections.bundleEvents.get(),
7256
+ collections.channels.get(),
7257
+ collections.apiKeys.get(),
7258
+ collections.releases.get(),
7259
+ collections.releaseCatalogs.get()
7260
+ ]);
7261
+ return toSnapshot([
7262
+ bundles,
7263
+ patches,
7264
+ events,
7265
+ channels,
7266
+ apiKeys,
7267
+ releases,
7268
+ releaseCatalogs
7269
+ ]);
7270
+ };
7271
+ const loadFirebaseTransactionSnapshot = async (transaction, collections) => {
7272
+ const [bundles, patches, events, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
7273
+ transaction.get(collections.bundles),
7274
+ transaction.get(collections.bundlePatches),
7275
+ transaction.get(collections.bundleEvents),
7276
+ transaction.get(collections.channels),
7277
+ transaction.get(collections.apiKeys),
7278
+ transaction.get(collections.releases),
7279
+ transaction.get(collections.releaseCatalogs)
7280
+ ]);
7281
+ return toSnapshot([
7282
+ bundles,
7283
+ patches,
7284
+ events,
7285
+ channels,
7286
+ apiKeys,
7287
+ releases,
7288
+ releaseCatalogs
7289
+ ]);
7290
+ };
7291
+ const persistCollection = ({ transaction, collection, before, after, documentId }) => {
7292
+ for (const [id, row] of before) if (!after.has(id)) transaction.delete(collection.doc(documentId(row)));
7293
+ for (const [id, row] of after) if (JSON.stringify(before.get(id)) !== JSON.stringify(row)) transaction.set(collection.doc(documentId(row)), row, { merge: true });
7294
+ };
7295
+ const persistFirebaseDatabaseSnapshot = ({ transaction, collections, before, after }) => {
7296
+ persistCollection({
7297
+ transaction,
7298
+ collection: collections.bundles,
7299
+ before: before.bundles,
7300
+ after: after.bundles,
7301
+ documentId: (row) => row.id
7302
+ });
7303
+ persistCollection({
7304
+ transaction,
7305
+ collection: collections.bundlePatches,
7306
+ before: before.bundlePatches,
7307
+ after: after.bundlePatches,
7308
+ documentId: (row) => row.id
7309
+ });
7310
+ persistCollection({
7311
+ transaction,
7312
+ collection: collections.bundleEvents,
7313
+ before: before.bundleEvents,
7314
+ after: after.bundleEvents,
7315
+ documentId: (row) => row.id
7316
+ });
7317
+ persistCollection({
7318
+ transaction,
7319
+ collection: collections.channels,
7320
+ before: before.channels,
7321
+ after: after.channels,
7322
+ documentId: (row) => firebaseChannelDocumentId(row.name)
7323
+ });
7324
+ for (const [id] of before.channels) if (!after.channels.has(id)) transaction.delete(collections.settings.doc(firebaseChannelIdDocumentId(id)));
7325
+ for (const [id, row] of after.channels) if (JSON.stringify(before.channels.get(id)) !== JSON.stringify(row)) transaction.set(collections.settings.doc(firebaseChannelIdDocumentId(id)), row);
7326
+ persistCollection({
7327
+ transaction,
7328
+ collection: collections.apiKeys,
7329
+ before: before.apiKeys,
7330
+ after: after.apiKeys,
7331
+ documentId: (row) => row.id
7332
+ });
7333
+ persistCollection({
7334
+ transaction,
7335
+ collection: collections.releases,
7336
+ before: before.releases,
7337
+ after: after.releases,
7338
+ documentId: (row) => row.id
7339
+ });
7340
+ persistCollection({
7341
+ transaction,
7342
+ collection: collections.releaseCatalogs,
7343
+ before: before.releaseCatalogs,
7344
+ after: after.releaseCatalogs,
7345
+ documentId: (row) => row.scope_key
7346
+ });
7347
+ };
7348
+ const migrateFirebaseDatabase = async (_db, collections) => {
7349
+ const versionDocument = collections.settings.doc("database_adapter_version");
7350
+ const version = await versionDocument.get();
7351
+ const adapterVersion = version.data()?.version;
7352
+ if (adapterVersion === 4) return;
7353
+ if (version.exists) throw new FirebaseDatabaseAdapterVersionError(adapterVersion);
7354
+ if ((await Promise.all([
7355
+ collections.bundles.limit(1).get(),
7356
+ collections.bundlePatches.limit(1).get(),
7357
+ collections.channels.limit(1).get(),
7358
+ collections.releases.limit(1).get(),
7359
+ collections.releaseCatalogs.limit(1).get()
7360
+ ])).some((snapshot) => !snapshot.empty)) throw new FirebaseDatabaseAdapterVersionError("v0");
7361
+ try {
7362
+ await versionDocument.create({ version: 4 });
7363
+ } catch (error) {
7364
+ if ((await versionDocument.get()).data()?.version !== 4) throw error;
7365
+ }
7366
+ };
7367
+ //#endregion
7368
+ //#region src/firebaseDatabase.ts
7369
+ const exactId = (input) => {
7370
+ if (input.where?.length !== 1) return void 0;
7371
+ const [condition] = input.where;
7372
+ return condition.field === "id" && (condition.operator === void 0 || condition.operator === "eq") && typeof condition.value === "string" ? condition.value : void 0;
7373
+ };
7374
+ const firebaseDatabase = (config) => {
7375
+ const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("firebaseDatabase", (() => {
7376
+ const db = (0, firebase_admin_firestore.getFirestore)((0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(config));
7377
+ const collections = createFirebaseDatabaseCollections(db);
7378
+ let migration;
7379
+ const ensureMigrated = () => {
7380
+ migration ??= migrateFirebaseDatabase(db, collections).catch((error) => {
7381
+ migration = void 0;
7382
+ throw error;
7383
+ });
7384
+ return migration;
7385
+ };
7386
+ const mutate = async (operation) => {
7387
+ await ensureMigrated();
7388
+ return db.runTransaction(async (transaction) => {
7389
+ const before = await loadFirebaseTransactionSnapshot(transaction, collections);
7390
+ const after = cloneFirebaseDatabaseSnapshot(before);
7391
+ const result = await operation(createFirebaseDatabaseState(after));
7392
+ persistFirebaseDatabaseSnapshot({
7393
+ transaction,
7394
+ collections,
7395
+ before,
7396
+ after
7397
+ });
7398
+ return result;
7399
+ });
7400
+ };
7401
+ const read = async (operation) => {
7402
+ await ensureMigrated();
7403
+ return operation(createFirebaseDatabaseState(await loadFirebaseDatabaseSnapshot(collections)));
7404
+ };
7405
+ return {
7406
+ create: (input) => mutate((database) => database.create(input)),
7407
+ update: (input) => mutate((database) => database.update(input)),
7408
+ delete: (input) => mutate((database) => database.delete(input)),
7409
+ count: (input) => read((database) => database.count(input)),
7410
+ findOne: async (input) => {
7411
+ const id = exactId(input);
7412
+ if (id === void 0) return read((database) => database.findOne(input));
7413
+ await ensureMigrated();
7414
+ switch (input.model) {
7415
+ case "bundles": {
7416
+ const document = await collections.bundles.doc(id).get();
7417
+ return document.exists ? requireFirebaseDocumentKey("bundles", document.id, parseFirebaseBundleRow(document.data(), `bundles/${document.id}`)) : null;
7418
+ }
7419
+ case "bundle_patches": {
7420
+ const document = await collections.bundlePatches.doc(id).get();
7421
+ return document.exists ? requireFirebaseDocumentKey("bundle_patches", document.id, parseFirebasePatchRow(document.data(), `bundle_patches/${document.id}`)) : null;
7422
+ }
7423
+ case "api_keys": {
7424
+ const document = await collections.apiKeys.doc(id).get();
7425
+ return document.exists ? requireFirebaseDocumentKey("api_keys", document.id, parseFirebaseApiKeyRow(document.data(), `api_keys/${document.id}`)) : null;
7426
+ }
7427
+ default: return read((database) => database.findOne(input));
7428
+ }
7429
+ },
7430
+ findMany: async (input) => {
7431
+ if (input.model !== "channels") return read((database) => database.findMany(input));
7432
+ await ensureMigrated();
7433
+ return queryFirebaseDatabaseRows(await loadFirebaseChannels(collections), input);
7434
+ },
7435
+ insertChannel: async (input) => {
7436
+ await ensureMigrated();
7437
+ return db.runTransaction(async (transaction) => {
7438
+ const reference = collections.channels.doc(firebaseChannelDocumentId(input.row.name));
7439
+ const idReference = collections.settings.doc(firebaseChannelIdDocumentId(input.row.id));
7440
+ const [document, idDocument] = await transaction.getAll(reference, idReference);
7441
+ if (idDocument.exists) {
7442
+ const row = parseFirebaseChannelRow(idDocument.data(), `${FIREBASE_V1_COLLECTION_NAMES.settings}/${idDocument.id}`);
7443
+ if (row.id !== input.row.id || row.name !== input.row.name) throw new FirebaseDatabaseConstraintError("channels.id.registry");
7444
+ }
7445
+ if (idDocument.exists && !document.exists) throw new FirebaseDatabaseConstraintError("channels.id.unique");
7446
+ if (document.exists) {
7447
+ const row = parseFirebaseChannelRow(document.data(), `channels/${document.id}`);
7448
+ if (document.id !== firebaseChannelDocumentId(row.name)) throw new FirebaseDatabaseConstraintError("channels.name.document-key");
7449
+ return {
7450
+ row,
7451
+ inserted: false
7452
+ };
7453
+ }
7454
+ transaction.create(reference, input.row);
7455
+ transaction.create(idReference, input.row);
7456
+ return {
7457
+ row: input.row,
7458
+ inserted: true
7459
+ };
7460
+ });
7461
+ },
7462
+ deleteChannel: async ({ id }) => {
7463
+ await ensureMigrated();
7464
+ return db.runTransaction(async (transaction) => {
7465
+ const idReference = collections.settings.doc(firebaseChannelIdDocumentId(id));
7466
+ const idDocument = await transaction.get(idReference);
7467
+ if (!idDocument.exists) return {
7468
+ deleted: false,
7469
+ reason: "not_found"
7470
+ };
7471
+ const row = parseFirebaseChannelRow(idDocument.data(), `${FIREBASE_V1_COLLECTION_NAMES.settings}/${idDocument.id}`);
7472
+ const reference = collections.channels.doc(firebaseChannelDocumentId(row.name));
7473
+ if (!(await transaction.get(reference)).exists || row.id !== id) throw new FirebaseDatabaseConstraintError("channels.id.registry");
7474
+ if (!(await transaction.get(collections.releases.where("channel_id", "==", id).limit(1))).empty) return {
7475
+ deleted: false,
7476
+ reason: "not_empty"
7477
+ };
7478
+ transaction.delete(reference);
7479
+ transaction.delete(idReference);
7480
+ return { deleted: true };
7481
+ });
7482
+ },
7483
+ transaction: (callback) => mutate(callback)
7484
+ };
7485
+ })());
7486
+ return (0, _hot_updater_plugin_core.createDatabasePlugin)({
7487
+ name: "firebaseDatabase",
7488
+ models: adapter.models,
7489
+ commit: adapter.commit
7490
+ });
7491
+ };
7492
+ //#endregion
6715
7493
  //#region iac/init/index.ts
6716
7494
  const isFirebaseRegion = (value) => value !== void 0 && /^[a-z]+(?:-[a-z]+)+[0-9]+$/.test(value);
6717
7495
  const isFirebaseProjectId = (value) => value !== void 0 && /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value);
@@ -6765,6 +7543,53 @@ const inputFirebaseApplicationCredentials = async ({ applicationCredentials, non
6765
7543
  return credentialsPath || void 0;
6766
7544
  };
6767
7545
  //#endregion
7546
+ //#region iac/firebaseInfrastructureState.ts
7547
+ const resolveFirebaseInfrastructureState = ({ adapterVersion, hasData }) => {
7548
+ if (adapterVersion === 4) return "v1";
7549
+ if (adapterVersion !== void 0 || hasData) return "incompatible";
7550
+ return "fresh";
7551
+ };
7552
+ const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredentials, projectId }) => {
7553
+ const app = (0, firebase_admin_app.initializeApp)({
7554
+ credential: applicationCredentials ? (0, firebase_admin_app.cert)(JSON.parse(await fs_promises.default.readFile(applicationCredentials, "utf8"))) : (0, firebase_admin_app.applicationDefault)(),
7555
+ projectId
7556
+ }, `hot-updater-init-${projectId}-${Date.now()}`);
7557
+ try {
7558
+ const db = (0, firebase_admin_firestore.getFirestore)(app);
7559
+ const version = await db.collection(FIREBASE_V1_COLLECTION_NAMES.settings).doc("database_adapter_version").get();
7560
+ const collections = await Promise.all([
7561
+ FIREBASE_V1_COLLECTION_NAMES.bundles,
7562
+ FIREBASE_V1_COLLECTION_NAMES.bundlePatches,
7563
+ FIREBASE_V1_COLLECTION_NAMES.channels,
7564
+ FIREBASE_V1_COLLECTION_NAMES.releaseCatalogs
7565
+ ].map((name) => db.collection(name).limit(1).get()));
7566
+ if (resolveFirebaseInfrastructureState({
7567
+ adapterVersion: version.exists ? version.data()?.version : void 0,
7568
+ hasData: collections.some((snapshot) => !snapshot.empty)
7569
+ }) === "incompatible") throw new _hot_updater_cli_tools.InitError(`Firebase v1 infrastructure in project ${projectId} is incomplete or uses an unsupported database version.`);
7570
+ } catch (error) {
7571
+ if (error instanceof _hot_updater_cli_tools.InitError) throw error;
7572
+ const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : void 0;
7573
+ if (code === 5 || code === "not-found") return;
7574
+ throw error;
7575
+ } finally {
7576
+ await (0, firebase_admin_app.deleteApp)(app);
7577
+ }
7578
+ };
7579
+ const assertFirebaseFunctionCanInitialize = async ({ fetchImpl, functions }) => {
7580
+ const existingFunctions = functions.filter(({ id }) => id === FIREBASE_V1_FUNCTION_NAME);
7581
+ for (const existingFunction of existingFunctions) {
7582
+ if (!existingFunction.uri) throw new _hot_updater_cli_tools.InitError(`Could not verify the Firebase infrastructure generation at Function ${FIREBASE_V1_FUNCTION_NAME}: endpoint URL was not reported.`);
7583
+ const versionUrl = new URL("version", `${existingFunction.uri.replace(/\/$/u, "")}/`).toString();
7584
+ await (0, _hot_updater_cli_tools.assertInfrastructureGenerationAtUrl)({
7585
+ fetchImpl,
7586
+ provider: "Firebase",
7587
+ resource: `Function ${FIREBASE_V1_FUNCTION_NAME}`,
7588
+ versionUrl
7589
+ });
7590
+ }
7591
+ };
7592
+ //#endregion
6768
7593
  //#region iac/firebaseInitInputs.ts
6769
7594
  const resolveFirebaseCredentialsPath = (credentialsPath, { cwd = process.cwd(), homeDir = node_os.default.homedir(), pathApi = node_path.default }) => {
6770
7595
  const homeRelativePath = credentialsPath === "~" ? "" : credentialsPath.startsWith("~/") || credentialsPath.startsWith("~\\") ? credentialsPath.slice(2) : void 0;
@@ -6894,7 +7719,7 @@ const resolveFirebaseRegion = async ({ cwd, discoverExistingProject = true, nonI
6894
7719
  if (functionsList.exitCode === 0) {
6895
7720
  const parsed = JSON.parse(functionsList.stdout);
6896
7721
  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") {
7722
+ for (const entry of parsed.result) if (typeof entry === "object" && entry !== null && "id" in entry && entry.id === "hot-updater-v1" && "region" in entry && typeof entry.region === "string") {
6898
7723
  discoveredRegion = entry.region;
6899
7724
  break;
6900
7725
  }
@@ -7216,10 +8041,16 @@ function App() {
7216
8041
  return null; // Replace with your app root
7217
8042
  }
7218
8043
 
7219
- export default HotUpdater.wrap({
8044
+ HotUpdater.init({
7220
8045
  baseURL: "%%source%%",
7221
- updateStrategy: "appVersion", // or "fingerprint"
7222
- })(App);`;
8046
+ requestHeaders: {
8047
+ "x-api-key": %%apiKey%%,
8048
+ },
8049
+ });
8050
+
8051
+ // Call HotUpdater.checkForUpdate({ updateStrategy: "appVersion" })
8052
+ // when your app is ready to check.
8053
+ export default App;`;
7223
8054
  const getFirebaseRuntimePackageInfo = () => {
7224
8055
  const firebasePackageRoot = path.default.dirname(require.resolve("@hot-updater/firebase/package.json"));
7225
8056
  return {
@@ -7240,6 +8071,71 @@ const syncFunctionsPackageJson = async (functionsDir) => {
7240
8071
  await fs.default.promises.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
7241
8072
  return runtimePackageInfo;
7242
8073
  };
8074
+ const commandErrorMessage = (error) => {
8075
+ if (error instanceof ExecaError) {
8076
+ const output = error.stderr || error.stdout;
8077
+ if (output !== void 0 && output !== null) {
8078
+ const text = String(output).trim();
8079
+ if (!text) return error.message;
8080
+ try {
8081
+ const parsed = JSON.parse(text);
8082
+ if (typeof parsed === "object" && parsed !== null) {
8083
+ const message = Reflect.get(parsed, "error");
8084
+ if (typeof message === "string") return message;
8085
+ }
8086
+ } catch {
8087
+ return text;
8088
+ }
8089
+ return text;
8090
+ }
8091
+ }
8092
+ return error instanceof Error ? error.message : String(error);
8093
+ };
8094
+ const ensureCloudFunctionsApiEnabled = async (projectId, cliEnv) => {
8095
+ await _hot_updater_cli_tools.p.tasks([{
8096
+ title: "Checking Cloud Functions API",
8097
+ task: async () => {
8098
+ try {
8099
+ await execa("gcloud", [
8100
+ "services",
8101
+ "enable",
8102
+ "cloudfunctions.googleapis.com",
8103
+ `--project=${projectId}`,
8104
+ "--quiet"
8105
+ ], { env: cliEnv });
8106
+ } catch (error) {
8107
+ throw new _hot_updater_cli_tools.InitError(`Could not enable the Cloud Functions API for Firebase project ${projectId}: ${commandErrorMessage(error)}`);
8108
+ }
8109
+ return "Cloud Functions API is enabled";
8110
+ }
8111
+ }]);
8112
+ };
8113
+ const listFirebaseFunctions = async (cwd, projectId, nonInteractive, cliEnv) => {
8114
+ let functionsList;
8115
+ try {
8116
+ functionsList = await execa("npx", [
8117
+ "firebase",
8118
+ "functions:list",
8119
+ "--json",
8120
+ "--project",
8121
+ projectId,
8122
+ ...nonInteractive ? ["--non-interactive"] : []
8123
+ ], {
8124
+ cwd,
8125
+ env: cliEnv
8126
+ });
8127
+ } catch (error) {
8128
+ throw new _hot_updater_cli_tools.InitError(`Could not list Firebase Functions for project ${projectId}: ${commandErrorMessage(error)}. Run npx firebase functions:list --project ${projectId} --debug for details.`);
8129
+ }
8130
+ let body;
8131
+ try {
8132
+ body = JSON.parse(functionsList.stdout);
8133
+ } catch {
8134
+ throw new _hot_updater_cli_tools.InitError("Firebase functions list response was invalid.");
8135
+ }
8136
+ if (typeof body !== "object" || body === null || !Array.isArray(Reflect.get(body, "result"))) throw new _hot_updater_cli_tools.InitError("Firebase functions list response was invalid.");
8137
+ return Reflect.get(body, "result");
8138
+ };
7243
8139
  function normalizeIndex(index) {
7244
8140
  return {
7245
8141
  collectionGroup: index.collectionGroup,
@@ -7304,7 +8200,7 @@ const deployFunctions = async (cwd, nonInteractive = false, cliEnv) => {
7304
8200
  "firebase",
7305
8201
  "deploy",
7306
8202
  "--only",
7307
- "functions",
8203
+ `functions:${FIREBASE_V1_FUNCTION_NAME}`,
7308
8204
  ...nonInteractive ? ["--non-interactive"] : []
7309
8205
  ], {
7310
8206
  cwd,
@@ -7317,21 +8213,26 @@ const deployFunctions = async (cwd, nonInteractive = false, cliEnv) => {
7317
8213
  process.exit(1);
7318
8214
  }
7319
8215
  };
7320
- const printTemplate = async (projectId, region, cliEnv) => {
8216
+ const printTemplate = async (apiKey, projectId, region, cliEnv) => {
7321
8217
  try {
7322
- const { stdout } = await execa("gcloud", [
8218
+ const describedFunction = await execa("gcloud", [
7323
8219
  "functions",
7324
8220
  "describe",
7325
- "hot-updater",
8221
+ FIREBASE_V1_FUNCTION_NAME,
7326
8222
  "--project",
7327
8223
  projectId,
7328
8224
  "--region",
7329
8225
  region,
7330
8226
  "--format=json"
7331
8227
  ], { env: cliEnv });
7332
- const parsedData = JSON.parse(stdout);
7333
- const functionUrl = `${parsedData?.serviceConfig?.uri ?? parsedData.url}/api/check-update`;
7334
- _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, { source: functionUrl }));
8228
+ const functionUrl = JSON.parse(describedFunction.stdout).serviceConfig?.uri;
8229
+ if (!functionUrl) throw new _hot_updater_cli_tools.InitError(`Firebase Function ${FIREBASE_V1_FUNCTION_NAME} did not report an endpoint URL.`);
8230
+ _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, {
8231
+ apiKey: JSON.stringify(apiKey),
8232
+ source: functionUrl
8233
+ }));
8234
+ _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.formatApiKeyNote)(apiKey), "API Key");
8235
+ _hot_updater_cli_tools.p.log.message("Store this API key separately in a secure place.");
7335
8236
  } catch (error) {
7336
8237
  if (error instanceof ExecaError) _hot_updater_cli_tools.p.log.error(error.stderr || error.stdout || error.message);
7337
8238
  else if (error instanceof Error) _hot_updater_cli_tools.p.log.error(error.message);
@@ -7350,7 +8251,8 @@ const runInit = async ({ build, envFile }) => {
7350
8251
  const nonInteractive = envFile !== void 0;
7351
8252
  const initEnvSources = await (0, _hot_updater_cli_tools.readHotUpdaterInitEnv)(process.cwd(), envFile);
7352
8253
  const { managedEnv } = initEnvSources;
7353
- const savedInputs = resolveFirebaseInitInputs((0, _hot_updater_cli_tools.getHotUpdaterInitInputEnv)(initEnvSources, nonInteractive));
8254
+ const initInputEnv = (0, _hot_updater_cli_tools.getHotUpdaterInitInputEnv)(initEnvSources, nonInteractive);
8255
+ const savedInputs = resolveFirebaseInitInputs(initInputEnv);
7354
8256
  assertFirebaseNonInteractiveInputs(savedInputs, nonInteractive);
7355
8257
  let applicationCredentials = savedInputs.applicationCredentials;
7356
8258
  const cliEnv = nonInteractive ? getFirebaseCliEnv(applicationCredentials) : void 0;
@@ -7371,6 +8273,14 @@ const runInit = async ({ build, envFile }) => {
7371
8273
  });
7372
8274
  return cliEnv;
7373
8275
  });
8276
+ if (initializeVariable.status === "ready") {
8277
+ await assertFirebaseInfrastructureCanInitialize({
8278
+ applicationCredentials,
8279
+ projectId: initializeVariable.projectId
8280
+ });
8281
+ await ensureCloudFunctionsApiEnabled(initializeVariable.projectId, cliEnv);
8282
+ await assertFirebaseFunctionCanInitialize({ functions: await listFirebaseFunctions(tmpDir, initializeVariable.projectId, nonInteractive, cliEnv) });
8283
+ }
7374
8284
  const currentRegion = await resolveFirebaseRegion({
7375
8285
  cwd: tmpDir,
7376
8286
  discoverExistingProject: initializeVariable.status === "ready",
@@ -7427,22 +8337,31 @@ const runInit = async ({ build, envFile }) => {
7427
8337
  }
7428
8338
  }]);
7429
8339
  await deployFirestore(tmpDir, nonInteractive, cliEnv);
8340
+ const credential = applicationCredentials ? (0, firebase_admin_app.cert)(JSON.parse(await fs.default.promises.readFile(applicationCredentials, "utf-8"))) : (0, firebase_admin_app.applicationDefault)();
8341
+ const existingApps = new Set((0, firebase_admin_app.getApps)());
8342
+ const databasePlugin = firebaseDatabase({
8343
+ credential,
8344
+ projectId: initializeVariable.projectId
8345
+ });
8346
+ let apiKey;
8347
+ try {
8348
+ apiKey = (await (0, _hot_updater_server.provisionApiKey)({
8349
+ apiKeys: databasePlugin.models.apiKeys,
8350
+ existingApiKey: initInputEnv.HOT_UPDATER_API_KEY,
8351
+ name: "Firebase init"
8352
+ })).apiKey;
8353
+ await (0, _hot_updater_cli_tools.makeEnv)({ HOT_UPDATER_API_KEY: apiKey });
8354
+ } finally {
8355
+ await databasePlugin.dispose?.();
8356
+ await Promise.all((0, firebase_admin_app.getApps)().filter((app) => !existingApps.has(app)).map((app) => (0, firebase_admin_app.deleteApp)(app)));
8357
+ }
7430
8358
  await deployFunctions(tmpDir, nonInteractive, cliEnv);
7431
8359
  await _hot_updater_cli_tools.p.tasks([{
7432
8360
  title: "Check IAM policy",
7433
8361
  async task(message) {
7434
- const functionsList = await execa("npx", [
7435
- "firebase",
7436
- "functions:list",
7437
- "--json",
7438
- ...nonInteractive ? ["--non-interactive"] : []
7439
- ], {
7440
- cwd: tmpDir,
7441
- env: cliEnv
7442
- });
7443
- const account = (JSON.parse(functionsList.stdout).result || []).find((fn) => fn.id === "hot-updater")?.serviceAccount;
8362
+ const account = (await listFirebaseFunctions(tmpDir, initializeVariable.projectId, nonInteractive, cliEnv)).find((fn) => fn.id === FIREBASE_V1_FUNCTION_NAME)?.serviceAccount;
7444
8363
  if (!account) {
7445
- _hot_updater_cli_tools.p.log.error("hot-updater function not found");
8364
+ _hot_updater_cli_tools.p.log.error(`${FIREBASE_V1_FUNCTION_NAME} function not found`);
7446
8365
  await removeTmpDir();
7447
8366
  process.exit(1);
7448
8367
  }
@@ -7479,7 +8398,7 @@ const runInit = async ({ build, envFile }) => {
7479
8398
  await removeTmpDir();
7480
8399
  process.exit(1);
7481
8400
  }
7482
- await printTemplate(initializeVariable.projectId, currentRegion, cliEnv);
8401
+ await printTemplate(apiKey, initializeVariable.projectId, currentRegion, cliEnv);
7483
8402
  await removeTmpDir();
7484
8403
  _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")}`);
7485
8404
  if (!applicationCredentials) _hot_updater_cli_tools.p.log.message("Next step: Change GOOGLE_APPLICATION_CREDENTIALS=your-credentials.json in .env.hotupdater");