@snorreks/firestack 0.0.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.
Files changed (48) hide show
  1. package/README.md +134 -0
  2. package/index.d.ts +8 -0
  3. package/index.d.ts.map +1 -0
  4. package/index.js +106 -0
  5. package/lib/constants/builders.d.ts +2 -0
  6. package/lib/constants/builders.d.ts.map +1 -0
  7. package/lib/constants/directories.d.ts +2 -0
  8. package/lib/constants/directories.d.ts.map +1 -0
  9. package/lib/constants/function-types.d.ts +8 -0
  10. package/lib/constants/function-types.d.ts.map +1 -0
  11. package/lib/constants/index.d.ts +4 -0
  12. package/lib/constants/index.d.ts.map +1 -0
  13. package/lib/helpers/auth.d.ts +31 -0
  14. package/lib/helpers/auth.d.ts.map +1 -0
  15. package/lib/helpers/database.d.ts +40 -0
  16. package/lib/helpers/database.d.ts.map +1 -0
  17. package/lib/helpers/firestore.d.ts +26 -0
  18. package/lib/helpers/firestore.d.ts.map +1 -0
  19. package/lib/helpers/https.d.ts +29 -0
  20. package/lib/helpers/https.d.ts.map +1 -0
  21. package/lib/helpers/scheduler.d.ts +4 -0
  22. package/lib/helpers/scheduler.d.ts.map +1 -0
  23. package/lib/helpers/storage.d.ts +50 -0
  24. package/lib/helpers/storage.d.ts.map +1 -0
  25. package/lib/types/build.d.ts +56 -0
  26. package/lib/types/build.d.ts.map +1 -0
  27. package/lib/types/checksum.d.ts +10 -0
  28. package/lib/types/checksum.d.ts.map +1 -0
  29. package/lib/types/core.d.ts +45 -0
  30. package/lib/types/core.d.ts.map +1 -0
  31. package/lib/types/deploy.d.ts +166 -0
  32. package/lib/types/deploy.d.ts.map +1 -0
  33. package/lib/types/firebase-auth-v2.d.ts +30 -0
  34. package/lib/types/firebase-auth-v2.d.ts.map +1 -0
  35. package/lib/types/function-types.d.ts +11 -0
  36. package/lib/types/function-types.d.ts.map +1 -0
  37. package/lib/types/functions-cache.d.ts +31 -0
  38. package/lib/types/functions-cache.d.ts.map +1 -0
  39. package/lib/types/helper-generics.d.ts +20 -0
  40. package/lib/types/helper-generics.d.ts.map +1 -0
  41. package/lib/types/helper-options.d.ts +81 -0
  42. package/lib/types/helper-options.d.ts.map +1 -0
  43. package/lib/types/index.d.ts +10 -0
  44. package/lib/types/index.d.ts.map +1 -0
  45. package/lib/types/script.d.ts +6 -0
  46. package/lib/types/script.d.ts.map +1 -0
  47. package/main.js +1661 -0
  48. package/package.json +28 -0
