firebase-tools 15.17.0 → 15.19.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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createLocalBuildTarArchive = createLocalBuildTarArchive;
4
4
  exports.createSourceDeployArchive = createSourceDeployArchive;
5
+ exports.resolveIgnorePatterns = resolveIgnorePatterns;
5
6
  const archiver = require("archiver");
6
7
  const fs = require("fs");
7
8
  const path = require("path");
@@ -9,25 +10,28 @@ const tar = require("tar");
9
10
  const tmp = require("tmp");
10
11
  const error_1 = require("../../error");
11
12
  const fsAsync = require("../../fsAsync");
12
- const config_1 = require("../../apphosting/config");
13
- async function createLocalBuildTarArchive(config, rootDir, targetSubDir) {
13
+ const utils_1 = require("../../utils");
14
+ async function createLocalBuildTarArchive(config, rootDir, outputFiles) {
14
15
  const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
15
- const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
16
- const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
17
- const rdrFiles = await fsAsync.readdirRecursive({
18
- path: targetDir,
19
- ignore: ignore,
20
- isGitIgnore: true,
21
- });
22
- const allFiles = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
23
- if (targetSubDir) {
24
- const defaultFiles = fs.readdirSync(rootDir).filter((file) => {
25
- return config_1.APPHOSTING_YAML_FILE_REGEX.test(file);
26
- });
27
- for (const file of defaultFiles) {
28
- if (!allFiles.includes(file)) {
29
- allFiles.push(file);
30
- }
16
+ const filesToPackage = outputFiles.length > 0 ? outputFiles : ["."];
17
+ const allFiles = [];
18
+ for (const fileOrDir of filesToPackage) {
19
+ const absolutePath = path.join(rootDir, fileOrDir);
20
+ if (!fs.existsSync(absolutePath)) {
21
+ (0, utils_1.logLabeledWarning)("apphosting", `Expected build output file or directory not found: ${fileOrDir}`);
22
+ continue;
23
+ }
24
+ const stat = fs.statSync(absolutePath);
25
+ if (stat.isDirectory()) {
26
+ const rdrFiles = await fsAsync.readdirRecursive({
27
+ path: absolutePath,
28
+ ignoreStrings: ["firebase-debug.log", "firebase-debug.*.log"],
29
+ supportGitIgnore: false,
30
+ });
31
+ allFiles.push(...rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)));
32
+ }
33
+ else {
34
+ allFiles.push(path.relative(rootDir, absolutePath));
31
35
  }
32
36
  }
33
37
  try {
@@ -58,15 +62,12 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
58
62
  });
59
63
  const archive = archiver("zip");
60
64
  const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
61
- const ignore = config.ignore || ["node_modules", ".git"];
62
- ignore.push("firebase-debug.log", "firebase-debug.*.log");
63
- const gitIgnorePatterns = parseGitIgnorePatterns(targetDir);
64
- ignore.push(...gitIgnorePatterns);
65
+ const ignore = resolveIgnorePatterns(config);
65
66
  try {
66
67
  const files = await fsAsync.readdirRecursive({
67
68
  path: targetDir,
68
- ignore: ignore,
69
- isGitIgnore: true,
69
+ ignoreStrings: ignore,
70
+ supportGitIgnore: true,
70
71
  });
71
72
  for (const file of files) {
72
73
  const name = path.relative(rootDir, file.name);
@@ -78,22 +79,19 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
78
79
  await pipeAsync(archive, fileStream);
79
80
  }
80
81
  catch (err) {
81
- throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${err}`, { original: err, exit: 1 });
82
+ throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${String(err)}`, { original: err, exit: 1 });
82
83
  }
83
84
  return tmpFile;
84
85
  }
