firebase-tools 11.25.3 → 11.27.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.
@@ -17,7 +17,10 @@ marked_1.marked.setOptions({
17
17
  });
18
18
  exports.command = new command_1.Command("ext:dev:publish <extensionRef>")
19
19
  .description(`publish a new version of an extension`)
20
- .option(`-s, --stage <stage>`, `release stage (supports "rc", "alpha", "beta", and "stable")`)
20
+ .option(`-s, --stage <stage>`, `release stage (supports "alpha", "beta", "rc", and "stable")`)
21
+ .option(`--repo <repo>`, `Public Git repo URI (only required for first version from repo, cannot be changed)`)
22
+ .option(`--ref <ref>`, `commit hash, branch, or tag to build from the repo (defaults to HEAD)`)
23
+ .option(`--root <root>`, `root directory that contains this Extension (defaults to previous version's root or root of repo if none set)`)
21
24
  .withForce()
22
25
  .help("if you have not previously published a version of this extension, this will " +
23
26
  "create the extension. If you have previously published a version of this extension, this version must " +
@@ -32,15 +35,30 @@ exports.command = new command_1.Command("ext:dev:publish <extensionRef>")
32
35
  if (!publisherId || !extensionId) {
33
36
  throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
34
37
  }
35
- const extensionYamlDirectory = (0, localHelper_1.findExtensionYaml)(process.cwd());
36
- const res = await (0, extensionsHelper_1.publishExtensionVersionFromLocalSource)({
37
- publisherId,
38
- extensionId,
39
- rootDirectory: extensionYamlDirectory,
40
- nonInteractive: options.nonInteractive,
41
- force: options.force,
42
- stage: (_a = options.stage) !== null && _a !== void 0 ? _a : "stable",
43
- });
38
+ let res;
39
+ if (options.repo || options.root || options.ref) {
40
+ res = await (0, extensionsHelper_1.publishExtensionVersionFromRemoteRepo)({
41
+ publisherId,
42
+ extensionId,
43
+ repoUri: options.repo,
44
+ sourceRef: options.ref,
45
+ extensionRoot: options.root,
46
+ nonInteractive: options.nonInteractive,
47
+ force: options.force,
48
+ stage: options.stage,
49
+ });
50
+ }
51
+ else {
52
+ const extensionYamlDirectory = (0, localHelper_1.findExtensionYaml)(process.cwd());
53
+ res = await (0, extensionsHelper_1.publishExtensionVersionFromLocalSource)({
54
+ publisherId,
55
+ extensionId,
56
+ rootDirectory: extensionYamlDirectory,
57
+ nonInteractive: options.nonInteractive,
58
+ force: options.force,
59
+ stage: (_a = options.stage) !== null && _a !== void 0 ? _a : "stable",
60
+ });
61
+ }
44
62
  if (res) {
45
63
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, (0, marked_1.marked)(`[Install Link](${(0, publishHelpers_1.consoleInstallLink)(res.ref)})`));
46
64
  }
@@ -115,9 +115,8 @@ exports.command = new command_1.Command("ext:install [extensionName]")
115
115
  });
116
116
  async function infoExtensionVersion(args) {
117
117
  const ref = refs.parse(args.extensionName);
118
- const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref));
119
118
  await (0, displayExtensionInfo_1.displayExtInfo)(args.extensionName, ref.publisherId, args.extensionVersion.spec, true);
120
- await (0, warnings_1.displayWarningPrompts)(ref.publisherId, extension.registryLaunchStage, args.extensionVersion);
119
+ await (0, warnings_1.displayWarningPrompts)(ref.publisherId, args.extensionVersion);
121
120
  }