package/main.js ADDED
@@ -0,0 +1,1661 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import { Command as Command9 } from "commander";
5
+
6
+ // src/lib/commands/build.ts
7
+ import { readdir, readFile, writeFile as writeFile2 } from "node:fs/promises";
8
+ import { basename, join } from "node:path";
9
+ import { cwd, exit } from "node:process";
10
+ import chalk2 from "chalk";
11
+ import { Command } from "commander";
12
+
13
+ // src/lib/utils/build_utils.ts
14
+ import { mkdir } from "node:fs/promises";
15
+ import { dirname } from "node:path";
16
+ import { build } from "esbuild";
17
+
18
+ // src/lib/utils/logger.ts
19
+ import chalk from "chalk";
20
+ var LogSeverityPriority = /* @__PURE__ */ ((LogSeverityPriority2) => {
21
+ LogSeverityPriority2[LogSeverityPriority2["silent"] = 0] = "silent";
22
+ LogSeverityPriority2[LogSeverityPriority2["debug"] = 1] = "debug";
23
+ LogSeverityPriority2[LogSeverityPriority2["info"] = 2] = "info";
24
+ LogSeverityPriority2[LogSeverityPriority2["warn"] = 4] = "warn";
25
+ LogSeverityPriority2[LogSeverityPriority2["error"] = 5] = "error";
26
+ return LogSeverityPriority2;
27
+ })(LogSeverityPriority || {});
28
+ var LoggerService = class {
29
+ currentLogSeverity = "info";
30
+ setLogSeverity(options) {
31
+ if (options.silent) {
32
+ this.currentLogSeverity = "silent";
33
+ return;
34
+ }
35
+ if (options.verbose) {
36
+ this.currentLogSeverity = "debug";
37
+ return;
38
+ }
39
+ }
40
+ write(entry, ...data) {
41
+ if (!this.currentLogSeverity) {
42
+ return;
43
+ }
44
+ const { logType, message, severity } = entry;
45
+ const currentLogSeverityPriority = this.toLogSeverityPriority(this.currentLogSeverity);
46
+ const entryLogSeverityPriority = this.toLogSeverityPriority(severity || "info");
47
+ if (currentLogSeverityPriority > entryLogSeverityPriority) {
48
+ return;
49
+ }
50
+ const log = console[logType || "log"];
51
+ if (typeof message !== "undefined") {
52
+ log(message, ...data);
53
+ } else {
54
+ log(...data);
55
+ }
56
+ }
57
+ debug(...args) {
58
+ this.write(
59
+ {
60
+ logType: "debug",
61
+ severity: "debug"
62
+ },
63
+ ...args
64
+ );
65
+ }
66
+ info(...args) {
67
+ this.write(
68
+ {
69
+ logType: "info",
70
+ severity: "info"
71
+ },
72
+ ...args
73
+ );
74
+ }
75
+ warn(...args) {
76
+ this.write(
77
+ {
78
+ logType: "warn",
79
+ severity: "warn"
80
+ },
81
+ chalk.yellow(args.join(" "))
82
+ );
83
+ }
84
+ error(...args) {
85
+ this.write(
86
+ {
87
+ logType: "error",
88
+ severity: "error"
89
+ },
90
+ chalk.red(args.join(" "))
91
+ );
92
+ }
93
+ log(...args) {
94
+ this.write(
95
+ {
96
+ logType: "log",
97
+ severity: "info"
98
+ },
99
+ ...args
100
+ );
101
+ }
102
+ toLogSeverityPriority(severity) {
103
+ return LogSeverityPriority[severity];
104
+ }
105
+ };
106
+ var LoggerFactory = class _LoggerFactory {
107
+ static logger = new LoggerService();
108
+ static getLogger() {
109
+ return _LoggerFactory.logger;
110
+ }
111
+ };
112
+ var logger = LoggerFactory.getLogger();
113
+
114
+ // src/lib/utils/build_utils.ts
115
+ async function buildFunction(options) {
116
+ const { inputFile, outputFile, minify, sourcemap } = options;
117
+ const outDir = dirname(outputFile);
118
+ await mkdir(outDir, { recursive: true });
119
+ const banner = {
120
+ js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`
121
+ };
122
+ try {
123
+ await build({
124
+ entryPoints: [inputFile],
125
+ outfile: outputFile,
126
+ bundle: true,
127
+ format: "esm",
128
+ platform: "node",
129
+ // external: ['firebase-functions', 'firebase-admin'],
130
+ minify,
131
+ sourcemap,
132
+ banner,
133
+ loader: {
134
+ ".ts": "ts",
135
+ ".tsx": "tsx"
136
+ }
137
+ });
138
+ } catch (error) {
139
+ logger.error("buildFunction", {
140
+ error,
141
+ options
142
+ });
143
+ throw error;
144
+ }
145
+ }
146
+
147
+ // src/lib/commands/build.ts
148
+ function cwdDir() {
149
+ return cwd();
150
+ }
151
+ function exitCode(code) {
152
+ return exit(code);
153
+ }
154
+ async function readDir(path) {
155
+ const entries = await readdir(path, { withFileTypes: true });
156
+ return entries.map((entry) => ({
157
+ name: entry.name,
158
+ isDirectory: () => entry.isDirectory(),
159
+ isFile: () => entry.isFile()
160
+ }));
161
+ }
162
+ var buildCommand = new Command("build").description("Builds all functions using esbuild.").option("-o, --output-directory <outputDirectory>", "The output directory.", "dist").option("-c, --config <config>", "Path to the package.json config file.").option(
163
+ "--functionsDirectory <functionsDirectory>",
164
+ "The directory where the functions are located."
165
+ ).action(async (options) => {
166
+ console.log(chalk2.bold.green("Starting build with esbuild..."));
167
+ console.log("Options:", options);
168
+ const functionsDirectory = options.functionsDirectory || "src/controllers";
169
+ const functionsPath = join(cwdDir(), functionsDirectory);
170
+ const functionFiles = await findFunctions(functionsPath);
171
+ if (functionFiles.length === 0) {
172
+ console.log(chalk2.yellow("No functions found to build."));
173
+ return;
174
+ }
175
+ console.log(chalk2.bold.cyan(`Found ${functionFiles.length} functions to build.`));
176
+ for (const funcPath of functionFiles) {
177
+ const functionName = basename(funcPath).replace(/\.(ts|tsx|js)$/, "");
178
+ const functionOutputDir = join(cwdDir(), options.outputDirectory, functionName);
179
+ const outputFile = join(functionOutputDir, "src", "index.js");
180
+ const configPath = options.config ? join(cwdDir(), options.config) : void 0;
181
+ console.log(`Building function: ${chalk2.bold.yellow(functionName)}`);
182
+ console.log(`Output file: ${outputFile}`);
183
+ if (configPath) {
184
+ console.log(`Using config file: ${configPath}`);
185
+ }
186
+ try {
187
+ await buildFunction({
188
+ inputFile: funcPath,
189
+ outputFile,
190
+ configPath
191
+ });
192
+ console.log(chalk2.green(`Successfully built ${functionName}.`));
193
+ } catch (error) {
194
+ console.error(chalk2.red(`Failed to build ${functionName}:`));
195
+ console.error(error);
196
+ exitCode(1);
197
+ }
198
+ }
199
+ console.log(chalk2.bold.green("Build complete!"));
200
+ });
201
+ async function findFunctions(dir) {
202
+ const functions2 = [];
203
+ try {
204
+ const entries = await readDir(dir);
205
+ for (const entry of entries) {
206
+ const path = join(dir, entry.name);
207
+ if (entry.isDirectory()) {
208
+ functions2.push(...await findFunctions(path));
209
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
210
+ const name = entry.name;
211
+ if (name.endsWith(".test.ts") || name.endsWith(".spec.ts") || name.endsWith(".test.js") || name.endsWith(".spec.js") || name.endsWith("_test.ts") || name.endsWith("_test.js")) {
212
+ continue;
213
+ }
214
+ functions2.push(path);
215
+ }
216
+ }
217
+ } catch {
218
+ }
219
+ return functions2;
220
+ }
221
+
222
+ // src/lib/commands/delete.ts
223
+ import { Command as Command3 } from "commander";
224
+
225
+ // src/lib/node-shim.ts
226
+ import { spawn } from "node:child_process";
227
+ import { existsSync, mkdirSync, watch } from "node:fs";
228
+ import { mkdir as mkdirProm, readdir as readdir2, readFile as readFile2, rm, writeFile as writeFile3 } from "node:fs/promises";
229
+ import { dirname as dirname2 } from "node:path";
230
+ import { cwd as cwd2, exit as exit2 } from "node:process";
231
+ async function readTextFile(path) {
232
+ return readFile2(path, "utf-8");
233
+ }
234
+ async function writeTextFile(path, contents) {
235
+ const dir = dirname2(path);
236
+ if (!existsSync(dir)) {
237
+ mkdirSync(dir, { recursive: true });
238
+ }
239
+ await writeFile3(path, contents, "utf-8");
240
+ }
241
+ async function mkdir3(path, options) {
242
+ await mkdirProm(path, { recursive: options?.recursive ?? false });
243
+ }
244
+ function cwdDir2() {
245
+ return cwd2();
246
+ }
247
+ function exitCode2(code) {
248
+ return exit2(code);
249
+ }
250
+ async function readDir2(path) {
251
+ const entries = await readdir2(path, { withFileTypes: true });
252
+ return entries.map((entry) => ({
253
+ name: entry.name,
254
+ isDirectory: () => entry.isDirectory(),
255
+ isFile: () => entry.isFile()
256
+ }));
257
+ }
258
+ async function remove(path, options) {
259
+ await rm(path, { recursive: options?.recursive ?? false, force: true });
260
+ }
261
+ var Command2 = class {
262
+ cmd;
263
+ args = [];
264
+ opts = {};
265
+ constructor(cmd, options) {
266
+ this.cmd = cmd;
267
+ if (options?.args) this.args = options.args;
268
+ if (options?.cwd) this.opts.cwd = options.cwd;
269
+ if (options?.env) this.opts.env = options.env;
270
+ if (options?.stdout) this.opts.stdout = options.stdout;
271
+ if (options?.stderr) this.opts.stderr = options.stderr;
272
+ }
273
+ async output() {
274
+ return new Promise((resolve) => {
275
+ const child = spawn(this.cmd, this.args, {
276
+ cwd: this.opts.cwd,
277
+ env: { ...process.env, ...this.opts.env }
278
+ });
279
+ let stdout = Buffer.alloc(0);
280
+ let stderr = Buffer.alloc(0);
281
+ if (child.stdout) {
282
+ child.stdout.on("data", (data) => {
283
+ stdout = Buffer.concat([stdout, Buffer.from(data)]);
284
+ });
285
+ }
286
+ if (child.stderr) {
287
+ child.stderr.on("data", (data) => {
288
+ stderr = Buffer.concat([stderr, Buffer.from(data)]);
289
+ });
290
+ }
291
+ child.on("close", (code) => {
292
+ resolve({ code: code ?? 0, stdout, stderr });
293
+ });
294
+ });
295
+ }
296
+ spawn() {
297
+ const child = spawn(this.cmd, this.args, {
298
+ cwd: this.opts.cwd,
299
+ env: { ...process.env, ...this.opts.env },
300
+ stdio: ["ignore", this.opts.stdout ?? "inherit", this.opts.stderr ?? "inherit"]
301
+ });
302
+ return {
303
+ status: new Promise((resolve) => {
304
+ child.on("close", (code) => {
305
+ resolve({ code: code ?? 0, success: code === 0 });
306
+ });
307
+ })
308
+ };
309
+ }
310
+ };
311
+
312
+ // src/lib/commands/delete/utils/delete_functions.ts
313
+ import { execa } from "execa";
314
+ async function deleteFunctions(options, functionNames) {
315
+ try {
316
+ await executeFirebaseFunctionsDelete(options, functionNames);
317
+ } catch (error) {
318
+ logger.error("deleteFunctions", error);
319
+ throw error;
320
+ }
321
+ }
322
+ async function executeFirebaseFunctionsDelete(options, functionNames) {
323
+ logger.info(`Deleting functions: ${functionNames.join(", ")}...`);
324
+ try {
325
+ await execa(
326
+ "firebase",
327
+ ["functions:delete", ...functionNames, "--project", options.projectId, "--force"],
328
+ {
329
+ stdio: "inherit"
330
+ }
331
+ );
332
+ } catch (error) {
333
+ logger.error("Failed to delete functions:");
334
+ logger.error(error.message);
335
+ exitCode2(1);
336
+ }
337
+ }
338
+
339
+ // src/lib/commands/delete/utils/read_functions.ts
340
+ import { basename as basename2, join as join3 } from "node:path";
341
+ import client from "firebase-tools";
342
+
343
+ // src/lib/commands/deploy/utils/find_functions.ts
344
+ import { join as join2 } from "node:path";
345
+ async function findFunctions2(dir) {
346
+ const functions2 = [];
347
+ try {
348
+ const entries = await readDir2(dir);
349
+ for (const entry of entries) {
350
+ const path = join2(dir, entry.name);
351
+ if (entry.isDirectory()) {
352
+ functions2.push(...await findFunctions2(path));
353
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js"))) {
354
+ const name = entry.name;
355
+ if (name.endsWith(".test.ts") || name.endsWith(".spec.ts") || name.endsWith(".test.js") || name.endsWith(".spec.js") || name.endsWith("_test.ts") || name.endsWith("_test.js")) {
356
+ continue;
357
+ }
358
+ functions2.push(path);
359
+ }
360
+ }
361
+ } catch {
362
+ }
363
+ return functions2;
364
+ }
365
+
366
+ // src/lib/commands/delete/utils/read_functions.ts
367
+ async function getLocalFunctionNames(options) {
368
+ try {
369
+ const functionsPath = join3(process.cwd(), options.functionsDirectory);
370
+ const localFunctionFiles = await findFunctions2(functionsPath);
371
+ const localFunctionNames = localFunctionFiles.map(
372
+ (file) => basename2(file).replace(/\.(ts|tsx|js)$/, "")
373
+ );
374
+ logger.debug("localFunctionNames", localFunctionNames);
375
+ return localFunctionNames;
376
+ } catch (error) {
377
+ logger.error("getLocalFunctionNames", error);
378
+ throw error;
379
+ }
380
+ }
381
+ async function getOnlineFunctionNames(options) {
382
+ try {
383
+ const onlineFunctions = await client.functions.list({
384
+ project: options.projectId
385
+ });
386
+ const onlineFunctionNames = onlineFunctions.map(
387
+ (functionData) => functionData.id
388
+ );
389
+ logger.debug("onlineFunctionNames", onlineFunctionNames);
390
+ return onlineFunctionNames;
391
+ } catch (error) {
392
+ logger.error("getOnlineFunctionNames", error);
393
+ throw error;
394
+ }
395
+ }
396
+ async function getUnusedFunctionNames(options) {
397
+ try {
398
+ const [localFunctionNames, onlineFunctionNames] = await Promise.all([
399
+ getLocalFunctionNames(options),
400
+ getOnlineFunctionNames(options)
401
+ ]);
402
+ const unusedFunctionNames = onlineFunctionNames.filter(
403
+ (onlineFunctionName) => !localFunctionNames.includes(onlineFunctionName)
404
+ );
405
+ return unusedFunctionNames;
406
+ } catch (error) {
407
+ logger.error("getUnusedFunctionNames", error);
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ // src/lib/commands/deploy/utils/options.ts
413
+ import { join as join4 } from "node:path";
414
+ async function getOptions(cliOptions) {
415
+ const configPath = join4(cwdDir2(), "firestack.json");
416
+ let config = {};
417
+ try {
418
+ const configContent = await readTextFile(configPath);
419
+ config = JSON.parse(configContent);
420
+ logger.debug(`Using configuration from ${configPath}`);
421
+ } catch (e) {
422
+ const error = e;
423
+ if (error.code === "ENOENT") {
424
+ logger.debug("firestack.json not found, using command-line options.");
425
+ } else {
426
+ logger.error(`Failed to read firestack.json at ${configPath}: ${error.message}`);
427
+ exitCode2(1);
428
+ }
429
+ }
430
+ const options = {
431
+ ...cliOptions,
432
+ functionsDirectory: cliOptions.functionsDirectory || config.functionsDirectory || "src/controllers",
433
+ rulesDirectory: cliOptions.rulesDirectory || config.rulesDirectory || "src/rules",
434
+ scriptsDirectory: cliOptions.scriptsDirectory || config.scriptsDirectory || "scripts",
435
+ initScript: cliOptions.initScript || config.initScript || "init.ts",
436
+ region: cliOptions.region || config.region,
437
+ nodeVersion: cliOptions.nodeVersion || config.nodeVersion || "20",
438
+ projectId: cliOptions.projectId || config.flavors?.[cliOptions.flavor]
439
+ };
440
+ logger.setLogSeverity(options);
441
+ logger.debug("Starting deployment...");
442
+ logger.debug("Options:", options);
443
+ return options;
444
+ }
445
+
446
+ // src/lib/commands/delete.ts
447
+ var deleteCommand = new Command3("delete").description("Deletes all unused Firebase functions.").option("--flavor <flavor>", "The flavor to use for deletion.", "development").option("--dry-run", "Show the deletion commands without executing them.").option("--verbose", "Whether to run the command with verbose logging.").option("--projectId <projectId>", "The Firebase project ID to delete from.").option("--all", "Delete all functions in the project.").action(async (cliOptions) => {
448
+ const options = await getOptions(cliOptions);
449
+ if (!options.projectId) {
450
+ logger.error(
451
+ "Project ID not found. Please provide it using --projectId option or in firestack.json."
452
+ );
453
+ exitCode2(1);
454
+ }
455
+ const functionsToDelete = cliOptions.all ? await getOnlineFunctionNames(options) : await getUnusedFunctionNames(options);
456
+ logger.info("Functions to delete:", functionsToDelete);
457
+ if (functionsToDelete.length > 0) {
458
+ if (cliOptions.dryRun) {
459
+ logger.info("Dry run: skipping deletion.");
460
+ } else {
461
+ await deleteFunctions(options, functionsToDelete);
462
+ logger.info("Deletion complete.");
463
+ }
464
+ } else {
465
+ logger.info("No unused functions found.");
466
+ }
467
+ });
468
+
469
+ // src/lib/commands/deploy/index.ts
470
+ import { basename as basename4, join as join10 } from "node:path";
471
+ import { cwd as cwd3, exit as exit3 } from "node:process";
472
+ import { Command as Command4 } from "commander";
473
+
474
+ // src/lib/utils/run-functions.ts
475
+ var runFunctions = async (functions2, concurrency = 5) => {
476
+ const results = [];
477
+ let i = 0;
478
+ while (i < functions2.length) {
479
+ const chunk = functions2.slice(i, i + concurrency);
480
+ i += concurrency;
481
+ results.push(...await Promise.all(chunk.map((fn) => fn())));
482
+ }
483
+ return results;
484
+ };
485
+
486
+ // src/lib/commands/deploy/utils/environment.ts
487
+ import { join as join5 } from "node:path";
488
+ async function getEnvironment(flavor) {
489
+ const envPath = join5(cwdDir2(), `.env.${flavor}`);
490
+ try {
491
+ const envContent = await readTextFile(envPath);
492
+ return envContent.split("\n").reduce(
493
+ (acc, line) => {
494
+ const [key, ...rest] = line.split("=");
495
+ const value = rest.join("=");
496
+ if (key && value && !key.startsWith("FIREBASE_SERVICE_ACCOUNT")) {
497
+ acc[key] = value;
498
+ }
499
+ return acc;
500
+ },
501
+ {}
502
+ );
503
+ } catch (e) {
504
+ const error = e;
505
+ if (error.code === "ENOENT") {
506
+ return {};
507
+ }
508
+ throw e;
509
+ }
510
+ }
511
+
512
+ // src/lib/commands/deploy/utils/process_function.ts
513
+ import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync } from "node:fs";
514
+ import { join as join9 } from "node:path";
515
+
516
+ // src/lib/utils/checksum.ts
517
+ import { createHash } from "node:crypto";
518
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
519
+ import { join as join6 } from "node:path";
520
+ var algorithm = "md5";
521
+ var encoding = "hex";
522
+ var checksumsFolderName = ".checksums";
523
+ var checksumsFolderPath = (options) => join6(options.outputDirectory, checksumsFolderName, options.flavor);
524
+ var checkForChanges = async (deployFunction) => {
525
+ try {
526
+ const { environment, outputRoot } = deployFunction;
527
+ const newCode = readFileSync(join6(outputRoot, "src/index.js"), "utf-8");
528
+ const environmentString = environment ? Object.entries(environment).map(([key, value]) => `${key}=${value}`).join("") : "";
529
+ const cachedChecksum = deployFunction.checksum ?? await getCachedChecksum(deployFunction);
530
+ const newChecksum = generateChecksum(newCode + environmentString);
531
+ if (!deployFunction.force && cachedChecksum && cachedChecksum === newChecksum) {
532
+ console.log(
533
+ `%c${deployFunction.functionName} has not changed, skipping deployment`,
534
+ "color: green"
535
+ );
536
+ return void 0;
537
+ }
538
+ deployFunction.checksum = newChecksum;
539
+ return deployFunction;
540
+ } catch (error) {
541
+ console.warn(
542
+ `%cError checking for local changes with ${deployFunction.functionName}.`,
543
+ "color: yellow"
544
+ );
545
+ console.debug(error);
546
+ return deployFunction;
547
+ }
548
+ };
549
+ var generateChecksum = (code) => {
550
+ const hash = createHash(algorithm);
551
+ hash.update(code);
552
+ return hash.digest(encoding);
553
+ };
554
+ var getCachedChecksum = async ({
555
+ outputDirectory,
556
+ functionName,
557
+ flavor
558
+ }) => {
559
+ try {
560
+ const checksumFileName = `${functionName}.${algorithm}`;
561
+ const checksumPath = join6(checksumsFolderPath({ outputDirectory, flavor }), checksumFileName);
562
+ if (existsSync2(checksumPath)) {
563
+ return readFileSync(checksumPath, "utf-8");
564
+ }
565
+ return void 0;
566
+ } catch (error) {
567
+ console.debug(error);
568
+ return;
569
+ }
570
+ };
571
+ var cacheChecksumLocal = async ({
572
+ outputDirectory,
573
+ checksum,
574
+ flavor,
575
+ functionName
576
+ }) => {
577
+ try {
578
+ if (!checksum) {
579
+ return;
580
+ }
581
+ const checksumFileName = `${functionName}.${algorithm}`;
582
+ const folderPath = checksumsFolderPath({ outputDirectory, flavor });
583
+ mkdirSync2(folderPath, { recursive: true });
584
+ writeFileSync(join6(folderPath, checksumFileName), checksum);
585
+ } catch (error) {
586
+ console.debug(error);
587
+ }
588
+ };
589
+
590
+ // src/lib/utils/common.ts
591
+ import { dirname as dirname3 } from "node:path";
592
+ async function findProjectRoot() {
593
+ let current = cwdDir2();
594
+ while (true) {
595
+ const entries = await readDir2(current);
596
+ for (const entry of entries) {
597
+ if (entry.isFile() && (entry.name === "firestack.json" || entry.name === "package.json")) {
598
+ return current;
599
+ }
600
+ }
601
+ const parent = dirname3(current);
602
+ if (parent === current) {
603
+ throw new Error("Could not find project root. Make sure you are in a firestack project.");
604
+ }
605
+ current = parent;
606
+ }
607
+ }
608
+
609
+ // src/lib/utils/firebase_utils.ts
610
+ function createFirebaseConfig(nodeVersion) {
611
+ const firebaseJsonContent = {
612
+ functions: {
613
+ source: "src",
614
+ runtime: `nodejs${nodeVersion}`
615
+ }
616
+ };
617
+ return JSON.stringify(firebaseJsonContent, null, 2);
618
+ }
619
+ function createPackageJson(nodeVersion) {
620
+ const packageJsonContent = {
621
+ type: "module",
622
+ main: "index.js",
623
+ engines: {
624
+ node: nodeVersion
625
+ }
626
+ };
627
+ return JSON.stringify(packageJsonContent, null, 2);
628
+ }
629
+ function toDotEnvironmentCode(environment) {
630
+ return Object.entries(environment).map(([key, value]) => `${key}=${value}`).join("\n");
631
+ }
632
+
633
+ // src/lib/utils/function_naming.ts
634
+ import { relative } from "node:path";
635
+ function deriveFunctionName(funcPath, controllersPath) {
636
+ const relativePath = relative(controllersPath, funcPath);
637
+ const parts = relativePath.replace(/\\/g, "/").split("/");
638
+ const fileName = parts[parts.length - 1].replace(/\.(ts|tsx|js)$/, "");
639
+ const pathParts = parts.slice(1, -1);
640
+ const nameParts = pathParts.filter((part) => !part.startsWith("[")).concat(fileName);
641
+ return nameParts.join("_");
642
+ }
643
+ function extractDocumentPath(funcPath, controllersPath) {
644
+ const relativePath = relative(controllersPath, funcPath);
645
+ const parts = relativePath.replace(/\\/g, "/").split("/");
646
+ if (parts[0] !== "firestore") {
647
+ return void 0;
648
+ }
649
+ const pathParts = parts.slice(1, -1);
650
+ const documentPath = pathParts.map((part) => {
651
+ if (part.startsWith("[") && part.endsWith("]")) {
652
+ return `{${part.slice(1, -1)}}`;
653
+ }
654
+ return part;
655
+ }).join("/");
656
+ return documentPath || void 0;
657
+ }
658
+ function extractDatabaseRef(funcPath, controllersPath) {
659
+ const relativePath = relative(controllersPath, funcPath);
660
+ const parts = relativePath.replace(/\\/g, "/").split("/");
661
+ if (parts[0] !== "database") {
662
+ return void 0;
663
+ }
664
+ const pathParts = parts.slice(1, -1);
665
+ const refPath = `/${pathParts.map((part) => {
666
+ if (part.startsWith("[") && part.endsWith("]")) {
667
+ return `{${part.slice(1, -1)}}`;
668
+ }
669
+ return part;
670
+ }).join("/")}`;
671
+ return refPath;
672
+ }
673
+
674
+ // src/lib/utils/read-compiled-file.ts
675
+ import { readFileSync as readFileSync2 } from "node:fs";
676
+ import { join as join7 } from "node:path";
677
+ var getEnvironmentNeeded = async (outputRoot, environment) => {
678
+ try {
679
+ if (!environment) {
680
+ return;
681
+ }
682
+ const outputPath = join7(outputRoot, "src/index.js");
683
+ const code = readFileSync2(outputPath, "utf-8");
684
+ return Object.fromEntries(Object.entries(environment).filter(([key]) => code.includes(key)));
685
+ } catch (error) {
686
+ console.error("getEnvironmentKeysNeeded", error);
687
+ return;
688
+ }
689
+ };
690
+
691
+ // src/lib/commands/deploy/utils/create_deploy_index.ts
692
+ import { join as join8, relative as relative2 } from "node:path";
693
+ import {
694
+ createSourceFile,
695
+ forEachChild,
696
+ isCallExpression,
697
+ isExportAssignment,
698
+ isIdentifier,
699
+ isObjectLiteralExpression,
700
+ isPropertyAssignment,
701
+ isStringLiteral,
702
+ ScriptTarget,
703
+ SyntaxKind
704
+ } from "typescript";
705
+
706
+ // src/lib/constants/function-types.ts
707
+ var firestoreFunctions = [
708
+ "onCreated",
709
+ "onUpdated",
710
+ "onDeleted",
711
+ "onWritten",
712
+ "onDocumentCreated",
713
+ "onDocumentUpdated",
714
+ "onDocumentDeleted",
715
+ "onDocumentWritten"
716
+ ];
717
+ var databaseFunctions = [
718
+ "onValueCreated",
719
+ "onValueDeleted",
720
+ "onValueUpdated",
721
+ "onValueWritten"
722
+ ];
723
+ var storageFunctions = [
724
+ "onObjectArchived",
725
+ "onObjectDeleted",
726
+ "onObjectFinalized",
727
+ "onObjectMetadataUpdated"
728
+ ];
729
+ var authFunctions = [
730
+ "onAuthCreate",
731
+ "onAuthDelete",
732
+ "beforeAuthCreate",
733
+ "beforeAuthSignIn"
734
+ ];
735
+ var schedulerFunctions = ["onSchedule"];
736
+ var httpsFunctions = ["onCall", "onRequest"];
737
+ var functions = [
738
+ ...authFunctions,
739
+ ...databaseFunctions,
740
+ ...firestoreFunctions,
741
+ ...storageFunctions,
742
+ ...schedulerFunctions,
743
+ ...httpsFunctions
744
+ ];
745
+
746
+ // src/lib/commands/deploy/utils/create_deploy_index.ts
747
+ async function createTemporaryIndexFunctionFile(buildFunctionData) {
748
+ logger.debug("Creating temporary index file", buildFunctionData);
749
+ const code = await toDeployIndexCode(buildFunctionData);
750
+ if (!code) {
751
+ return buildFunctionData.funcPath;
752
+ }
753
+ const temporaryFilePath = getTemporaryFilePath(
754
+ buildFunctionData.temporaryDirectory,
755
+ buildFunctionData.functionName
756
+ );
757
+ await writeTextFile(temporaryFilePath, code);
758
+ return temporaryFilePath;
759
+ }
760
+ var getTemporaryFilePath = (temporaryDirectory, functionName) => join8(temporaryDirectory, `${functionName}.ts`);
761
+ async function toDeployIndexCode(buildFunctionData) {
762
+ const { functionName, funcPath, temporaryDirectory } = buildFunctionData;
763
+ const fileContent = await readTextFile(funcPath);
764
+ const sourceFile = createSourceFile(funcPath, fileContent, ScriptTarget.ESNext, true);
765
+ let deployFunction;
766
+ let options = {};
767
+ forEachChild(sourceFile, (node) => {
768
+ if (isExportAssignment(node) && isCallExpression(node.expression) && isIdentifier(node.expression.expression)) {
769
+ const escapedText = node.expression.expression.escapedText;
770
+ if (isDeployFunction(escapedText)) {
771
+ deployFunction = escapedText;
772
+ const optionsNode = node.expression.arguments[1];
773
+ if (optionsNode && isObjectLiteralExpression(optionsNode)) {
774
+ options = parseOptions(optionsNode);
775
+ }
776
+ }
777
+ }
778
+ });
779
+ if (!deployFunction) {
780
+ logger.debug(`No deploy function type found in ${funcPath}`);
781
+ return void 0;
782
+ }
783
+ const rootFunctionBuilder = toRootFunction(deployFunction);
784
+ const functionCodeType = toFunctionCodeType(deployFunction);
785
+ const importPath = `${relative2(temporaryDirectory, funcPath).replaceAll("\\", "/").replace(/\.(ts|js)$/, "")}.ts`;
786
+ logger.debug("Generated importPath:", importPath);
787
+ if (rootFunctionBuilder === "firestore") {
788
+ const documentPath = extractDocumentPath(funcPath, buildFunctionData.controllersPath);
789
+ if (documentPath) {
790
+ options.document = documentPath;
791
+ }
792
+ } else if (rootFunctionBuilder === "database") {
793
+ const refPath = extractDatabaseRef(funcPath, buildFunctionData.controllersPath);
794
+ if (refPath) {
795
+ options.ref = refPath;
796
+ }
797
+ }
798
+ const optionsCode = toOptionsCode(options);
799
+ const fileCode = `
800
+ import { ${functionCodeType} } from 'firebase-functions/${rootFunctionBuilder}';
801
+ import functionStart from '${importPath}';
802
+
803
+ export const ${functionName} = ${functionCodeType}(${optionsCode}, functionStart);
804
+ `;
805
+ return fileCode;
806
+ }
807
+ var isDeployFunction = (functionName) => {
808
+ return functions.includes(functionName);
809
+ };
810
+ var parseOptions = (optionsNode) => {
811
+ const options = {};
812
+ optionsNode.properties.forEach((prop) => {
813
+ if (isPropertyAssignment(prop) && prop.name) {
814
+ const key = prop.name.getText();
815
+ const value = getInitializerValue(prop.initializer);
816
+ if (value !== void 0) {
817
+ options[key] = value;
818
+ }
819
+ }
820
+ });
821
+ return options;
822
+ };
823
+ var getInitializerValue = (initializer) => {
824
+ if (isStringLiteral(initializer)) {
825
+ return initializer.text;
826
+ }
827
+ if (initializer.kind === SyntaxKind.TrueKeyword) {
828
+ return true;
829
+ }
830
+ if (initializer.kind === SyntaxKind.FalseKeyword) {
831
+ return false;
832
+ }
833
+ if (initializer.kind === SyntaxKind.NumericLiteral) {
834
+ return Number(initializer.getText());
835
+ }
836
+ if (isObjectLiteralExpression(initializer)) {
837
+ return parseOptions(initializer);
838
+ }
839
+ logger.warn(`Unsupported initializer kind: ${SyntaxKind[initializer.kind]}`);
840
+ return void 0;
841
+ };
842
+ var toRootFunction = (deployFunction) => {
843
+ switch (deployFunction) {
844
+ case "onCreated":
845
+ case "onUpdated":
846
+ case "onDeleted":
847
+ case "onWritten":
848
+ case "onDocumentCreated":
849
+ case "onDocumentUpdated":
850
+ case "onDocumentDeleted":
851
+ case "onDocumentWritten":
852
+ return "firestore";
853
+ case "onValueCreated":
854
+ case "onValueUpdated":
855
+ case "onValueDeleted":
856
+ case "onValueWritten":
857
+ return "database";
858
+ case "onCall":
859
+ case "onRequest":
860
+ return "https";
861
+ case "onSchedule":
862
+ return "scheduler";
863
+ case "onObjectArchived":
864
+ case "onObjectDeleted":
865
+ case "onObjectFinalized":
866
+ case "onObjectMetadataUpdated":
867
+ return "storage";
868
+ case "onAuthDelete":
869
+ case "onAuthCreate":
870
+ case "beforeAuthCreate":
871
+ case "beforeAuthSignIn":
872
+ return "auth";
873
+ default:
874
+ throw new Error("Invalid function type");
875
+ }
876
+ };
877
+ var toFunctionCodeType = (deployFunction) => {
878
+ switch (deployFunction) {
879
+ case "onCall":
880
+ return "onCall";
881
+ case "onRequest":
882
+ return "onRequest";
883
+ case "onCreated":
884
+ case "onDocumentCreated":
885
+ return "onDocumentCreated";
886
+ case "onDeleted":
887
+ case "onDocumentDeleted":
888
+ return "onDocumentDeleted";
889
+ case "onUpdated":
890
+ case "onDocumentUpdated":
891
+ return "onDocumentUpdated";
892
+ case "onWritten":
893
+ case "onDocumentWritten":
894
+ return "onDocumentWritten";
895
+ case "onSchedule":
896
+ return "onSchedule";
897
+ case "onObjectArchived":
898
+ return "onObjectArchived";
899
+ case "onObjectDeleted":
900
+ return "onObjectDeleted";
901
+ case "onObjectFinalized":
902
+ return "onObjectFinalized";
903
+ case "onObjectMetadataUpdated":
904
+ return "onObjectMetadataUpdated";
905
+ case "onValueCreated":
906
+ return "onValueCreated";
907
+ case "onValueDeleted":
908
+ return "onValueDeleted";
909
+ case "onValueUpdated":
910
+ return "onValueUpdated";
911
+ case "onValueWritten":
912
+ return "onValueWritten";
913
+ case "onAuthCreate":
914
+ return "onCreate";
915
+ case "onAuthDelete":
916
+ return "onDelete";
917
+ case "beforeAuthCreate":
918
+ return "beforeCreate";
919
+ case "beforeAuthSignIn":
920
+ return "beforeSignIn";
921
+ default:
922
+ throw new Error(`Unknown function type: ${deployFunction}`);
923
+ }
924
+ };
925
+ var toOptionsCode = (options) => {
926
+ let optionsCode = "{";
927
+ for (const [key, value] of Object.entries(options)) {
928
+ if (Array.isArray(value)) {
929
+ optionsCode += `'${key}': [${value.map((v) => `${typeof v === "string" ? `'${v}'` : v}`).join(",")}],`;
930
+ continue;
931
+ }
932
+ if (typeof value === "object") {
933
+ optionsCode += `'${key}': ${toOptionsCode(value)},`;
934
+ continue;
935
+ }
936
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
937
+ continue;
938
+ }
939
+ optionsCode += `'${key}': ${typeof value === "string" ? `'${value}'` : value},`;
940
+ }
941
+ optionsCode += "}";
942
+ return optionsCode;
943
+ };
944
+
945
+ // src/lib/commands/deploy/utils/process_function.ts
946
+ async function processFunction(funcPath, options, environment, controllersPath) {
947
+ const functionName = deriveFunctionName(funcPath, controllersPath);
948
+ logger.info(`
949
+ Processing function: ${functionName}`);
950
+ const outputDir = join9(cwdDir2(), "dist", functionName);
951
+ const temporaryDir = join9(cwdDir2(), "tmp", functionName);
952
+ await mkdir3(join9(outputDir, "src"), { recursive: true });
953
+ await mkdir3(temporaryDir, { recursive: true });
954
+ await writeTextFile(join9(outputDir, "firebase.json"), createFirebaseConfig(options.nodeVersion));
955
+ await writeTextFile(
956
+ join9(outputDir, "src", "package.json"),
957
+ createPackageJson(options.nodeVersion)
958
+ );
959
+ const outputFile = join9(outputDir, "src", "index.js");
960
+ logger.debug(`Building ${funcPath} to ${outputFile}...`);
961
+ try {
962
+ const inputFile = await createTemporaryIndexFunctionFile({
963
+ funcPath,
964
+ functionName,
965
+ temporaryDirectory: temporaryDir,
966
+ controllersPath
967
+ });
968
+ logger.debug("Temporary input file content:", await readTextFile(inputFile));
969
+ const projectRoot = await findProjectRoot();
970
+ await buildFunction({
971
+ inputFile,
972
+ outputFile,
973
+ configPath: join9(projectRoot, "package.json"),
974
+ minify: options.minify,
975
+ sourcemap: options.sourcemap
976
+ });
977
+ logger.debug(`Successfully built ${functionName}.`);
978
+ } catch (buildError) {
979
+ logger.error(`Failed to build ${functionName}: ${buildError.message}`);
980
+ return { functionName, status: "failed" };
981
+ } finally {
982
+ if (!options.debug) {
983
+ await remove(temporaryDir, { recursive: true });
984
+ }
985
+ }
986
+ const envNeeded = await getEnvironmentNeeded(outputDir, environment);
987
+ if (envNeeded) {
988
+ const envCode = toDotEnvironmentCode(envNeeded);
989
+ await writeTextFile(join9(outputDir, ".env"), envCode);
990
+ }
991
+ const deployFunctionData = await checkForChanges({
992
+ functionName,
993
+ outputRoot: outputDir,
994
+ flavor: options.flavor,
995
+ force: options.force,
996
+ outputDirectory: join9(cwdDir2(), "dist"),
997
+ environment: envNeeded
998
+ });
999
+ if (!deployFunctionData) {
1000
+ return { functionName, status: "skipped" };
1001
+ }
1002
+ if (!options.dryRun) {
1003
+ logger.debug("Copying shared dependencies...");
1004
+ try {
1005
+ const dependenciesDir = join9(cwdDir2(), "tmp", "dependencies");
1006
+ const outputNodeModules = join9(outputDir, "node_modules");
1007
+ await remove(outputNodeModules, { recursive: true });
1008
+ if (existsSync3(join9(dependenciesDir, "node_modules"))) {
1009
+ copyDirRecursive(join9(dependenciesDir, "node_modules"), outputNodeModules);
1010
+ }
1011
+ if (existsSync3(join9(dependenciesDir, "package.json"))) {
1012
+ copyFileSync(join9(dependenciesDir, "package.json"), join9(outputDir, "src", "package.json"));
1013
+ }
1014
+ } catch (error) {
1015
+ logger.error("Failed to copy shared dependencies:", error);
1016
+ return { functionName, status: "failed" };
1017
+ }
1018
+ logger.debug("Installing dependencies...");
1019
+ const npmInstall = new Command2("npm", {
1020
+ args: ["install"],
1021
+ cwd: join9(outputDir, "src")
1022
+ });
1023
+ const { code: npmCode, stderr: npmStderr } = await npmInstall.output();
1024
+ if (npmCode !== 0) {
1025
+ logger.error("Failed to install dependencies:");
1026
+ logger.error(new TextDecoder().decode(npmStderr));
1027
+ return { functionName, status: "failed" };
1028
+ }
1029
+ logger.debug("Dependencies installed successfully.");
1030
+ }
1031
+ const deployArgs = [
1032
+ "deploy",
1033
+ "--only",
1034
+ `functions:${functionName}`,
1035
+ "--project",
1036
+ options.projectId
1037
+ ];
1038
+ if (options.force) {
1039
+ deployArgs.push("--force");
1040
+ }
1041
+ logger.debug(`> firebase ${deployArgs.join(" ")}`);
1042
+ if (!options.dryRun) {
1043
+ try {
1044
+ const command = new Command2("firebase", {
1045
+ args: deployArgs,
1046
+ cwd: outputDir
1047
+ });
1048
+ const { success } = await command.spawn().status;
1049
+ if (success) {
1050
+ logger.info(`Successfully deployed ${functionName}.`);
1051
+ await cacheChecksumLocal(deployFunctionData);
1052
+ return { functionName, status: "deployed" };
1053
+ }
1054
+ logger.error(`Failed to deploy ${functionName}.`);
1055
+ return { functionName, status: "failed" };
1056
+ } catch (deployError) {
1057
+ logger.error(`Failed to deploy ${functionName}: ${deployError.message}`);
1058
+ return { functionName, status: "failed" };
1059
+ }
1060
+ } else {
1061
+ logger.info(`Dry run: skipped deployment of ${functionName}.`);
1062
+ return { functionName, status: "dry-run" };
1063
+ }
1064
+ }
1065
+ function copyDirRecursive(src, dest) {
1066
+ mkdirSync3(dest, { recursive: true });
1067
+ const entries = readdirSync(src, { withFileTypes: true });
1068
+ for (const entry of entries) {
1069
+ const srcPath = join9(src, entry.name);
1070
+ const destPath = join9(dest, entry.name);
1071
+ if (entry.isDirectory()) {
1072
+ copyDirRecursive(srcPath, destPath);
1073
+ } else {
1074
+ copyFileSync(srcPath, destPath);
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ // src/lib/commands/deploy/utils/retry_failed_functions.ts
1080
+ async function retryFailedFunctions(failedFunctions, functionFiles, options, environment, controllersPath) {
1081
+ if (failedFunctions.length > 0 && options.retryAmount && options.retryAmount > 0) {
1082
+ logger.warn(`
1083
+ Retrying ${failedFunctions.length} failed functions...`);
1084
+ for (let i = 0; i < options.retryAmount; i++) {
1085
+ logger.warn(`Retry attempt ${i + 1}/${options.retryAmount}`);
1086
+ const retryFiles = functionFiles.filter((file) => {
1087
+ const functionName = deriveFunctionName(file, controllersPath);
1088
+ return failedFunctions.some((f) => f.functionName === functionName);
1089
+ });
1090
+ const retryResults = (await runFunctions(
1091
+ retryFiles.map(
1092
+ (path) => () => processFunction(path, options, environment, controllersPath)
1093
+ ),
1094
+ options.concurrency
1095
+ )).filter((r) => r);
1096
+ const newlyFailed = retryResults.filter((r) => r?.status === "failed");
1097
+ if (newlyFailed.length === 0) {
1098
+ logger.info("All failed functions deployed successfully on retry.");
1099
+ return [];
1100
+ }
1101
+ failedFunctions = newlyFailed;
1102
+ }
1103
+ }
1104
+ return failedFunctions;
1105
+ }
1106
+
1107
+ // src/lib/commands/deploy/index.ts
1108
+ function cwdDir3() {
1109
+ return cwd3();
1110
+ }
1111
+ function exitCode3(code) {
1112
+ return exit3(code);
1113
+ }
1114
+ var deployCommand = new Command4("deploy").description("Builds and deploys all Firebase functions.").option("--flavor <flavor>", "The flavor to use for deployment.", "development").option("--dry-run", "Show the deployment commands without executing them.").option("--force", "Force deploy all functions, even if no files changed.").option("--verbose", "Whether to run the command with verbose logging.").option("--only <only>", "Only deploy the given function names separated by comma.").option("--region <region>", "The default region to deploy the functions to.").option("--concurrency <concurrency>", "The number of functions to deploy in parallel.", "5").option("--retryAmount <retryAmount>", "The amount of times to retry a failed deployment.", "0").option("--minify", "Will minify the functions.", true).option("--sourcemap", "Whether to generate sourcemaps.", true).option(
1115
+ "--functionsDirectory <functionsDirectory>",
1116
+ "The directory where the functions are located."
1117
+ ).option("--projectId <projectId>", "The Firebase project ID to deploy to.").option("--node-version <nodeVersion>", "The Node.js version to use for the functions.").option("--debug", "Enable debug mode (keeps temporary files).").action(async (cliOptions) => {
1118
+ const options = await getOptions(cliOptions);
1119
+ if (!options.projectId) {
1120
+ logger.error(
1121
+ "Project ID not found. Please provide it using --projectId option or in firestack.json."
1122
+ );
1123
+ exitCode3(1);
1124
+ }
1125
+ const functionsPath = join10(cwdDir3(), options.functionsDirectory);
1126
+ let functionFiles = await findFunctions2(functionsPath);
1127
+ if (options.only) {
1128
+ const onlyFunctions = options.only.split(",").map((f) => f.trim());
1129
+ functionFiles = functionFiles.filter((file) => {
1130
+ const functionName = basename4(file).replace(/\.(ts|tsx|js)$/, "");
1131
+ return onlyFunctions.includes(functionName);
1132
+ });
1133
+ }
1134
+ if (functionFiles.length === 0) {
1135
+ logger.warn("No functions found to deploy.");
1136
+ return;
1137
+ }
1138
+ logger.info(`Found ${functionFiles.length} functions to deploy.`);
1139
+ const environment = await getEnvironment(options.flavor);
1140
+ const results = (await runFunctions(
1141
+ functionFiles.map(
1142
+ (path) => () => processFunction(path, options, environment, functionsPath)
1143
+ ),
1144
+ options.concurrency
1145
+ )).filter((r) => r);
1146
+ let failedFunctions = results.filter((r) => r?.status === "failed");
1147
+ failedFunctions = await retryFailedFunctions(
1148
+ failedFunctions,
1149
+ functionFiles,
1150
+ options,
1151
+ environment,
1152
+ functionsPath
1153
+ );
1154
+ if (failedFunctions.length > 0) {
1155
+ logger.error(`
1156
+ Deployment failed for ${failedFunctions.length} functions.`);
1157
+ exitCode3(1);
1158
+ }
1159
+ logger.info("\nDeployment process complete!");
1160
+ });
1161
+
1162
+ // src/lib/commands/emulate.ts
1163
+ import { existsSync as existsSync4, watch as watch2 } from "node:fs";
1164
+ import { mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
1165
+ import { basename as basename5, join as join11, relative as relative3 } from "node:path";
1166
+ import { Command as Command5 } from "commander";
1167
+ import { execa as execa2 } from "execa";
1168
+ async function runInitScript(scriptsDirectory, initScript, projectId) {
1169
+ const initScriptPath = join11(process.cwd(), scriptsDirectory, initScript);
1170
+ if (!existsSync4(initScriptPath)) {
1171
+ logger.info(`Init script not found at ${initScriptPath}, skipping.`);
1172
+ return;
1173
+ }
1174
+ logger.info(`Running init script: ${initScript}`);
1175
+ try {
1176
+ await execa2("bun", ["run", initScriptPath], {
1177
+ cwd: process.cwd(),
1178
+ env: {
1179
+ ...process.env,
1180
+ FIREBASE_PROJECT_ID: projectId,
1181
+ GCLOUD_PROJECT: projectId
1182
+ },
1183
+ stdio: "inherit"
1184
+ });
1185
+ logger.info("Init script completed successfully.");
1186
+ } catch (error) {
1187
+ logger.error(`Failed to run init script: ${error.message}`);
1188
+ throw error;
1189
+ }
1190
+ }
1191
+ async function buildEmulatorFunctions(functionFiles, outputDir, options, controllersPath) {
1192
+ const projectRoot = await findProjectRoot();
1193
+ const tempDir = join11(process.cwd(), "tmp", "emulator");
1194
+ await mkdir4(tempDir, { recursive: true });
1195
+ await mkdir4(join11(outputDir, "src"), { recursive: true });
1196
+ const exports = [];
1197
+ for (const funcFile of functionFiles) {
1198
+ const funcName = deriveFunctionName(funcFile, controllersPath);
1199
+ const generatedFile = await createTemporaryIndexFunctionFile({
1200
+ funcPath: funcFile,
1201
+ functionName: funcName,
1202
+ temporaryDirectory: tempDir,
1203
+ controllersPath
1204
+ });
1205
+ if (generatedFile !== funcFile) {
1206
+ const relativePath = relative3(tempDir, generatedFile).replace(/\\/g, "/");
1207
+ const importPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
1208
+ exports.push(`export * from '${importPath}';`);
1209
+ }
1210
+ }
1211
+ const combinedIndexContent = `${exports.join("\n")}
1212
+ `;
1213
+ const tempIndexPath = join11(tempDir, "index.ts");
1214
+ await writeFile4(tempIndexPath, combinedIndexContent);
1215
+ logger.debug("Generated combined index file:", combinedIndexContent);
1216
+ await buildFunction({
1217
+ inputFile: tempIndexPath,
1218
+ outputFile: join11(outputDir, "src", "index.js"),
1219
+ configPath: join11(projectRoot, "package.json"),
1220
+ minify: options.minify,
1221
+ sourcemap: options.sourcemap
1222
+ });
1223
+ const packageJson = {
1224
+ name: "functions",
1225
+ type: "module",
1226
+ main: "index.js",
1227
+ engines: { node: `${options.nodeVersion || "20"}` }
1228
+ };
1229
+ await writeFile4(join11(outputDir, "src", "package.json"), JSON.stringify(packageJson, null, 2));
1230
+ logger.info("Installing dependencies...");
1231
+ try {
1232
+ await execa2("npm", ["install"], {
1233
+ cwd: join11(outputDir, "src"),
1234
+ stdio: "inherit"
1235
+ });
1236
+ } catch (error) {
1237
+ throw new Error("Failed to install dependencies");
1238
+ }
1239
+ logger.info("Dependencies installed.");
1240
+ if (!options.debug) {
1241
+ await rm2(tempDir, { recursive: true, force: true }).catch(() => {
1242
+ });
1243
+ }
1244
+ }
1245
+ async function generateFirebaseJson(outputDir, options) {
1246
+ const firebaseConfig = {
1247
+ functions: [
1248
+ {
1249
+ source: "src",
1250
+ codebase: "default",
1251
+ runtime: `nodejs${options.nodeVersion || "20"}`
1252
+ }
1253
+ ],
1254
+ emulators: {
1255
+ functions: { port: 5001 },
1256
+ firestore: { port: 8080 },
1257
+ ui: { enabled: true, port: 4e3 },
1258
+ singleProjectMode: true
1259
+ }
1260
+ };
1261
+ if (options.firestoreRules && existsSync4(options.firestoreRules)) {
1262
+ firebaseConfig.firestore = {
1263
+ rules: options.firestoreRules
1264
+ };
1265
+ }
1266
+ if (options.storageRules && existsSync4(options.storageRules)) {
1267
+ firebaseConfig.storage = {
1268
+ rules: options.storageRules
1269
+ };
1270
+ }
1271
+ await writeFile4(join11(outputDir, "firebase.json"), JSON.stringify(firebaseConfig, null, 2));
1272
+ }
1273
+ async function watchAndRebuild(functionsPath, functionFiles, outputDir, options, controllersPath) {
1274
+ logger.info("Watching for file changes...");
1275
+ const watcher = watch2(functionsPath, { recursive: true });
1276
+ watcher.on("change", async (_eventType, filename) => {
1277
+ if (filename && typeof filename === "string" && (filename.endsWith(".ts") || filename.endsWith(".tsx"))) {
1278
+ logger.info(`File changed: ${basename5(filename)}, rebuilding...`);
1279
+ try {
1280
+ await buildEmulatorFunctions(functionFiles, outputDir, options, controllersPath);
1281
+ logger.info("Rebuild complete.");
1282
+ } catch (error) {
1283
+ logger.error(`Rebuild failed: ${error.message}`);
1284
+ }
1285
+ }
1286
+ });
1287
+ }
1288
+ var emulateCommand = new Command5("emulate").description("Starts the Firebase emulator with live reload.").option("--flavor <flavor>", "The flavor to use for emulation.", "development").option("--verbose", "Whether to run the command with verbose logging.").option("--debug", "Enable debug mode (keeps temporary files).").option("--projectId <projectId>", "The Firebase project ID to emulate.").option(
1289
+ "--only <only>",
1290
+ 'Only start the emulator for the given services (e.g., "functions,firestore").',
1291
+ "functions,firestore"
1292
+ ).option(
1293
+ "--firestoreRules <firestoreRules>",
1294
+ "Path to the Firestore rules file.",
1295
+ "firestore.rules"
1296
+ ).option("--storageRules <storageRules>", "Path to the Storage rules file.", "storage.rules").option("--watch", "Enable file watching for live reload.", true).option("--no-watch", "Disable file watching.").option("--init", "Run init script before starting emulators.", true).option("--no-init", "Skip running init script.").option("--minify", "Will minify the functions.", false).option("--no-minify", "Do not minify the functions.").option("--sourcemap", "Whether to generate sourcemaps.", true).option("--no-sourcemap", "Do not generate sourcemaps.").option(
1297
+ "--functionsDirectory <functionsDirectory>",
1298
+ "The directory where the functions are located."
1299
+ ).option("--node-version <nodeVersion>", "The Node.js version to use for the functions.").action(async (cliOptions) => {
1300
+ const options = await getOptions(cliOptions);
1301
+ if (!options.projectId) {
1302
+ logger.error(
1303
+ "Project ID not found. Please provide it using --projectId option or in firestack.json."
1304
+ );
1305
+ process.exit(1);
1306
+ }
1307
+ if (cliOptions.init !== false) {
1308
+ try {
1309
+ await runInitScript(
1310
+ options.scriptsDirectory || "scripts",
1311
+ options.initScript || "init.ts",
1312
+ options.projectId
1313
+ );
1314
+ } catch (error) {
1315
+ logger.error("Failed to run init script, continuing without initialization...");
1316
+ }
1317
+ }
1318
+ const functionsPath = join11(process.cwd(), options.functionsDirectory);
1319
+ const functionFiles = await findFunctions2(functionsPath);
1320
+ if (functionFiles.length === 0) {
1321
+ logger.warn("No functions found to emulate.");
1322
+ return;
1323
+ }
1324
+ logger.info(`Found ${functionFiles.length} functions to build.`);
1325
+ const outputDir = join11(process.cwd(), "dist", "emulator");
1326
+ await mkdir4(outputDir, { recursive: true });
1327
+ logger.info("Building functions for emulator...");
1328
+ await buildEmulatorFunctions(functionFiles, outputDir, options, functionsPath);
1329
+ logger.info("Build complete.");
1330
+ await generateFirebaseJson(outputDir, options);
1331
+ const commandArgs = ["emulators:start", "--project", options.projectId];
1332
+ if (cliOptions.only) {
1333
+ commandArgs.push("--only", cliOptions.only);
1334
+ }
1335
+ logger.info("Starting Firebase emulator...");
1336
+ logger.debug(`> firebase ${commandArgs.join(" ")}`);
1337
+ const emulatorProcess = execa2("firebase", commandArgs, {
1338
+ cwd: outputDir,
1339
+ stdio: "inherit"
1340
+ });
1341
+ if (cliOptions.watch) {
1342
+ watchAndRebuild(functionsPath, functionFiles, outputDir, options, functionsPath);
1343
+ }
1344
+ await emulatorProcess;
1345
+ });
1346
+
1347
+ // src/lib/commands/logs.ts
1348
+ import { Command as Command6 } from "commander";
1349
+ import { execa as execa3 } from "execa";
1350
+ var logsCommand = new Command6("logs").description("View logs from Firebase Cloud Functions.").option("--flavor <flavor>", "The flavor to use for logs.", "development").option("--verbose", "Whether to run the command with verbose logging.").option("--projectId <projectId>", "The Firebase project ID.").option("--only <only>", "Only show logs for specific function(s).").option("-n, --lines <lines>", "Number of log lines to fetch.", "50").option("--since <since>", 'Only show logs after this time (e.g., "1h", "30m").').option("--open", "Open logs in web browser.").action(async (cliOptions) => {
1351
+ const options = await getOptions(cliOptions);
1352
+ if (!options.projectId) {
1353
+ logger.error(
1354
+ "Project ID not found. Please provide it using --projectId option or in firestack.json."
1355
+ );
1356
+ process.exit(1);
1357
+ }
1358
+ const commandArgs = ["functions:log", "--project", options.projectId];
1359
+ if (cliOptions.only) {
1360
+ commandArgs.push("--only", cliOptions.only);
1361
+ }
1362
+ if (cliOptions.lines) {
1363
+ commandArgs.push("--lines", cliOptions.lines);
1364
+ }
1365
+ if (cliOptions.since) {
1366
+ commandArgs.push("--since", cliOptions.since);
1367
+ }
1368
+ if (cliOptions.open) {
1369
+ commandArgs.push("--open");
1370
+ }
1371
+ logger.info(`Fetching logs for project: ${options.projectId}`);
1372
+ logger.debug(`> firebase ${commandArgs.join(" ")}`);
1373
+ await execa3("firebase", commandArgs, {
1374
+ stdio: "inherit"
1375
+ });
1376
+ });
1377
+
1378
+ // src/lib/commands/rules.ts
1379
+ import { copyFileSync as copyFileSync2, existsSync as existsSync5, writeFileSync as writeFileSync2 } from "node:fs";
1380
+ import { join as join13 } from "node:path";
1381
+ import { Command as Command7 } from "commander";
1382
+ import { execa as execa4 } from "execa";
1383
+
1384
+ // src/lib/commands/rules/utils/rule_files.ts
1385
+ import { readdir as readdir3 } from "node:fs/promises";
1386
+ import { join as join12 } from "node:path";
1387
+ async function findRuleFiles(rulesDirectory) {
1388
+ const rules = [];
1389
+ const rulesPath = join12(cwdDir2(), rulesDirectory);
1390
+ try {
1391
+ const entries = await readdir3(rulesPath, { withFileTypes: true });
1392
+ for (const entry of entries) {
1393
+ if (entry.isFile()) {
1394
+ const name = entry.name.toLowerCase();
1395
+ if (name.startsWith("firestore.") && name.endsWith(".rules")) {
1396
+ rules.push({
1397
+ name: entry.name,
1398
+ type: "firestore",
1399
+ path: join12(rulesPath, entry.name)
1400
+ });
1401
+ } else if (name.startsWith("storage.") && name.endsWith(".rules")) {
1402
+ rules.push({
1403
+ name: entry.name,
1404
+ type: "storage",
1405
+ path: join12(rulesPath, entry.name)
1406
+ });
1407
+ } else if (name === "firestore.indexes.json") {
1408
+ rules.push({
1409
+ name: entry.name,
1410
+ type: "firestoreIndexes",
1411
+ path: join12(rulesPath, entry.name)
1412
+ });
1413
+ }
1414
+ }
1415
+ }
1416
+ } catch {
1417
+ logger.debug(`Rules directory ${rulesPath} not found`);
1418
+ }
1419
+ return rules;
1420
+ }
1421
+
1422
+ // src/lib/commands/rules.ts
1423
+ var rulesCommand = new Command7("rules").description("Deploys Firestore, Storage rules, and indexes.").option("--flavor <flavor>", "The flavor to use for deployment.", "development").option("--verbose", "Whether to run the command with verbose logging.").option("--projectId <projectId>", "The Firebase project ID to deploy to.").option("--only <only>", 'Only deploy the specified components (e.g., "firestore,storage").').action(async (cliOptions) => {
1424
+ const options = await getOptions(cliOptions);
1425
+ if (!options.projectId) {
1426
+ logger.error(
1427
+ "Project ID not found. Please provide it using --projectId option or in firestack.json."
1428
+ );
1429
+ exitCode2(1);
1430
+ }
1431
+ const rulesDir = join13(cwdDir2(), options.rulesDirectory || "src/rules");
1432
+ const ruleFiles = await findRuleFiles(options.rulesDirectory || "src/rules");
1433
+ if (ruleFiles.length === 0) {
1434
+ logger.warn("No rule or index files found to deploy.");
1435
+ return;
1436
+ }
1437
+ logger.info(`Found ${ruleFiles.length} rule/index file(s) to deploy.`);
1438
+ const tempDir = join13(cwdDir2(), "dist", "rules-deploy");
1439
+ await mkdir3(join13(tempDir), { recursive: true });
1440
+ const firebaseConfig = {};
1441
+ for (const rule of ruleFiles) {
1442
+ if (rule.type === "firestore") {
1443
+ firebaseConfig.firestore = { rules: rule.name };
1444
+ } else if (rule.type === "storage") {
1445
+ firebaseConfig.storage = { rules: rule.name };
1446
+ } else if (rule.type === "firestoreIndexes") {
1447
+ firebaseConfig.firestore = {
1448
+ ...firebaseConfig.firestore,
1449
+ indexes: rule.name
1450
+ };
1451
+ }
1452
+ }
1453
+ const hasFirestore = ruleFiles.some(
1454
+ (r) => r.type === "firestore" || r.type === "firestoreIndexes"
1455
+ );
1456
+ const hasStorage = ruleFiles.some((r) => r.type === "storage");
1457
+ if (!hasFirestore && !hasStorage) {
1458
+ logger.warn("No firestore or storage rules found to deploy.");
1459
+ return;
1460
+ }
1461
+ writeFileSync2(join13(tempDir, "firebase.json"), JSON.stringify(firebaseConfig, null, 2));
1462
+ for (const rule of ruleFiles) {
1463
+ const sourcePath = join13(cwdDir2(), options.rulesDirectory || "src/rules", rule.name);
1464
+ const destPath = join13(tempDir, rule.name);
1465
+ if (existsSync5(sourcePath)) {
1466
+ copyFileSync2(sourcePath, destPath);
1467
+ logger.debug(`Copied ${rule.name} to ${tempDir}`);
1468
+ }
1469
+ }
1470
+ const deployTargets = [];
1471
+ if (hasFirestore) deployTargets.push("firestore");
1472
+ if (hasStorage) deployTargets.push("storage");
1473
+ const commandArgs = [
1474
+ "deploy",
1475
+ "--only",
1476
+ deployTargets.join(","),
1477
+ "--project",
1478
+ options.projectId
1479
+ ];
1480
+ logger.info(`Deploying: ${deployTargets.join(", ")}`);
1481
+ logger.debug(`> firebase ${commandArgs.join(" ")}`);
1482
+ try {
1483
+ await execa4("firebase", commandArgs, {
1484
+ cwd: tempDir,
1485
+ stdio: "inherit"
1486
+ });
1487
+ logger.info("Rules and indexes deployed successfully.");
1488
+ } catch {
1489
+ logger.error("Failed to deploy rules.");
1490
+ exitCode2(1);
1491
+ }
1492
+ });
1493
+
1494
+ // src/lib/commands/scripts.ts
1495
+ import { join as join15 } from "node:path";
1496
+ import { Command as Command8 } from "commander";
1497
+ import { execa as execa5 } from "execa";
1498
+ import prompts from "prompts";
1499
+
1500
+ // src/lib/utils/env.ts
1501
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
1502
+ import { join as join14 } from "node:path";
1503
+ async function getScriptEnvironment(flavor) {
1504
+ const envPath = join14(process.cwd(), `.env.${flavor}`);
1505
+ try {
1506
+ if (!existsSync6(envPath)) {
1507
+ logger.debug(`.env.${flavor} file not found, continuing without it.`);
1508
+ return {};
1509
+ }
1510
+ const envContent = readFileSync4(envPath, "utf-8");
1511
+ const env = {};
1512
+ let currentKey = "";
1513
+ let currentValue = "";
1514
+ for (const line of envContent.split("\n")) {
1515
+ const match = line.match(/^([A-Za-z0-9_]+)=(.*)$/);
1516
+ if (match) {
1517
+ if (currentKey) {
1518
+ env[currentKey] = currentValue;
1519
+ }
1520
+ currentKey = match[1];
1521
+ currentValue = match[2];
1522
+ } else {
1523
+ currentValue += `
1524
+ ${line}`;
1525
+ }
1526
+ }
1527
+ if (currentKey) {
1528
+ env[currentKey] = currentValue;
1529
+ }
1530
+ return env;
1531
+ } catch (e) {
1532
+ const error = e;
1533
+ if (error.code === "ENOENT") {
1534
+ logger.warn(`No .env.${flavor} file found, continuing without it.`);
1535
+ return {};
1536
+ }
1537
+ throw e;
1538
+ }
1539
+ }
1540
+
1541
+ // src/lib/commands/scripts.ts
1542
+ async function getOptions2(cliOptions) {
1543
+ const configPath = join15(cwdDir2(), "firestack.json");
1544
+ let config = {};
1545
+ try {
1546
+ const configContent = await readTextFile(configPath);
1547
+ config = JSON.parse(configContent);
1548
+ logger.debug(`Using configuration from ${configPath}`);
1549
+ } catch (e) {
1550
+ const error = e;
1551
+ if (error.code === "ENOENT") {
1552
+ logger.debug("firestack.json not found, using command-line options.");
1553
+ } else {
1554
+ logger.error(`Failed to read firestack.json at ${configPath}: ${error.message}`);
1555
+ exitCode2(1);
1556
+ }
1557
+ }
1558
+ const options = {
1559
+ ...cliOptions,
1560
+ scriptsDirectory: cliOptions.scriptsDirectory || config.scriptsDirectory || "scripts"
1561
+ };
1562
+ logger.setLogSeverity(cliOptions);
1563
+ logger.debug("Starting script command...");
1564
+ logger.debug("Options:", options);
1565
+ logger.debug("Current working directory:", cwdDir2());
1566
+ logger.debug("Scripts directory:", options.scriptsDirectory);
1567
+ return options;
1568
+ }
1569
+ async function findScripts(dir) {
1570
+ const scripts = [];
1571
+ try {
1572
+ const entries = await readDir2(dir);
1573
+ for (const entry of entries) {
1574
+ if (entry.isFile() && entry.name.endsWith(".ts") && entry.name !== "init.ts") {
1575
+ scripts.push(entry.name.replace(".ts", ""));
1576
+ }
1577
+ }
1578
+ } catch {
1579
+ logger.debug(`Scripts directory ${dir} not found`);
1580
+ }
1581
+ return scripts;
1582
+ }
1583
+ async function runScript(scriptName, options, projectRoot) {
1584
+ const scriptsPath = join15(cwdDir2(), options.scriptsDirectory);
1585
+ const scriptPath = join15(scriptsPath, `${scriptName}.ts`);
1586
+ const packageJsonPath = join15(projectRoot, "package.json");
1587
+ logger.debug("Script details:");
1588
+ logger.debug(" scriptsPath:", scriptsPath);
1589
+ logger.debug(" scriptPath:", scriptPath);
1590
+ logger.debug(" packageJsonPath:", packageJsonPath);
1591
+ const env = await getScriptEnvironment(options.flavor);
1592
+ logger.debug(`Running command: bun run "${scriptPath}"`);
1593
+ try {
1594
+ await execa5("bun", ["run", scriptPath], {
1595
+ cwd: cwdDir2(),
1596
+ env: { ...process.env, ...env },
1597
+ stdio: "inherit"
1598
+ });
1599
+ logger.info("\n\u2705 Script finished successfully!");
1600
+ } catch (error) {
1601
+ const err = error;
1602
+ logger.error(`
1603
+ \u274C Error running script. Exit code: ${err.exitCode ?? "unknown"}`);
1604
+ if (err.cause) {
1605
+ logger.error(`Cause: ${err.cause.message}`);
1606
+ }
1607
+ exitCode2(err.exitCode ?? 1);
1608
+ }
1609
+ }
1610
+ var scriptsCommand = new Command8("scripts").description("Run a script from the scripts directory.").option("--flavor <flavor>", "The flavor to use.", "development").option("--verbose", "Enable verbose logging.").option("--silent", "Disable logging.").argument("[scriptName]", "The name of the script to run.").action(async (scriptName, cliOptions) => {
1611
+ const options = await getOptions2(cliOptions);
1612
+ const scriptsPath = join15(cwdDir2(), options.scriptsDirectory);
1613
+ let selectedScriptName;
1614
+ if (scriptName) {
1615
+ selectedScriptName = scriptName;
1616
+ } else {
1617
+ const scriptFiles = await findScripts(scriptsPath);
1618
+ if (scriptFiles.length === 0) {
1619
+ logger.warn("No scripts found.");
1620
+ return;
1621
+ }
1622
+ if (scriptFiles.length === 1) {
1623
+ selectedScriptName = scriptFiles[0];
1624
+ logger.info(`Running single script: ${selectedScriptName}`);
1625
+ } else {
1626
+ const response = await prompts({
1627
+ type: "select",
1628
+ name: "script",
1629
+ message: "Please select a script to run:",
1630
+ choices: scriptFiles.map((script) => ({ title: script, value: script }))
1631
+ });
1632
+ if (!response.script) {
1633
+ logger.warn("No script selected. Exiting.");
1634
+ return;
1635
+ }
1636
+ selectedScriptName = response.script;
1637
+ }
1638
+ }
1639
+ const projectRoot = await findProjectRoot();
1640
+ await runScript(selectedScriptName, options, projectRoot);
1641
+ });
1642
+
1643
+ // src/main.ts
1644
+ var program = new Command9();
1645
+ program.name("firestack").version("1.0.0").description("CLI for building and deploying Firebase Cloud Functions.");
1646
+ program.addCommand(buildCommand);
1647
+ program.addCommand(deployCommand);
1648
+ program.addCommand(scriptsCommand);
1649
+ program.addCommand(deleteCommand);
1650
+ program.addCommand(emulateCommand);
1651
+ program.addCommand(rulesCommand);
1652
+ program.addCommand(logsCommand);
1653
+ program.parse(process.argv);
1654
+ /**
1655
+ * @file This file contains a function for running multiple functions in parallel.
1656
+ * @license MIT
1657
+ */
1658
+ /**
1659
+ * @file Utilities for deriving function names and document paths from file paths.
1660
+ * @license MIT
1661
+ */