85
- function parseGitIgnorePatterns(projectRoot, gitIgnorePath = ".gitignore") {
86
- const absoluteFilePath = path.resolve(projectRoot, gitIgnorePath);
87
- if (!fs.existsSync(absoluteFilePath)) {
88
- return [];
89
- }
90
- const lines = fs
91
- .readFileSync(absoluteFilePath)
92
- .toString()
93
- .split("\n")
94
- .map((line) => line.trim())
95
- .filter((line) => !line.startsWith("#") && !(line === ""));
96
- return lines;
86
+ function resolveIgnorePatterns(config, skipDefaultNodeModules = false) {
87
+ const ignore = config.ignore
88
+ ? [...config.ignore]
89
+ : skipDefaultNodeModules
90
+ ? [".git"]
91
+ : ["node_modules", ".git"];
92
+ ignore.push("firebase-debug.log", "firebase-debug.*.log");
93
+ ignore.push(".local_build_*");
94
+ return ignore;
97
95
  }
98
96
  async function pipeAsync(from, to) {
99
97
  from.pipe(to);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
3
+ exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_TIMEOUT_SECONDS = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
4
4
  exports.endpointTriggerType = endpointTriggerType;
5
5
  exports.isValidMemoryOption = isValidMemoryOption;
6
6
  exports.isValidEgressSetting = isValidEgressSetting;
@@ -116,6 +116,7 @@ function memoryToGen2Cpu(memory) {
116
116
  }
117
117
  exports.DEFAULT_CONCURRENCY = 80;
118
118
  exports.DEFAULT_MEMORY = 256;
119
+ exports.DEFAULT_TIMEOUT_SECONDS = 60;
119
120
  exports.MIN_CPU_FOR_CONCURRENCY = 1;
120
121
  exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
121
122
  function secretVersionName(s) {
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_FUNCTION_REGION = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  exports.prepare = prepare;
5
- exports.matchRegionsForExisting = matchRegionsForExisting;
6
- exports.resolveDefaultRegions = resolveDefaultRegions;
5
+ exports.resolveDefaultRegionsForBuild = resolveDefaultRegionsForBuild;
7
6
  exports.inferDetailsFromExisting = inferDetailsFromExisting;
8
7
  exports.updateEndpointTargetedStatus = updateEndpointTargetedStatus;
9
8
  exports.inferBlockingDetails = inferBlockingDetails;
10
9
  exports.resolveCpuAndConcurrency = resolveCpuAndConcurrency;
10
+ exports.resolveDefaultTimeout = resolveDefaultTimeout;
11
11
  exports.loadCodebases = loadCodebases;
12
12
  exports.warnIfNewGenkitFunctionIsMissingSecrets = warnIfNewGenkitFunctionIsMissingSecrets;
13
13
  exports.ensureAllRequiredAPIsEnabled = ensureAllRequiredAPIsEnabled;
@@ -78,6 +78,7 @@ async function prepare(context, options, payload) {
78
78
  }
79
79
  context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
80
80
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
81
+ const existingBackend = await backend.existingBackend(context);
81
82
  if (Object.values(wantBuilds).some((b) => b.extensions)) {
82
83
  const extContext = {};
83
84
  const extPayload = {};
@@ -99,6 +100,10 @@ async function prepare(context, options, payload) {
99
100
  proto.convertIfPresent(userEnvOpt, localCfg, "configDir", (cd) => options.config.path(cd));
100
101
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
101
102
  const envs = { ...userEnvs, ...firebaseEnvs };
103
+ const relevantEndpoints = backend
104
+ .allEndpoints(existingBackend)
105
+ .filter((e) => e.codebase === codebase || e.codebase === undefined);
106
+ await resolveDefaultRegionsForBuild(wantBuild, backend.of(...relevantEndpoints));
102
107
  const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
103
108
  build: wantBuild,
104
109
  firebaseConfig,
@@ -194,13 +199,6 @@ async function prepare(context, options, payload) {
194
199
  context.sources[codebase] = source;
195
200
  }
196
201
  payload.functions = {};
197
- const existingBackend = await backend.existingBackend(context);
198
- for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
199
- const relevantEndpoints = backend
200
- .allEndpoints(existingBackend)
201
- .filter((e) => e.codebase === codebase || e.codebase === undefined);
202
- await resolveDefaultRegions(wantBackend, backend.of(...relevantEndpoints));
203
- }
204
202
  const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(existingBackend));
205
203
  for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
206
204
  const haveBackend = haveBackends[codebase] || backend.empty();
@@ -210,6 +208,7 @@ async function prepare(context, options, payload) {
210
208
  inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
211
209
  await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
212
210
  resolveCpuAndConcurrency(wantBackend);
211
+ resolveDefaultTimeout(wantBackend);
213
212
  validate.endpointsAreValid(wantBackend);
214
213
  inferBlockingDetails(wantBackend);
215
214
  }
@@ -232,66 +231,50 @@ async function prepare(context, options, payload) {
232
231
  validate.checkFiltersIntegrity(wantBackends, context.filters);
233
232
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
234
233
  }
235
- function moveEndpointToRegion(backend, endpoint, region) {
236
- endpoint.region = region;
237
- backend.endpoints[region] = backend.endpoints[region] || {};
238
- backend.endpoints[region][endpoint.id] = endpoint;
239
- delete backend.endpoints[build.REGION_TBD][endpoint.id];
240
- if (Object.keys(backend.endpoints[build.REGION_TBD]).length === 0) {
241
- delete backend.endpoints[build.REGION_TBD];
242
- }
243
- }
244
- function matchRegionsForExisting(want, have) {
245
- for (const [id, wantE] of Object.entries(want.endpoints[build.REGION_TBD] || {})) {
246
- let matching;
247
- for (const region of Object.keys(have.endpoints)) {
248
- if (region === build.REGION_TBD) {
249
- continue;
250
- }
251
- if (have.endpoints[region][id]) {
252
- if (matching) {
253
- throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
234
+ async function resolveDefaultRegionsForBuild(buildObj, have) {
235
+ for (const [id, endpoint] of Object.entries(buildObj.endpoints)) {
236
+ if (!endpoint.region?.length || endpoint.region.includes(build.REGION_TBD)) {
237
+ let resolvedRegion = exports.DEFAULT_FUNCTION_REGION;
238
+ let matching;
239
+ for (const region of Object.keys(have.endpoints)) {
240
+ if (have.endpoints[region][id]) {
241
+ if (matching) {
242
+ throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
243
+ }
244
+ matching = have.endpoints[region][id];
254
245
  }
255
- matching = have.endpoints[region][id];
256
246
  }
257
- }
258
- if (!matching) {
259
- continue;
260
- }
261
- moveEndpointToRegion(want, wantE, matching.region);
262
- }
263
- }
264
- async function resolveDefaultRegions(want, have) {
265
- matchRegionsForExisting(want, have);
266
- const endpoints = Object.values(want.endpoints[build.REGION_TBD] || {});
267
- for (const endpoint of endpoints) {
268
- let resolvedRegion = "us-central1";
269
- try {
270
- if (backend.isBlockingTriggered(endpoint)) {
271
- resolvedRegion = resolveRegionForBlockingTrigger(endpoint);
247
+ if (matching) {
248
+ resolvedRegion = matching.region;
272
249
  }
273
- else if (backend.isEventTriggered(endpoint)) {
274
- resolvedRegion = await resolveRegionForEventTrigger(endpoint);
250
+ else {
251
+ try {
252
+ if (build.isBlockingTriggered(endpoint)) {
253
+ resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
254
+ }
255
+ else if (build.isEventTriggered(endpoint)) {
256
+ resolvedRegion = await resolveRegionForEventTrigger(endpoint.project, endpoint.eventTrigger);
257
+ }
258
+ }
259
+ catch (err) {
260
+ logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${exports.DEFAULT_FUNCTION_REGION}.`, (0, error_1.getErrStack)(err));
261
+ }
275
262
  }
263
+ endpoint.region = [resolvedRegion];
276
264
  }
277
- catch (err) {
278
- logger_1.logger.debug(`Failed to resolve region for endpoint ${endpoint.id}. Defaulting to us-central1.`, (0, error_1.getErrStack)(err));
279
- }
280
- moveEndpointToRegion(want, endpoint, resolvedRegion);
281
265
  }
282
266
  }
283
- function resolveRegionForBlockingTrigger(endpoint) {
284
- const eventType = endpoint.blockingTrigger.eventType;
267
+ function resolveRegionForBlockingTrigger(blockingTrigger) {
268
+ const eventType = blockingTrigger.eventType;
285
269
  if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
286
270
  return "us-east1";
287
271
  }
288
- if ((0, ailogic_1.isGlobalAILogicEndpoint)(endpoint)) {
272
+ if ((0, ailogic_1.isGlobalAILogicTrigger)(blockingTrigger)) {
289
273
  return "us-east1";
290
274
  }
291
275
  return exports.DEFAULT_FUNCTION_REGION;
292
276
  }
293
- async function resolveRegionForEventTrigger(endpoint) {
294
- const eventTrigger = endpoint.eventTrigger;
277
+ async function resolveRegionForEventTrigger(project, eventTrigger) {
295
278
  const eventType = eventTrigger.eventType;
296
279
  if (eventType.startsWith("google.cloud.pubsub.") ||
297
280
  eventType.startsWith("providers/cloud.auth/eventTypes/") ||
@@ -304,7 +287,7 @@ async function resolveRegionForEventTrigger(endpoint) {
304
287
  if (eventType.startsWith("google.cloud.firestore.")) {
305
288
  try {
306
289
  const databaseId = eventTrigger.eventFilters?.database || "(default)";
307
- const db = await (0, firestore_1.getDatabase)(endpoint.project, databaseId);
290
+ const db = await (0, firestore_1.getDatabase)(project, databaseId);
308
291
  const locationId = db.locationId.toLowerCase();
309
292
  if (locationId === "nam5" || locationId === "nam7")
310
293
  return "us-central1";
@@ -341,7 +324,7 @@ async function resolveRegionForEventTrigger(endpoint) {
341
324
  try {
342
325
  const instanceName = eventTrigger.eventFilters?.instance;
343
326
  if (instanceName) {
344
- const details = await (0, database_1.getDatabaseInstanceDetails)(endpoint.project, instanceName);
327
+ const details = await (0, database_1.getDatabaseInstanceDetails)(project, instanceName);
345
328
  if (details.location && details.location !== "-") {
346
329
  return details.location.toLowerCase();
347
330
  }
@@ -389,6 +372,9 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
389
372
  if (typeof wantE.cpu === "undefined" && haveE.cpu) {
390
373
  wantE.cpu = haveE.cpu;
391
374
  }
375
+ if (typeof wantE.timeoutSeconds === "undefined" && haveE.timeoutSeconds) {
376
+ wantE.timeoutSeconds = haveE.timeoutSeconds;
377
+ }
392
378
  wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
393
379
  maybeCopyTriggerRegion(wantE, haveE);
394
380
  }
@@ -454,6 +440,13 @@ function resolveCpuAndConcurrency(want) {
454
440
  }
455
441
  }
456
442
  }
443
+ function resolveDefaultTimeout(want) {
444
+ for (const e of backend.allEndpoints(want)) {
445
+ if (e.platform === "run" && e.timeoutSeconds === undefined) {
446
+ e.timeoutSeconds = backend.DEFAULT_TIMEOUT_SECONDS;
447
+ }
448
+ }
449
+ }
457
450
  async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
458
451
  const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
459
452
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -78,7 +78,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
78
78
  const ignore = config.ignore || ["node_modules", ".git"];
79
79
  ignore.push("firebase-debug.log", "firebase-debug.*.log", CONFIG_DEST_FILE);
80
80
  try {
81
- const files = await fsAsync.readdirRecursive({ path: sourceDir, ignore: ignore });
81
+ const files = await fsAsync.readdirRecursive({ path: sourceDir, ignoreStrings: ignore });
82
82
  hashes.push(...(await addFilesToArchive(archive, files, sourceDir, options?.executablePaths)));
83
83
  for (const name of additionalSources) {
84
84
  const absPath = utils.resolveWithin(projectDir, name);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERATE_CONTENT = exports.AI_LOGIC_BEFORE_GENERATE_CONTENT = void 0;
4
4
  exports.isAILogicEvent = isAILogicEvent;
5
- exports.isGlobalAILogicEndpoint = isGlobalAILogicEndpoint;
5
+ exports.isGlobalAILogicTrigger = isGlobalAILogicTrigger;
6
6
  const backend = require("../backend");
7
7
  const error_1 = require("../../../error");
8
8
  const ailogicApi = require("../../../gcp/ailogic");
@@ -20,11 +20,9 @@ function isAILogicEvent(endpoint) {
20
20
  }
21
21
  return exports.AI_LOGIC_EVENTS.includes(endpoint.blockingTrigger.eventType);
22
22
  }
23
- function isGlobalAILogicEndpoint(endpoint) {
24
- if (!isAILogicEvent(endpoint)) {
25
- return false;
26
- }
27
- return !endpoint.blockingTrigger.options?.regionalWebhook;
23
+ function isGlobalAILogicTrigger(blockingTrigger) {
24
+ return (exports.AI_LOGIC_EVENTS.includes(blockingTrigger.eventType) &&
25
+ !blockingTrigger.options?.regionalWebhook);
28
26
  }
29
27
  class AILogicService {
30
28
  constructor() {
@@ -32,6 +30,16 @@ class AILogicService {
32
30
  this.name = "ailogic";
33
31
  this.api = "firebasevertexai.googleapis.com";
34
32
  }
33
+ async requiredProjectBindings(projectNumber) {
34
+ return [
35
+ {
36
+ role: "roles/run.invoker",
37
+ members: [
38
+ `serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
39
+ ],
40
+ },
41
+ ];
42
+ }
35
43
  validateTrigger(endpoint, wantBackend) {
36
44
  if (!isAILogicEvent(endpoint)) {
37
45
  return;
@@ -221,7 +221,7 @@ async function secretsAreValid(projectId, wantBackend) {
221
221
  validatePlatformTargets(endpoints);
222
222
  await validateSecretVersions(projectId, endpoints);
223
223
  }
224
- const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
224
+ const secretsSupportedPlatforms = backend.AllFunctionsPlatforms;
225
225
  function validatePlatformTargets(endpoints) {
226
226
  const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
227
227
  if (unsupported.length > 0) {
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.downloadToTmp = downloadToTmp;
4
+ exports.validateSize = validateSize;
5
+ exports.validateChecksum = validateChecksum;
4
6
  const url_1 = require("url");
7
+ const crypto = require("crypto");
5
8
  const fs = require("fs-extra");
6
9
  const ProgressBar = require("progress");
7
10
  const tmp = require("tmp");
@@ -37,3 +40,24 @@ async function downloadToTmp(remoteUrl, auth = false) {
37
40
  });
38
41
  return tmpfile.name;
39
42
  }
43
+ function validateSize(filepath, expectedSize) {
44
+ return new Promise((resolve, reject) => {
45
+ const stat = fs.statSync(filepath);
46
+ return stat.size === expectedSize
47
+ ? resolve()
48
+ : reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
49
+ });
50
+ }
51
+ function validateChecksum(filepath, expectedChecksum, algorithm = "md5") {
52
+ return new Promise((resolve, reject) => {
53
+ const hash = crypto.createHash(algorithm);
54
+ const stream = fs.createReadStream(filepath);
55
+ stream.on("data", (data) => hash.update(data));
56
+ stream.on("end", () => {
57
+ const checksum = hash.digest("hex");
58
+ return checksum === expectedChecksum
59
+ ? resolve()
60
+ : reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
61
+ });
62
+ });
63
+ }
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.downloadEmulator = downloadEmulator;
4
4
  exports.downloadExtensionVersion = downloadExtensionVersion;
5
- const crypto = require("crypto");
6
5
  const fs = require("fs-extra");
7
6
  const path = require("path");
8
7
  const tmp = require("tmp");
@@ -36,8 +35,8 @@ async function downloadEmulator(name) {
36
35
  throw err;
37
36
  }
38
37
  if (!emulator.opts.skipChecksumAndSize) {
39
- await validateSize(tmpfile, emulator.opts.expectedSize);
40
- await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
38
+ await downloadUtils.validateSize(tmpfile, emulator.opts.expectedSize);
39
+ await downloadUtils.validateChecksum(tmpfile, emulator.opts.expectedChecksum, "md5");
41
40
  }
42
41
  if (emulator.opts.skipCache) {
43
42
  removeOldFiles(name, emulator, true);
@@ -82,24 +81,3 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
82
81
  }
83
82
  }
84
83
  }
85
- function validateSize(filepath, expectedSize) {
86
- return new Promise((resolve, reject) => {
87
- const stat = fs.statSync(filepath);
88
- return stat.size === expectedSize
89
- ? resolve()
90
- : reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
91
- });
92
- }
93
- function validateChecksum(filepath, expectedChecksum) {
94
- return new Promise((resolve, reject) => {
95
- const hash = crypto.createHash("md5");
96
- const stream = fs.createReadStream(filepath);
97
- stream.on("data", (data) => hash.update(data));
98
- stream.on("end", () => {
99
- const checksum = hash.digest("hex");
100
- return checksum === expectedChecksum
101
- ? resolve()
102
- : reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
103
- });
104
- });
105
- }
@@ -44,46 +44,46 @@
44
44
  }
45
45
  },
46
46
  "pubsub": {
47
- "version": "0.8.30",
48
- "expectedSize": 52917173,
49
- "expectedChecksum": "f7935320d11894d0c4cd26dd064ee42f",
50
- "expectedChecksumSHA256": "aabf028a55ec3f06fe2223471dad26623be542848abebd7563f8f0b8697dd2a3",
51
- "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.30.zip",
52
- "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.30.zip",
53
- "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.30/pubsub-emulator/bin/cloud-pubsub-emulator"
47
+ "version": "0.8.32",
48
+ "expectedSize": 52942305,
49
+ "expectedChecksum": "b47a3698985bff5f6aedec04fad9354e",
50
+ "expectedChecksumSHA256": "476d6492718210837f13e64b5b9f54335469381827bad25eda62e36572e47a82",
51
+ "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.32.zip",
52
+ "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.32.zip",
53
+ "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.32/pubsub-emulator/bin/cloud-pubsub-emulator"
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "3.4.7",
58
- "expectedSize": 32361408,
59
- "expectedChecksum": "b756c557573420a54da92e9d66934355",
60
- "expectedChecksumSHA256": "d97bbc39e6c9960d233db975c3f2e66d43c2a34436f8e39fae3cbe2a815e2c6b",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.7",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
57
+ "version": "3.4.9",
58
+ "expectedSize": 32423488,
59
+ "expectedChecksum": "75788cfdb69c2762ac9f78e37714d364",
60
+ "expectedChecksumSHA256": "af26c22a6fe131b111b4e71eb4c179b2b42a9cf8d334d50e4c2c419bbbb54416",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.9",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.9"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "3.4.7",
66
- "expectedSize": 30504610,
67
- "expectedChecksum": "f3015d7cfd03bd4fcb7d76d6aac29c84",
68
- "expectedChecksumSHA256": "3b0d4d273dabd04dc01ef57001180ed6daae2f1781d25f1f01eb6da5d25d5ae5",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.7",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
65
+ "version": "3.4.9",
66
+ "expectedSize": 30554802,
67
+ "expectedChecksum": "7e64aba6ffc071ef60b8f2a51b3f5683",
68
+ "expectedChecksumSHA256": "49ba258954b3568e93ef75b4c7e9a50c708a7e15ba9a22697ff967e195a907aa",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.9",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.9"
71
71
  },
72
72
  "win32": {
73
- "version": "3.4.7",
74
- "expectedSize": 32403456,
75
- "expectedChecksum": "7fac83eee96f4f638d84a9c3a6950040",
76
- "expectedChecksumSHA256": "42a88d0377565a94b966336d45b45a8ec24c6a81b0206654327d7722107569de",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.7",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7.exe"
73
+ "version": "3.4.9",
74
+ "expectedSize": 32464896,
75
+ "expectedChecksum": "1bc40de5be6bab9280904ee68f849594",
76
+ "expectedChecksumSHA256": "b0b277ed1efd374099b90a74ebb2994ffa5755c185d8d9d2cf667f49c0c31139",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.9",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.9.exe"
79
79
  },
80
80
  "linux": {
81
- "version": "3.4.7",
82
- "expectedSize": 31518904,
83
- "expectedChecksum": "2aa615b65f12a9efd768e13ab92bf457",
84
- "expectedChecksumSHA256": "7ce36de105950172ee6f41100df93c92ba2cd2b2f89cb157740fc1ae808fcebb",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.7",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.7"
81
+ "version": "3.4.9",
82
+ "expectedSize": 31580344,
83
+ "expectedChecksum": "4de2d7d37652d652d059335a43dc02b4",
84
+ "expectedChecksumSHA256": "803ec59b5ec1590ed4521df70864a46808f3ec8008756206bf210852e2298291",
85
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.9",
86
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.9"
87
87
  }
88
88
  }
89
89
  }
package/lib/fsAsync.js CHANGED
@@ -6,12 +6,37 @@ const ignore_1 = require("ignore");
6
6
  const _ = require("lodash");
7
7
  const minimatch = require("minimatch");
8
8
  const path_1 = require("path");
9
+ const logger_1 = require("./logger");
9
10
  async function readdirRecursiveHelper(options) {
10
11
  const dirContents = (0, fs_extra_1.readdirSync)(options.path, { withFileTypes: true });
12
+ let currentGitIgnoreStack = options.gitIgnoreStack || [];
13
+ if (options.supportGitIgnore) {
14
+ if (dirContents.find((n) => n.name === ".gitignore")?.isFile()) {
15
+ const localGitIgnore = (0, path_1.join)(options.path, ".gitignore");
16
+ try {
17
+ const lines = (0, fs_extra_1.readFileSync)(localGitIgnore)
18
+ .toString()
19
+ .split("\n")
20
+ .map((line) => line.trim())
21
+ .filter((line) => !line.startsWith("#") && line !== "");
22
+ const subIgnore = (0, ignore_1.default)().add(lines);
23
+ currentGitIgnoreStack = [
24
+ ...currentGitIgnoreStack,
25
+ {
26
+ dirPath: options.path,
27
+ ignore: subIgnore,
28
+ },
29
+ ];
30
+ }
31
+ catch (e) {
32
+ logger_1.logger.debug(`Error reading .gitignore file at ${localGitIgnore}:`, e);
33
+ }
34
+ }
35
+ }
11
36
  const fullPaths = dirContents
12
37
  .filter((n) => !options.ignoreSymlinks || !n.isSymbolicLink())
13
38
  .map((n) => (0, path_1.join)(options.path, n.name));
14
- const filteredPaths = fullPaths.filter((p) => !options.filter(p));
39
+ const filteredPaths = fullPaths.filter((p) => !options.filter(p, currentGitIgnoreStack));
15
40
  const filePromises = [];
16
41
  for (const p of filteredPaths) {
17
42
  const fstat = (0, fs_extra_1.statSync)(p);
@@ -27,6 +52,8 @@ async function readdirRecursiveHelper(options) {
27
52
  filter: options.filter,
28
53
  maxDepth: options.maxDepth - 1,
29
54
  ignoreSymlinks: options.ignoreSymlinks,
55
+ supportGitIgnore: options.supportGitIgnore,
56
+ gitIgnoreStack: currentGitIgnoreStack,
30
57
  }));
31
58
  }
32
59
  }
@@ -37,25 +64,41 @@ async function readdirRecursiveHelper(options) {
37
64
  }
38
65
  async function readdirRecursive(options) {
39
66
  const mmopts = { matchBase: true, dot: true };
40
- const rules = (options.ignore || []).map((glob) => {
67
+ const ignoreRules = (options.ignoreStrings || []).map((glob) => {
41
68
  return (p) => minimatch(p, glob, mmopts);
42
69
  });
43
- const gitIgnoreRules = (0, ignore_1.default)()
44
- .add(options.ignore || [])
45
- .createFilter();
46
- const filter = (t) => {
47
- if (options.isGitIgnore) {
48
- return !gitIgnoreRules((0, path_1.relative)(options.path, t));
70
+ const filter = (targetPath, gitIgnoreStack = []) => {
71
+ if (options.supportGitIgnore) {
72
+ let ignored = false;
73
+ for (let i = gitIgnoreStack.length - 1; i >= 0; i--) {
74
+ const state = gitIgnoreStack[i];
75
+ const relPath = (0, path_1.relative)(state.dirPath, targetPath);
76
+ const result = state.ignore.test(relPath);
77
+ if (result.ignored || result.unignored) {
78
+ ignored = result.ignored;
79
+ break;
80
+ }
81
+ }
82
+ return ignored;
49
83
  }
50
- return rules.some((rule) => {
51
- return rule(t);
84
+ return ignoreRules.some((rule) => {
85
+ return rule(targetPath);
52
86
  });
53
87
  };
88
+ const initialGitIgnoreStack = [];
89
+ if (options.supportGitIgnore) {
90
+ initialGitIgnoreStack.push({
91
+ dirPath: options.path,
92
+ ignore: (0, ignore_1.default)().add(options.ignoreStrings || []),
93
+ });
94
+ }
54
95
  const maxDepth = options.maxDepth && options.maxDepth > 0 ? options.maxDepth : Infinity;
55
96
  return await readdirRecursiveHelper({
56
97
  path: options.path,
57
98
  filter: filter,
58
99
  maxDepth,
59
100
  ignoreSymlinks: !!options.ignoreSymlinks,
101
+ supportGitIgnore: options.supportGitIgnore,
102
+ gitIgnoreStack: initialGitIgnoreStack,
60
103
  });
61
104
  }