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