122
121
  async function installToManifest(options) {
123
122
  var _a, _b;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.memoryToGen2Cpu = exports.memoryToGen1Cpu = exports.memoryOptionDisplayName = exports.isValidMemoryOption = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
- const run = require("../../gcp/run");
7
6
  const utils = require("../../utils");
8
7
  const error_1 = require("../../error");
9
8
  const functional_1 = require("../../functional");
@@ -195,11 +194,8 @@ async function loadExistingBackend(ctx) {
195
194
  let gcfV2Results;
196
195
  try {
197
196
  gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
198
- const runResults = await Promise.all(gcfV2Results.functions.map((fn) => run.getService(fn.serviceConfig.service)));
199
- for (const [apiFunction, runService] of (0, functional_1.zip)(gcfV2Results.functions, runResults)) {
197
+ for (const apiFunction of gcfV2Results.functions) {
200
198
  const endpoint = gcfV2.endpointFromFunction(apiFunction);
201
- endpoint.concurrency = runService.spec.template.spec.containerConcurrency || 1;
202
- endpoint.cpu = +runService.spec.template.spec.containers[0].resources.limits.cpu;
203
199
  ctx.existingBackend.endpoints[endpoint.region] =
204
200
  ctx.existingBackend.endpoints[endpoint.region] || {};
205
201
  ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
@@ -219,7 +219,7 @@ class Fabricator {
219
219
  }
220
220
  }
221
221
  async createV2Function(endpoint) {
222
- var _a, _b, _c;
222
+ var _a, _b, _c, _d, _e;
223
223
  const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
224
224
  if (!storage) {
225
225
  logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
@@ -274,8 +274,13 @@ class Fabricator {
274
274
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
275
275
  })
276
276
  .catch(rethrowAs(endpoint, "create"));
277
- endpoint.uri = resultFunction.serviceConfig.uri;
278
- const serviceName = resultFunction.serviceConfig.service;
277
+ endpoint.uri = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.uri;
278
+ const serviceName = (_e = resultFunction.serviceConfig) === null || _e === void 0 ? void 0 : _e.service;
279
+ if (!serviceName) {
280
+ logger_1.logger.debug("Result function unexpectedly didn't have a service name.");
281
+ utils.logLabeledWarning("functions", "Updated function is not associated with a service. This deployment is in an unexpected state - please re-deploy your functions.");
282
+ return;
283
+ }
279
284
  if (backend.isHttpsTriggered(endpoint)) {
280
285
  const invoker = endpoint.httpsTrigger.invoker || ["public"];
281
286
  if (!invoker.includes("private")) {
@@ -346,7 +351,7 @@ class Fabricator {
346
351
  }
347
352
  }
348
353
  async updateV2Function(endpoint) {
349
- var _a, _b;
354
+ var _a, _b, _c, _d;
350
355
  const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
351
356
  if (!storage) {
352
357
  logger_1.logger.debug("Precondition failed. Cannot update a GCFv2 function without storage");
@@ -362,8 +367,13 @@ class Fabricator {
362
367
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
363
368
  })
364
369
  .catch(rethrowAs(endpoint, "update"));
365
- endpoint.uri = resultFunction.serviceConfig.uri;
366
- const serviceName = resultFunction.serviceConfig.service;
370
+ endpoint.uri = (_c = resultFunction.serviceConfig) === null || _c === void 0 ? void 0 : _c.uri;
371
+ const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
372
+ if (!serviceName) {
373
+ logger_1.logger.debug("Result function unexpectedly didn't have a service name.");
374
+ utils.logLabeledWarning("functions", "Updated function is not associated with a service. This deployment is in an unexpected state - please re-deploy your functions.");
375
+ return;
376
+ }
367
377
  let invoker;
368
378
  if (backend.isHttpsTriggered(endpoint)) {
369
379
  invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker;
@@ -82,15 +82,15 @@ class FunctionsEmulator {
82
82
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
83
83
  this.multicastTriggers = {};
84
84
  this.blockingFunctionsConfig = {};
85
+ this.debugMode = false;
85
86
  emulatorLogger_1.EmulatorLogger.verbosity = this.args.quiet ? emulatorLogger_1.Verbosity.QUIET : emulatorLogger_1.Verbosity.DEBUG;
86
87
  if (this.args.debugPort) {
87
88
  this.args.disabledRuntimeFeatures = this.args.disabledRuntimeFeatures || {};
88
89
  this.args.disabledRuntimeFeatures.timeout = true;
90
+ this.debugMode = true;
89
91
  }
90
92
  this.adminSdkConfig = Object.assign(Object.assign({}, this.args.adminSdkConfig), { projectId: this.args.projectId });
91
- const mode = this.args.debugPort
92
- ? types_1.FunctionsExecutionMode.SEQUENTIAL
93
- : types_1.FunctionsExecutionMode.AUTO;
93
+ const mode = this.debugMode ? types_1.FunctionsExecutionMode.SEQUENTIAL : types_1.FunctionsExecutionMode.AUTO;
94
94
  this.workerPools = {};
95
95
  for (const backend of this.args.emulatableBackends) {
96
96
  const pool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
@@ -204,6 +204,12 @@ class FunctionsEmulator {
204
204
  }
205
205
  }
206
206
  const worker = pool.getIdleWorker(trigger.id);
207
+ if (this.debugMode) {
208
+ await worker.sendDebugMsg({
209
+ functionTarget: trigger.entryPoint,
210
+ functionSignature: (0, functionsEmulatorShared_1.getSignatureType)(trigger),
211
+ });
212
+ }
207
213
  const reqBody = JSON.stringify(body);
208
214
  const headers = {
209
215
  "Content-Type": "application/json",
@@ -406,7 +412,7 @@ class FunctionsEmulator {
406
412
  this.logger.logLabeled("SUCCESS", `functions[${definition.id}]`, msg);
407
413
  }
408
414
  }
409
- if (this.args.debugPort) {
415
+ if (this.debugMode) {
410
416
  if (!((_a = emulatableBackend.runtime) === null || _a === void 0 ? void 0 : _a.startsWith("node"))) {
411
417
  this.logger.log("WARN", "--inspect-functions only supported for Node.js runtimes.");
412
418
  }
@@ -722,7 +728,7 @@ class FunctionsEmulator {
722
728
  emulatorInfos = emulatorInfos.concat(Object.values(this.args.remoteEmulators));
723
729
  }
724
730
  (0, env_1.setEnvVarsForEmulators)(envs, emulatorInfos);
725
- if (this.args.debugPort) {
731
+ if (this.debugMode) {
726
732
  envs["FUNCTION_DEBUG_MODE"] = "true";
727
733
  }
728
734
  return envs;
@@ -789,7 +795,7 @@ class FunctionsEmulator {
789
795
  }
790
796
  async startNode(backend, envs) {
791
797
  const args = [path.join(__dirname, "functionsEmulatorRuntime")];
792
- if (this.args.debugPort) {
798
+ if (this.debugMode) {
793
799
  if (process.env.FIREPIT_VERSION) {
794
800
  this.logger.log("WARN", `To enable function inspection, please run "npm i node@${semver.coerce(backend.runtime || "18.0.0")} --save-dev" in your functions directory`);
795
801
  }
@@ -824,7 +830,7 @@ class FunctionsEmulator {
824
830
  }
825
831
  async startPython(backend, envs) {
826
832
  const args = ["functions-framework"];
827
- if (this.args.debugPort) {
833
+ if (this.debugMode) {
828
834
  this.logger.log("WARN", "--inspect-functions not supported for Python functions. Ignored.");
829
835
  }
830
836
  const port = await portfinder.getPortPromise({
@@ -961,12 +967,13 @@ class FunctionsEmulator {
961
967
  return;
962
968
  }
963
969
  }
964
- const debugBundle = this.args.debugPort
965
- ? {
970
+ let debugBundle;
971
+ if (this.debugMode) {
972
+ debugBundle = {
966
973
  functionTarget: trigger.entryPoint,
967
974
  functionSignature: (0, functionsEmulatorShared_1.getSignatureType)(trigger),
968
- }
969
- : undefined;
975
+ };
976
+ }
970
977
  await pool.submitRequest(trigger.id, {
971
978
  method,
972
979
  path,
@@ -135,7 +135,7 @@ function createCloudEndpoints(emulator) {
135
135
  items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
136
136
  });
137
137
  });
138
- gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
138
+ gcloudStorageAPI.delete(["/b/:bucketId/o/:objectId", "/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
139
139
  try {
140
140
  await adminStorageLayer.deleteObject({
141
141
  bucketId: req.params.bucketId,
@@ -391,15 +391,18 @@ async function undeprecateExtensionVersion(extensionRef) {
391
391
  }
392
392
  }
393
393
  exports.undeprecateExtensionVersion = undeprecateExtensionVersion;
394
- async function publishExtensionVersion(extensionVersionRef, packageUri, extensionRoot) {
395
- const ref = refs.parse(extensionVersionRef);
394
+ async function publishExtensionVersion(args) {
395
+ var _a, _b, _c, _d;
396
+ const ref = refs.parse(args.extensionVersionRef);
396
397
  if (!ref.version) {
397
- throw new error_1.FirebaseError(`ExtensionVersion ref "${extensionVersionRef}" must supply a version.`);
398
+ throw new error_1.FirebaseError(`ExtensionVersion ref "${args.extensionVersionRef}" must supply a version.`);
398
399
  }
399
400
  const publishRes = await apiClient.post(`/${refs.toExtensionName(ref)}/versions:publish`, {
400
401
  versionId: ref.version,
401
- packageUri,
402
- extensionRoot: extensionRoot !== null && extensionRoot !== void 0 ? extensionRoot : "/",
402
+ packageUri: (_a = args.packageUri) !== null && _a !== void 0 ? _a : "",
403
+ extensionRoot: (_b = args.extensionRoot) !== null && _b !== void 0 ? _b : "/",
404
+ repoUri: (_c = args.repoUri) !== null && _c !== void 0 ? _c : "",
405
+ sourceRef: (_d = args.sourceRef) !== null && _d !== void 0 ? _d : "",
403
406
  });
404
407
  const pollRes = await operationPoller.pollOperation({
405
408
  apiOrigin: api_1.extensionsOrigin,
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canonicalizeRefInput = exports.diagnoseAndFixProject = exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.incrementPrereleaseVersion = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
3
+ exports.canonicalizeRefInput = exports.diagnoseAndFixProject = exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.publishExtensionVersionFromRemoteRepo = exports.incrementPrereleaseVersion = exports.ensureExtensionsApiEnabled = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
4
4
  const clc = require("colorette");
5
5
  const ora = require("ora");
6
6
  const semver = require("semver");
7
+ const tmp = require("tmp");
8
+ const fs = require("fs-extra");
9
+ const unzipper = require("unzipper");
10
+ const node_fetch_1 = require("node-fetch");
11
+ const path = require("path");
7
12
  const marked_1 = require("marked");
8
13
  const TerminalRenderer = require("marked-terminal");
9
14
  marked_1.marked.setOptions({
@@ -66,6 +71,11 @@ exports.AUTOPOULATED_PARAM_PLACEHOLDERS = {
66
71
  DATABASE_INSTANCE: "project-id-default-rtdb",
67
72
  DATABASE_URL: "https://project-id-default-rtdb.firebaseio.com",
68
73
  };
74
+ exports.resourceTypeToNiceName = {
75
+ "firebaseextensions.v1beta.function": "Cloud Function",
76
+ };
77
+ const repoRegex = new RegExp(`^https:\/\/github\.com\/[^\/]+\/[^\/]+$`);
78
+ const stageOptions = ["alpha", "beta", "rc", "stable"];
69
79
  function getDBInstanceFromURL(databaseUrl = "") {
70
80
  const instanceRegex = new RegExp("(?:https://)(.*)(?:.firebaseio.com)");
71
81
  const matches = instanceRegex.exec(databaseUrl);
@@ -263,6 +273,24 @@ async function promptForValidInstanceId(instanceId) {
263
273
  return newInstanceId;
264
274
  }
265
275
  exports.promptForValidInstanceId = promptForValidInstanceId;
276
+ async function promptForValidRepoURI() {
277
+ let repoIsValid = false;
278
+ let extensionRoot = "";
279
+ while (!repoIsValid) {
280
+ extensionRoot = await (0, prompt_1.promptOnce)({
281
+ type: "input",
282
+ message: "Enter the GitHub repo URI where this Extension's source code is located:",
283
+ });
284
+ if (!repoRegex.test(extensionRoot)) {
285
+ logger_1.logger.info("Repo URI must follow this format: https://github.com/<user>/<repo>");
286
+ }
287
+ else {
288
+ repoIsValid = true;
289
+ }
290
+ }
291
+ return extensionRoot;
292
+ }
293
+ exports.promptForValidRepoURI = promptForValidRepoURI;
266
294
  async function ensureExtensionsApiEnabled(options) {
267
295
  const projectId = (0, projectUtils_1.getProjectId)(options);
268
296
  if (!projectId) {
@@ -281,7 +309,6 @@ async function archiveAndUploadSource(extPath, bucketName) {
281
309
  }
282
310
  async function incrementPrereleaseVersion(ref, extensionVersion, stage) {
283
311
  var _a;
284
- const stageOptions = ["stable", "alpha", "beta", "rc"];
285
312
  if (!stageOptions.includes(stage)) {
286
313
  throw new error_1.FirebaseError(`--stage flag only supports the following values: ${stageOptions}`);
287
314
  }
@@ -306,7 +333,8 @@ async function incrementPrereleaseVersion(ref, extensionVersion, stage) {
306
333
  return extensionVersion;
307
334
  }
308
335
  exports.incrementPrereleaseVersion = incrementPrereleaseVersion;
309
- async function publishExtensionVersionFromLocalSource(args) {
336
+ async function validateExtensionSpec(args) {
337
+ const extensionRef = `${args.publisherId}/${args.extensionId}`;
310
338
  const extensionSpec = await (0, localHelper_1.getLocalExtensionSpec)(args.rootDirectory);
311
339
  if (extensionSpec.name !== args.extensionId) {
312
340
  throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
@@ -314,13 +342,7 @@ async function publishExtensionVersionFromLocalSource(args) {
314
342
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
315
343
  subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
316
344
  validateSpec(subbedSpec);
317
- extensionSpec.version = await incrementPrereleaseVersion(`${args.publisherId}/${args.extensionId}`, extensionSpec.version, args.stage);
318
- let extension;
319
- try {
320
- extension = await (0, extensionsApi_1.getExtension)(`${args.publisherId}/${args.extensionId}`);
321
- }
322
- catch (err) {
323
- }
345
+ extensionSpec.version = await incrementPrereleaseVersion(extensionRef, extensionSpec.version, args.stage);
324
346
  let notes;
325
347
  try {
326
348
  const changes = (0, change_log_1.getLocalChangelog)(args.rootDirectory);
@@ -331,30 +353,197 @@ async function publishExtensionVersionFromLocalSource(args) {
331
353
  "Please create one and add an entry for this version. " +
332
354
  (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
333
355
  }
334
- if (!notes && !semver.prerelease(extensionSpec.version) && extension) {
356
+ if (!notes && !semver.prerelease(extensionSpec.version) && args.latestVersion) {
335
357
  throw new error_1.FirebaseError(`No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` +
336
358
  "Please add one so users know what has changed in this version. " +
337
359
  (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
338
360
  }
339
- displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes);
340
- if (!(await confirm({
361
+ if (args.latestVersion) {
362
+ if (semver.lt(extensionSpec.version, args.latestVersion)) {
363
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(args.latestVersion)}) for the extension '${clc.bold(extensionRef)}'. Please make sure this version is greater than the current version (${clc.bold(args.latestVersion)}) inside of extension.yaml.\n`, { exit: 104 });
364
+ }
365
+ else if (semver.eq(extensionSpec.version, args.latestVersion)) {
366
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for Extension '${clc.bold(extensionRef)}'. Please increment the version inside of extension.yaml.\n`, { exit: 103 });
367
+ }
368
+ }
369
+ return { extensionSpec, notes };
370
+ }
371
+ async function publishExtensionVersionFromRemoteRepo(args) {
372
+ const extensionRef = `${args.publisherId}/${args.extensionId}`;
373
+ let extension;
374
+ try {
375
+ extension = await (0, extensionsApi_1.getExtension)(extensionRef);
376
+ }
377
+ catch (err) {
378
+ }
379
+ if (args.repoUri && !repoRegex.test(args.repoUri)) {
380
+ throw new error_1.FirebaseError("Repo URI must follow this format: https://github.com/<user>/<repo>");
381
+ }
382
+ let repoUri = args.repoUri || (extension === null || extension === void 0 ? void 0 : extension.repoUri);
383
+ if (!repoUri) {
384
+ if (!args.nonInteractive) {
385
+ repoUri = await promptForValidRepoURI();
386
+ }
387
+ else {
388
+ throw new error_1.FirebaseError("Repo URI is required but not currently set.");
389
+ }
390
+ }
391
+ if ((extension === null || extension === void 0 ? void 0 : extension.repoUri) && extension.repoUri !== repoUri) {
392
+ throw new error_1.FirebaseError(`Repo URI '${clc.bold(args.repoUri)}' does not match repo URI '${clc.bold(extension.repoUri)}' already associated with Extension ${clc.bold(extensionRef)}. Repo URI cannot be changed.`);
393
+ }
394
+ if (!(extension === null || extension === void 0 ? void 0 : extension.repoUri)) {
395
+ logger_1.logger.info(`\n${clc.red("Warning:")} You are about to associate repo URI ${clc.bold(repoUri)} with Extension ${clc.bold(extensionRef)}. This cannot be changed. All future verifiable versions must be published from this repo. ` +
396
+ `You can continue publishing unverifiable versions from local source.`);
397
+ const confirmed = await confirm({
398
+ nonInteractive: args.nonInteractive,
399
+ force: args.force,
400
+ default: false,
401
+ });
402
+ if (!confirmed) {
403
+ return;
404
+ }
405
+ }
406
+ else {
407
+ logger_1.logger.info(`Extension ${clc.bold(extensionRef)} is published from ${clc.bold(extension === null || extension === void 0 ? void 0 : extension.repoUri)}.`);
408
+ }
409
+ let extensionRoot = args.extensionRoot;
410
+ let defaultRoot = "/";
411
+ if (!extensionRoot) {
412
+ if (extension) {
413
+ try {
414
+ const extensionVersionRef = `${extensionRef}@${extension.latestVersion}`;
415
+ const extensionVersion = await (0, extensionsApi_1.getExtensionVersion)(extensionVersionRef);
416
+ if (extensionVersion.extensionRoot) {
417
+ defaultRoot = extensionVersion.extensionRoot;
418
+ }
419
+ }
420
+ catch (err) {
421
+ }
422
+ }
423
+ extensionRoot = defaultRoot;
424
+ if (!args.nonInteractive) {
425
+ extensionRoot = await (0, prompt_1.promptOnce)({
426
+ type: "input",
427
+ message: "Enter this Extension's root directory in the repo (defaults to previous root if set):",
428
+ default: defaultRoot,
429
+ });
430
+ }
431
+ }
432
+ let sourceRef = args.sourceRef;
433
+ const defaultSourceRef = "HEAD";
434
+ if (!sourceRef) {
435
+ if (!args.nonInteractive) {
436
+ sourceRef = await (0, prompt_1.promptOnce)({
437
+ type: "input",
438
+ message: "Enter the commit hash, branch, or tag name to build from in the repo:",
439
+ default: defaultSourceRef,
440
+ });
441
+ }
442
+ else {
443
+ sourceRef = defaultSourceRef;
444
+ }
445
+ }
446
+ let stage = args.stage;
447
+ const defaultStage = "rc";
448
+ if (!stage) {
449
+ if (!args.nonInteractive) {
450
+ stage = await (0, prompt_1.promptOnce)({
451
+ type: "list",
452
+ message: "Choose the release stage (pre-release annotations will be auto-incremented):",
453
+ choices: stageOptions,
454
+ default: defaultStage,
455
+ });
456
+ }
457
+ else {
458
+ stage = defaultStage;
459
+ }
460
+ }
461
+ logger_1.logger.info("Downloading and validating source code...");
462
+ const archiveUri = `${repoUri}/archive/${sourceRef}.zip`;
463
+ const tempDirectory = tmp.dirSync({ unsafeCleanup: true });
464
+ try {
465
+ const response = await (0, node_fetch_1.default)(archiveUri);
466
+ if (response.ok) {
467
+ await response.body.pipe(unzipper.Extract({ path: tempDirectory.name })).promise();
468
+ }
469
+ }
470
+ catch (err) {
471
+ throw new error_1.FirebaseError(`Failed to fetch Extension archive ${archiveUri}. Please check the repo URI and source ref. ${err}`);
472
+ }
473
+ const archiveName = fs.readdirSync(tempDirectory.name)[0];
474
+ const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot);
475
+ try {
476
+ (0, localHelper_1.readFile)(path.resolve(rootDirectory, localHelper_1.EXTENSIONS_SPEC_FILE));
477
+ }
478
+ catch (err) {
479
+ throw new error_1.FirebaseError(`Failed to find ${clc.bold(localHelper_1.EXTENSIONS_SPEC_FILE)} in directory ${clc.bold(extensionRoot)}. Please verify the root and try again.`);
480
+ }
481
+ const { extensionSpec, notes } = await validateExtensionSpec({
482
+ publisherId: args.publisherId,
483
+ extensionId: args.extensionId,
484
+ rootDirectory: rootDirectory,
485
+ latestVersion: extension === null || extension === void 0 ? void 0 : extension.latestVersion,
486
+ stage: stage,
487
+ });
488
+ const sourceUri = path.join(repoUri, "tree", sourceRef, extensionRoot);
489
+ displayReleaseNotes(extensionRef, extensionSpec.version, notes, sourceUri);
490
+ const confirmed = await confirm({
341
491
  nonInteractive: args.nonInteractive,
342
492
  force: args.force,
343
493
  default: false,
344
- }))) {
494
+ });
495
+ if (!confirmed) {
345
496
  return;
346
497
  }
347
- if (extension &&
348
- extension.latestVersion &&
349
- semver.lt(extensionSpec.version, extension.latestVersion)) {
350
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`, { exit: 104 });
498
+ const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`;
499
+ const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`);
500
+ let res;
501
+ try {
502
+ publishSpinner.start();
503
+ res = await (0, extensionsApi_1.publishExtensionVersion)({
504
+ extensionVersionRef,
505
+ packageUri: "",
506
+ extensionRoot,
507
+ repoUri,
508
+ sourceRef: args.sourceRef,
509
+ });
510
+ publishSpinner.succeed(` Successfully published ${clc.bold(extensionRef)}`);
351
511
  }
352
- else if (extension &&
353
- extension.latestVersion &&
354
- semver.eq(extensionSpec.version, extension.latestVersion)) {
355
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`, { exit: 103 });
512
+ catch (err) {
513
+ publishSpinner.fail();
514
+ if (err.status === 404) {
515
+ throw getMissingPublisherError(args.publisherId);
516
+ }
517
+ throw err;
518
+ }
519
+ return res;
520
+ }
521
+ exports.publishExtensionVersionFromRemoteRepo = publishExtensionVersionFromRemoteRepo;
522
+ async function publishExtensionVersionFromLocalSource(args) {
523
+ let extension;
524
+ try {
525
+ extension = await (0, extensionsApi_1.getExtension)(`${args.publisherId}/${args.extensionId}`);
356
526
  }
357
- const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
527
+ catch (err) {
528
+ }
529
+ const { extensionSpec, notes } = await validateExtensionSpec({
530
+ publisherId: args.publisherId,
531
+ extensionId: args.extensionId,
532
+ rootDirectory: args.rootDirectory,
533
+ latestVersion: extension === null || extension === void 0 ? void 0 : extension.latestVersion,
534
+ stage: args.stage,
535
+ });
536
+ const extensionRef = `${args.publisherId}/${args.extensionId}`;
537
+ displayReleaseNotes(extensionRef, extensionSpec.version, notes);
538
+ const confirmed = await confirm({
539
+ nonInteractive: args.nonInteractive,
540
+ force: args.force,
541
+ default: false,
542
+ });
543
+ if (!confirmed) {
544
+ return;
545
+ }
546
+ const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`;
358
547
  let packageUri;
359
548
  let objectPath = "";
360
549
  const uploadSpinner = ora(" Archiving and uploading extension source code");
@@ -370,17 +559,17 @@ async function publishExtensionVersionFromLocalSource(args) {
370
559
  original: err,
371
560
  });
372
561
  }
373
- const publishSpinner = ora(`Publishing ${clc.bold(ref)}`);
562
+ const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`);
374
563
  let res;
375
564
  try {
376
565
  publishSpinner.start();
377
- res = await (0, extensionsApi_1.publishExtensionVersion)(ref, packageUri);
378
- publishSpinner.succeed(` Successfully published ${clc.bold(ref)}`);
566
+ res = await (0, extensionsApi_1.publishExtensionVersion)({ extensionVersionRef, packageUri });
567
+ publishSpinner.succeed(` Successfully published ${clc.bold(extensionVersionRef)}`);
379
568
  }
380
569
  catch (err) {
381
570
  publishSpinner.fail();
382
571
  if (err.status === 404) {
383
- throw new error_1.FirebaseError((0, marked_1.marked)(`Couldn't find publisher ID '${clc.bold(args.publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
572
+ throw getMissingPublisherError(args.publisherId);
384
573
  }
385
574
  throw err;
386
575
  }
@@ -388,6 +577,9 @@ async function publishExtensionVersionFromLocalSource(args) {
388
577
  return res;
389
578
  }
390
579
  exports.publishExtensionVersionFromLocalSource = publishExtensionVersionFromLocalSource;
580
+ function getMissingPublisherError(publisherId) {
581
+ return new error_1.FirebaseError((0, marked_1.marked)(`Couldn't find publisher ID '${clc.bold(publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
582
+ }
391
583
  async function createSourceFromLocation(projectId, sourceUri) {
392
584
  const extensionRoot = "/";
393
585
  let packageUri;
@@ -431,12 +623,18 @@ function getPublisherProjectFromName(publisherName) {
431
623
  throw new error_1.FirebaseError(`Could not find publisher with name '${publisherName}'.`);
432
624
  }
433
625
  exports.getPublisherProjectFromName = getPublisherProjectFromName;
434
- function displayReleaseNotes(publisherId, extensionId, versionId, releaseNotes) {
626
+ function displayReleaseNotes(extensionRef, versionId, releaseNotes, sourceUri) {
627
+ const source = sourceUri || "local source";
435
628
  const releaseNotesMessage = releaseNotes
436
- ? ` Release notes for this version:\n${(0, marked_1.marked)(releaseNotes)}\n`
437
- : "\n";
438
- const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.${releaseNotesMessage}` +
439
- "Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n";
629
+ ? `${clc.bold("Release notes:")}\n${(0, marked_1.marked)(releaseNotes)}`
630
+ : "";
631
+ const metadataMessage = `${clc.bold("Extension:")} ${extensionRef}\n` +
632
+ `${clc.bold("Version:")} ${clc.bold(clc.green(versionId))}\n` +
633
+ `${clc.bold("Source:")} ${source}\n`;
634
+ const message = `\nYou are about to publish a new version to Firebase's registry of Extensions.\n\n` +
635
+ metadataMessage +
636
+ releaseNotesMessage +
637
+ `\nOnce an Extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n`;
440
638
  logger_1.logger.info(message);
441
639
  }
442
640
  exports.displayReleaseNotes = displayReleaseNotes;
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isLocalExtension = exports.readFile = exports.findExtensionYaml = exports.getLocalExtensionSpec = void 0;
3
+ exports.isLocalExtension = exports.readFile = exports.findExtensionYaml = exports.getLocalExtensionSpec = exports.EXTENSIONS_SPEC_FILE = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
6
  const yaml = require("js-yaml");
7
7
  const fsutils_1 = require("../fsutils");
8
8
  const error_1 = require("../error");
9
9
  const logger_1 = require("../logger");
10
- const EXTENSIONS_SPEC_FILE = "extension.yaml";
10
+ exports.EXTENSIONS_SPEC_FILE = "extension.yaml";
11
11
  const EXTENSIONS_PREINSTALL_FILE = "PREINSTALL.md";
12
12
  async function getLocalExtensionSpec(directory) {
13
- const spec = await parseYAML(readFile(path.resolve(directory, EXTENSIONS_SPEC_FILE)));
13
+ const spec = await parseYAML(readFile(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE)));
14
14
  try {
15
15
  const preinstall = readFile(path.resolve(directory, EXTENSIONS_PREINSTALL_FILE));
16
16
  spec.preinstallContent = preinstall;
@@ -22,7 +22,7 @@ async function getLocalExtensionSpec(directory) {
22
22
  }
23
23
  exports.getLocalExtensionSpec = getLocalExtensionSpec;
24
24
  function findExtensionYaml(directory) {
25
- while (!(0, fsutils_1.fileExistsSync)(path.resolve(directory, EXTENSIONS_SPEC_FILE))) {
25
+ while (!(0, fsutils_1.fileExistsSync)(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE))) {
26
26
  const parentDir = path.dirname(directory);
27
27
  if (parentDir === directory) {
28
28
  throw new error_1.FirebaseError("Couldn't find an extension.yaml file. Check that you are in the root directory of your extension.");