@hot-updater/firebase 0.29.4 → 0.29.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,16 @@
1
1
  {
2
2
  "indexes": [
3
+ {
4
+ "collectionGroup": "bundles",
5
+ "queryScope": "COLLECTION",
6
+ "fields": [
7
+ { "fieldPath": "channel", "order": "ASCENDING" },
8
+ { "fieldPath": "enabled", "order": "ASCENDING" },
9
+ { "fieldPath": "platform", "order": "ASCENDING" },
10
+ { "fieldPath": "target_app_version", "order": "ASCENDING" },
11
+ { "fieldPath": "id", "order": "ASCENDING" }
12
+ ]
13
+ },
3
14
  {
4
15
  "collectionGroup": "bundles",
5
16
  "queryScope": "COLLECTION",
@@ -36,6 +47,17 @@
36
47
  { "fieldPath": "id", "order": "DESCENDING" }
37
48
  ]
38
49
  },
50
+ {
51
+ "collectionGroup": "bundles",
52
+ "queryScope": "COLLECTION",
53
+ "fields": [
54
+ { "fieldPath": "channel", "order": "ASCENDING" },
55
+ { "fieldPath": "enabled", "order": "ASCENDING" },
56
+ { "fieldPath": "platform", "order": "ASCENDING" },
57
+ { "fieldPath": "fingerprint_hash", "order": "ASCENDING" },
58
+ { "fieldPath": "id", "order": "ASCENDING" }
59
+ ]
60
+ },
39
61
  {
40
62
  "collectionGroup": "bundles",
41
63
  "queryScope": "COLLECTION",
@@ -1034,7 +1034,7 @@ const handleCommand = (filePath, rawArguments, rawOptions) => {
1034
1034
  var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1035
1035
  module.exports = isexe;
1036
1036
  isexe.sync = sync;
1037
- var fs$6 = require("fs");
1037
+ var fs$5 = require("fs");
1038
1038
  function checkPathExt(path, options) {
1039
1039
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
1040
1040
  if (!pathext) return true;
@@ -1051,12 +1051,12 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1051
1051
  return checkPathExt(path, options);
1052
1052
  }
1053
1053
  function isexe(path, options, cb) {
1054
- fs$6.stat(path, function(er, stat) {
1054
+ fs$5.stat(path, function(er, stat) {
1055
1055
  cb(er, er ? false : checkStat(stat, path, options));
1056
1056
  });
1057
1057
  }
1058
1058
  function sync(path, options) {
1059
- return checkStat(fs$6.statSync(path), path, options);
1059
+ return checkStat(fs$5.statSync(path), path, options);
1060
1060
  }
1061
1061
  }));
1062
1062
  //#endregion
@@ -1064,14 +1064,14 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1064
1064
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1065
1065
  module.exports = isexe;
1066
1066
  isexe.sync = sync;
1067
- var fs$5 = require("fs");
1067
+ var fs$4 = require("fs");
1068
1068
  function isexe(path, options, cb) {
1069
- fs$5.stat(path, function(er, stat) {
1069
+ fs$4.stat(path, function(er, stat) {
1070
1070
  cb(er, er ? false : checkStat(stat, options));
1071
1071
  });
1072
1072
  }
1073
1073
  function sync(path, options) {
1074
- return checkStat(fs$5.statSync(path), options);
1074
+ return checkStat(fs$4.statSync(path), options);
1075
1075
  }
1076
1076
  function checkStat(stat, options) {
1077
1077
  return stat.isFile() && checkMode(stat, options);
@@ -1286,16 +1286,16 @@ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) =
1286
1286
  //#endregion
1287
1287
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
1288
1288
  var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1289
- const fs$4 = require("fs");
1289
+ const fs$3 = require("fs");
1290
1290
  const shebangCommand = require_shebang_command();
1291
1291
  function readShebang(command) {
1292
1292
  const size = 150;
1293
1293
  const buffer = Buffer.alloc(size);
1294
1294
  let fd;
1295
1295
  try {
1296
- fd = fs$4.openSync(command, "r");
1297
- fs$4.readSync(fd, buffer, 0, size, 0);
1298
- fs$4.closeSync(fd);
1296
+ fd = fs$3.openSync(command, "r");
1297
+ fs$3.readSync(fd, buffer, 0, size, 0);
1298
+ fs$3.closeSync(fd);
1299
1299
  } catch (e) {}
1300
1300
  return shebangCommand(buffer.toString());
1301
1301
  }
@@ -6458,7 +6458,7 @@ const prepareFirebaseTemplate = async (firebaseRootDir) => {
6458
6458
  };
6459
6459
  //#endregion
6460
6460
  //#region iac/select.ts
6461
- const getConfigTemplate = (build) => {
6461
+ const getConfigScaffold = (build) => {
6462
6462
  const storageConfig = {
6463
6463
  imports: [{
6464
6464
  pkg: "@hot-updater/firebase",
@@ -6480,16 +6480,20 @@ const getConfigTemplate = (build) => {
6480
6480
  credential,
6481
6481
  })`
6482
6482
  };
6483
- const intermediate = `
6483
+ const helperStatements = [{
6484
+ name: "credential",
6485
+ strategy: "preserve-existing",
6486
+ code: `
6484
6487
  // https://firebase.google.com/docs/admin/setup?hl=en#initialize_the_sdk_in_non-google_environments
6485
6488
  // Check your .env file and add the credentials
6486
6489
  // Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to your credentials file path
6487
6490
  // Example: GOOGLE_APPLICATION_CREDENTIALS=./firebase-adminsdk-credentials.json
6488
- const credential = admin.credential.applicationDefault();`.trim();
6489
- return new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).addImport({
6491
+ const credential = admin.credential.applicationDefault();`.trim()
6492
+ }];
6493
+ return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).addImport({
6490
6494
  pkg: "firebase-admin",
6491
6495
  defaultOrNamespace: "admin"
6492
- }).setIntermediateCode(intermediate).getResult();
6496
+ }).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n")), { helperStatements });
6493
6497
  };
6494
6498
  const setEnv = async ({ projectId, storageBucket, build }) => {
6495
6499
  await (0, _hot_updater_cli_tools.makeEnv)({
@@ -6499,11 +6503,13 @@ const setEnv = async ({ projectId, storageBucket, build }) => {
6499
6503
  },
6500
6504
  HOT_UPDATER_FIREBASE_PROJECT_ID: projectId,
6501
6505
  HOT_UPDATER_FIREBASE_STORAGE_BUCKET: storageBucket
6502
- });
6506
+ }, ".env.hotupdater", { preserveKeys: ["GOOGLE_APPLICATION_CREDENTIALS"] });
6503
6507
  _hot_updater_cli_tools.p.log.success("Firebase credentials have been successfully configured.");
6504
6508
  try {
6505
- await fs.default.promises.writeFile("hot-updater.config.ts", getConfigTemplate(build));
6506
- _hot_updater_cli_tools.p.log.success("Configuration file 'hot-updater.config.ts' has been created.");
6509
+ const configWriteResult = await (0, _hot_updater_cli_tools.writeHotUpdaterConfig)(getConfigScaffold(build));
6510
+ if (configWriteResult.status === "created") _hot_updater_cli_tools.p.log.success("Configuration file 'hot-updater.config.ts' has been created.");
6511
+ else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Configuration file 'hot-updater.config.ts' has been updated.");
6512
+ else _hot_updater_cli_tools.p.log.warn(`Existing 'hot-updater.config.ts' was left unchanged: ${configWriteResult.reason}`);
6507
6513
  } catch (error) {
6508
6514
  console.error("Error writing configuration file:", error.message);
6509
6515
  }
@@ -1,7 +1,7 @@
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, copyDirToTmp, link, makeEnv, p, resolveHotUpdaterServerVersion, resolvePackageVersion, transformEnv, transformTemplate } from "@hot-updater/cli-tools";
4
+ import { ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, link, makeEnv, p, resolveHotUpdaterServerVersion, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterConfig } from "@hot-updater/cli-tools";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
7
7
  import { StringDecoder } from "node:string_decoder";
@@ -6453,7 +6453,7 @@ const prepareFirebaseTemplate = async (firebaseRootDir) => {
6453
6453
  };
6454
6454
  //#endregion
6455
6455
  //#region iac/select.ts
6456
- const getConfigTemplate = (build) => {
6456
+ const getConfigScaffold = (build) => {
6457
6457
  const storageConfig = {
6458
6458
  imports: [{
6459
6459
  pkg: "@hot-updater/firebase",
@@ -6475,16 +6475,20 @@ const getConfigTemplate = (build) => {
6475
6475
  credential,
6476
6476
  })`
6477
6477
  };
6478
- const intermediate = `
6478
+ const helperStatements = [{
6479
+ name: "credential",
6480
+ strategy: "preserve-existing",
6481
+ code: `
6479
6482
  // https://firebase.google.com/docs/admin/setup?hl=en#initialize_the_sdk_in_non-google_environments
6480
6483
  // Check your .env file and add the credentials
6481
6484
  // Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to your credentials file path
6482
6485
  // Example: GOOGLE_APPLICATION_CREDENTIALS=./firebase-adminsdk-credentials.json
6483
- const credential = admin.credential.applicationDefault();`.trim();
6484
- return new ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).addImport({
6486
+ const credential = admin.credential.applicationDefault();`.trim()
6487
+ }];
6488
+ return createHotUpdaterConfigScaffoldFromBuilder(new ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).addImport({
6485
6489
  pkg: "firebase-admin",
6486
6490
  defaultOrNamespace: "admin"
6487
- }).setIntermediateCode(intermediate).getResult();
6491
+ }).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n")), { helperStatements });
6488
6492
  };
6489
6493
  const setEnv = async ({ projectId, storageBucket, build }) => {
6490
6494
  await makeEnv({
@@ -6494,11 +6498,13 @@ const setEnv = async ({ projectId, storageBucket, build }) => {
6494
6498
  },
6495
6499
  HOT_UPDATER_FIREBASE_PROJECT_ID: projectId,
6496
6500
  HOT_UPDATER_FIREBASE_STORAGE_BUCKET: storageBucket
6497
- });
6501
+ }, ".env.hotupdater", { preserveKeys: ["GOOGLE_APPLICATION_CREDENTIALS"] });
6498
6502
  p.log.success("Firebase credentials have been successfully configured.");
6499
6503
  try {
6500
- await fs.promises.writeFile("hot-updater.config.ts", getConfigTemplate(build));
6501
- p.log.success("Configuration file 'hot-updater.config.ts' has been created.");
6504
+ const configWriteResult = await writeHotUpdaterConfig(getConfigScaffold(build));
6505
+ if (configWriteResult.status === "created") p.log.success("Configuration file 'hot-updater.config.ts' has been created.");
6506
+ else if (configWriteResult.status === "merged") p.log.success("Configuration file 'hot-updater.config.ts' has been updated.");
6507
+ else p.log.warn(`Existing 'hot-updater.config.ts' was left unchanged: ${configWriteResult.reason}`);
6502
6508
  } catch (error) {
6503
6509
  console.error("Error writing configuration file:", error.message);
6504
6510
  }
package/dist/index.cjs CHANGED
@@ -71,6 +71,11 @@ const applyFirestoreQueryableFilters = (query, where) => {
71
71
  const requiresInMemoryFiltering = (where) => {
72
72
  return Boolean(where?.id?.in || where?.targetAppVersionIn || where?.targetAppVersionNotNull || where?.targetAppVersion === null || where?.fingerprintHash === null);
73
73
  };
74
+ const chunkValues = (values, size) => {
75
+ const chunks = [];
76
+ for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
77
+ return chunks;
78
+ };
74
79
  const convertToBundle = (firestoreData) => ({
75
80
  channel: firestoreData.channel,
76
81
  enabled: Boolean(firestoreData.enabled),
@@ -98,14 +103,28 @@ const firebaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
98
103
  }
99
104
  const db = firebase_admin.default.firestore(app);
100
105
  const bundlesCollection = db.collection("bundles");
106
+ const targetAppVersionsCollection = db.collection("target_app_versions");
101
107
  return {
108
+ getUpdateInfo: (0, _hot_updater_plugin_core.createDatabasePluginGetUpdateInfo)({
109
+ async listTargetAppVersions({ platform, channel }) {
110
+ const querySnapshot = await targetAppVersionsCollection.where("platform", "==", platform).where("channel", "==", channel).select("target_app_version").get();
111
+ return Array.from(new Set(querySnapshot.docs.map((doc) => doc.data().target_app_version).filter((version) => Boolean(version))));
112
+ },
113
+ async getBundlesByTargetAppVersions({ platform, channel, minBundleId }, targetAppVersions) {
114
+ return (await Promise.all(chunkValues(targetAppVersions, 10).map((versions) => bundlesCollection.where("platform", "==", platform).where("channel", "==", channel).where("enabled", "==", true).where("id", ">=", minBundleId).where("target_app_version", "in", versions).get()))).flatMap((snapshot) => snapshot.docs.map((doc) => convertToBundle(doc.data())));
115
+ },
116
+ async getBundlesByFingerprint({ platform, channel, minBundleId, fingerprintHash }) {
117
+ return (await bundlesCollection.where("platform", "==", platform).where("channel", "==", channel).where("enabled", "==", true).where("id", ">=", minBundleId).where("fingerprint_hash", "==", fingerprintHash).get()).docs.map((doc) => convertToBundle(doc.data()));
118
+ }
119
+ }),
102
120
  async getBundleById(bundleId) {
103
121
  const bundleSnap = await bundlesCollection.doc(bundleId).get();
104
122
  if (!bundleSnap.exists) return null;
105
123
  return convertToBundle(bundleSnap.data());
106
124
  },
107
125
  async getBundles(options) {
108
- const { where, limit, offset, orderBy } = options;
126
+ const { where, limit, orderBy } = options;
127
+ const offset = ("offset" in options ? options.offset : void 0) ?? 0;
109
128
  let query = applyFirestoreQueryableFilters(bundlesCollection, where);
110
129
  query = query.orderBy("id", orderBy?.direction === "asc" ? "asc" : "desc");
111
130
  if (requiresInMemoryFiltering(where)) {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_ROLLOUT_COHORT_COUNT } from "@hot-updater/core";
2
- import { calculatePagination, createDatabasePlugin, createStorageKeyBuilder, createStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
2
+ import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
3
3
  import admin from "firebase-admin";
4
4
  import fs from "fs/promises";
5
5
  import path from "path";
@@ -45,6 +45,11 @@ const applyFirestoreQueryableFilters = (query, where) => {
45
45
  const requiresInMemoryFiltering = (where) => {
46
46
  return Boolean(where?.id?.in || where?.targetAppVersionIn || where?.targetAppVersionNotNull || where?.targetAppVersion === null || where?.fingerprintHash === null);
47
47
  };
48
+ const chunkValues = (values, size) => {
49
+ const chunks = [];
50
+ for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
51
+ return chunks;
52
+ };
48
53
  const convertToBundle = (firestoreData) => ({
49
54
  channel: firestoreData.channel,
50
55
  enabled: Boolean(firestoreData.enabled),
@@ -72,14 +77,28 @@ const firebaseDatabase = createDatabasePlugin({
72
77
  }
73
78
  const db = admin.firestore(app);
74
79
  const bundlesCollection = db.collection("bundles");
80
+ const targetAppVersionsCollection = db.collection("target_app_versions");
75
81
  return {
82
+ getUpdateInfo: createDatabasePluginGetUpdateInfo({
83
+ async listTargetAppVersions({ platform, channel }) {
84
+ const querySnapshot = await targetAppVersionsCollection.where("platform", "==", platform).where("channel", "==", channel).select("target_app_version").get();
85
+ return Array.from(new Set(querySnapshot.docs.map((doc) => doc.data().target_app_version).filter((version) => Boolean(version))));
86
+ },
87
+ async getBundlesByTargetAppVersions({ platform, channel, minBundleId }, targetAppVersions) {
88
+ return (await Promise.all(chunkValues(targetAppVersions, 10).map((versions) => bundlesCollection.where("platform", "==", platform).where("channel", "==", channel).where("enabled", "==", true).where("id", ">=", minBundleId).where("target_app_version", "in", versions).get()))).flatMap((snapshot) => snapshot.docs.map((doc) => convertToBundle(doc.data())));
89
+ },
90
+ async getBundlesByFingerprint({ platform, channel, minBundleId, fingerprintHash }) {
91
+ return (await bundlesCollection.where("platform", "==", platform).where("channel", "==", channel).where("enabled", "==", true).where("id", ">=", minBundleId).where("fingerprint_hash", "==", fingerprintHash).get()).docs.map((doc) => convertToBundle(doc.data()));
92
+ }
93
+ }),
76
94
  async getBundleById(bundleId) {
77
95
  const bundleSnap = await bundlesCollection.doc(bundleId).get();
78
96
  if (!bundleSnap.exists) return null;
79
97
  return convertToBundle(bundleSnap.data());
80
98
  },
81
99
  async getBundles(options) {
82
- const { where, limit, offset, orderBy } = options;
100
+ const { where, limit, orderBy } = options;
101
+ const offset = ("offset" in options ? options.offset : void 0) ?? 0;
83
102
  let query = applyFirestoreQueryableFilters(bundlesCollection, where);
84
103
  query = query.orderBy("id", orderBy?.direction === "asc" ? "asc" : "desc");
85
104
  if (requiresInMemoryFiltering(where)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/firebase",
3
3
  "type": "module",
4
- "version": "0.29.4",
4
+ "version": "0.29.6",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -36,10 +36,10 @@
36
36
  "dependencies": {
37
37
  "firebase": "^11.3.1",
38
38
  "hono": "4.12.9",
39
- "@hot-updater/cli-tools": "0.29.4",
40
- "@hot-updater/plugin-core": "0.29.4",
41
- "@hot-updater/server": "0.29.4",
42
- "@hot-updater/core": "0.29.4"
39
+ "@hot-updater/cli-tools": "0.29.6",
40
+ "@hot-updater/plugin-core": "0.29.6",
41
+ "@hot-updater/server": "0.29.6",
42
+ "@hot-updater/core": "0.29.6"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
@@ -54,9 +54,9 @@
54
54
  "firebase-tools": "^13.32.0",
55
55
  "fkill": "^9.0.0",
56
56
  "mime": "^4.0.4",
57
- "@hot-updater/js": "0.29.4",
58
- "@hot-updater/mock": "0.29.4",
59
- "@hot-updater/test-utils": "0.29.4"
57
+ "@hot-updater/js": "0.29.6",
58
+ "@hot-updater/mock": "0.29.6",
59
+ "@hot-updater/test-utils": "0.29.6"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "firebase-admin": "*",