firebase-tools 11.29.1 → 12.0.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.
Files changed (95) hide show
  1. package/lib/api.js +4 -2
  2. package/lib/commands/database-import.js +2 -2
  3. package/lib/commands/ext-configure.js +2 -1
  4. package/lib/commands/ext-dev-deprecate.js +24 -20
  5. package/lib/commands/ext-dev-list.js +12 -11
  6. package/lib/commands/ext-dev-publish.js +13 -47
  7. package/lib/commands/ext-dev-register.js +8 -5
  8. package/lib/commands/ext-dev-undeprecate.js +4 -4
  9. package/lib/commands/ext-dev-upload.js +88 -0
  10. package/lib/commands/ext-dev-usage.js +3 -3
  11. package/lib/commands/ext-install.js +5 -10
  12. package/lib/commands/ext-uninstall.js +0 -1
  13. package/lib/commands/ext-update.js +4 -10
  14. package/lib/commands/hosting-channel-deploy.js +3 -0
  15. package/lib/commands/index.js +9 -19
  16. package/lib/database/import.js +113 -18
  17. package/lib/deploy/extensions/planner.js +13 -7
  18. package/lib/deploy/extensions/prepare.js +16 -32
  19. package/lib/deploy/functions/ensure.js +7 -1
  20. package/lib/deploy/functions/release/fabricator.js +2 -0
  21. package/lib/deploy/functions/runtimes/discovery/index.js +1 -1
  22. package/lib/deploy/functions/runtimes/index.js +11 -3
  23. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  24. package/lib/deploy/functions/runtimes/python/index.js +41 -13
  25. package/lib/deploy/hosting/convertConfig.js +8 -4
  26. package/lib/deploy/hosting/prepare.js +64 -6
  27. package/lib/deploy/index.js +24 -8
  28. package/lib/emulator/adminSdkConfig.js +8 -0
  29. package/lib/emulator/controller.js +7 -9
  30. package/lib/emulator/download.js +3 -12
  31. package/lib/emulator/downloadableEmulators.js +5 -5
  32. package/lib/emulator/functionsEmulator.js +57 -7
  33. package/lib/emulator/functionsEmulatorRuntime.js +4 -1
  34. package/lib/emulator/functionsEmulatorShared.js +1 -0
  35. package/lib/emulator/functionsRuntimeWorker.js +12 -4
  36. package/lib/emulator/storage/rules/config.js +17 -7
  37. package/lib/experiments.js +22 -8
  38. package/lib/extensions/extensionsApi.js +24 -151
  39. package/lib/extensions/extensionsHelper.js +283 -146
  40. package/lib/extensions/manifest.js +1 -8
  41. package/lib/extensions/publisherApi.js +215 -0
  42. package/lib/extensions/refs.js +1 -1
  43. package/lib/extensions/resolveSource.js +1 -18
  44. package/lib/extensions/tos.js +78 -0
  45. package/lib/extensions/warnings.js +21 -41
  46. package/lib/frameworks/angular/index.js +74 -192
  47. package/lib/frameworks/angular/interfaces.js +2 -0
  48. package/lib/frameworks/angular/utils.js +274 -0
  49. package/lib/frameworks/astro/index.js +3 -4
  50. package/lib/frameworks/constants.js +45 -0
  51. package/lib/frameworks/express/index.js +3 -2
  52. package/lib/frameworks/flutter/index.js +39 -0
  53. package/lib/frameworks/flutter/utils.js +11 -0
  54. package/lib/frameworks/index.js +104 -145
  55. package/lib/frameworks/interfaces.js +2 -0
  56. package/lib/frameworks/next/constants.js +2 -1
  57. package/lib/frameworks/next/index.js +197 -114
  58. package/lib/frameworks/next/utils.js +97 -15
  59. package/lib/frameworks/nuxt/index.js +4 -5
  60. package/lib/frameworks/nuxt/utils.js +2 -2
  61. package/lib/frameworks/nuxt2/index.js +5 -5
  62. package/lib/frameworks/utils.js +108 -1
  63. package/lib/frameworks/vite/index.js +5 -6
  64. package/lib/functions/ensureTargeted.js +4 -4
  65. package/lib/functions/python.js +12 -5
  66. package/lib/gcp/resourceManager.js +1 -0
  67. package/lib/hosting/api.js +32 -1
  68. package/lib/hosting/config.js +4 -8
  69. package/lib/init/features/functions/index.js +4 -7
  70. package/lib/init/features/hosting/github.js +7 -2
  71. package/lib/init/features/hosting/index.js +3 -2
  72. package/lib/serve/index.js +2 -1
  73. package/lib/unzip.js +126 -0
  74. package/package.json +2 -3
  75. package/schema/firebase-config.json +1 -1
  76. package/templates/extensions/POSTINSTALL.md +2 -2
  77. package/templates/extensions/PREINSTALL.md +1 -1
  78. package/templates/extensions/extension.yaml +10 -6
  79. package/templates/extensions/javascript/WELCOME.md +1 -1
  80. package/templates/extensions/typescript/WELCOME.md +1 -1
  81. package/templates/extensions/typescript/index.ts +1 -1
  82. package/templates/init/functions/javascript/index.js +16 -6
  83. package/templates/init/functions/javascript/package.lint.json +4 -4
  84. package/templates/init/functions/javascript/package.nolint.json +4 -4
  85. package/templates/init/functions/python/requirements.txt +1 -1
  86. package/templates/init/functions/typescript/index.ts +16 -6
  87. package/templates/init/functions/typescript/package.lint.json +4 -4
  88. package/templates/init/functions/typescript/package.nolint.json +4 -4
  89. package/lib/commands/ext-dev-emulators-exec.js +0 -27
  90. package/lib/commands/ext-dev-emulators-start.js +0 -24
  91. package/lib/commands/ext-dev-extension-delete.js +0 -45
  92. package/lib/commands/ext-dev-unpublish.js +0 -49
  93. package/lib/commands/ext-sources-create.js +0 -24
  94. package/lib/extensions/askUserForConsent.js +0 -33
  95. package/npm-shrinkwrap.json +0 -12649
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canonicalizeRefInput = exports.diagnoseAndFixProject = 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;
3
+ exports.canonicalizeRefInput = exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.promptForValidExtensionRoot = 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
7
  const tmp = require("tmp");
8
8
  const fs = require("fs-extra");
9
- const unzipper = require("unzipper");
10
9
  const node_fetch_1 = require("node-fetch");
11
10
  const path = require("path");
12
11
  const marked_1 = require("marked");
12
+ const unzip_1 = require("./../unzip");
13
13
  const TerminalRenderer = require("marked-terminal");
14
14
  marked_1.marked.setOptions({
15
15
  renderer: new TerminalRenderer(),
@@ -27,6 +27,7 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
27
27
  const storage_1 = require("../gcp/storage");
28
28
  const projectUtils_1 = require("../projectUtils");
29
29
  const extensionsApi_1 = require("./extensionsApi");
30
+ const publisherApi_1 = require("./publisherApi");
30
31
  const refs = require("./refs");
31
32
  const localHelper_1 = require("./localHelper");
32
33
  const prompt_1 = require("../prompt");
@@ -75,7 +76,7 @@ exports.resourceTypeToNiceName = {
75
76
  "firebaseextensions.v1beta.function": "Cloud Function",
76
77
  };
77
78
  const repoRegex = new RegExp(`^https:\/\/github\.com\/[^\/]+\/[^\/]+$`);
78
- const stageOptions = ["alpha", "beta", "rc", "stable"];
79
+ const stageOptions = ["rc", "alpha", "beta", "stable"];
79
80
  function getDBInstanceFromURL(databaseUrl = "") {
80
81
  const instanceRegex = new RegExp("(?:https://)(.*)(?:.firebaseio.com)");
81
82
  const matches = instanceRegex.exec(databaseUrl);
@@ -175,6 +176,15 @@ function validateSpec(spec) {
175
176
  if (!spec.version) {
176
177
  errors.push("extension.yaml is missing required field: version");
177
178
  }
179
+ else if (!semver.valid(spec.version)) {
180
+ errors.push(`version ${spec.version} in extension.yaml is not a valid semver`);
181
+ }
182
+ else {
183
+ const version = semver.parse(spec.version);
184
+ if (version.prerelease.length > 0 || version.build.length > 0) {
185
+ errors.push("version field in extension.yaml does not support pre-release annotations; instead, set a pre-release stage using the --stage flag");
186
+ }
187
+ }
178
188
  if (!spec.license) {
179
189
  errors.push("extension.yaml is missing required field: license");
180
190
  }
@@ -279,7 +289,7 @@ async function promptForValidRepoURI() {
279
289
  while (!repoIsValid) {
280
290
  extensionRoot = await (0, prompt_1.promptOnce)({
281
291
  type: "input",
282
- message: "Enter the GitHub repo URI where this Extension's source code is located:",
292
+ message: "Enter the GitHub repo URI where this extension's source code is located:",
283
293
  });
284
294
  if (!repoRegex.test(extensionRoot)) {
285
295
  logger_1.logger.info("Repo URI must follow this format: https://github.com/<user>/<repo>");
@@ -291,6 +301,60 @@ async function promptForValidRepoURI() {
291
301
  return extensionRoot;
292
302
  }
293
303
  exports.promptForValidRepoURI = promptForValidRepoURI;
304
+ async function promptForValidExtensionRoot(defaultRoot) {
305
+ let rootIsValid = false;
306
+ let extensionRoot = "";
307
+ while (!rootIsValid) {
308
+ extensionRoot = await (0, prompt_1.promptOnce)({
309
+ type: "input",
310
+ message: "Enter this extension's root directory in the repo (defaults to previous root if set):",
311
+ default: defaultRoot,
312
+ });
313
+ rootIsValid = true;
314
+ }
315
+ return extensionRoot;
316
+ }
317
+ exports.promptForValidExtensionRoot = promptForValidExtensionRoot;
318
+ async function promptForReleaseStage(args) {
319
+ let stage = "rc";
320
+ if (!args.nonInteractive) {
321
+ const choices = [
322
+ { name: `Release candidate (${args.versionByStage.get("rc")})`, value: "rc" },
323
+ { name: `Alpha (${args.versionByStage.get("alpha")})`, value: "alpha" },
324
+ { name: `Beta (${args.versionByStage.get("beta")})`, value: "beta" },
325
+ ];
326
+ if (args.allowStable) {
327
+ const stableChoice = {
328
+ name: `Stable (${args.versionByStage.get("stable")}${args.autoReview ? ", automatically sent for review" : ""})`,
329
+ value: "stable",
330
+ };
331
+ choices.push(stableChoice);
332
+ }
333
+ stage = await (0, prompt_1.promptOnce)({
334
+ type: "list",
335
+ message: "Choose the release stage:",
336
+ choices: choices,
337
+ default: stage,
338
+ });
339
+ if (stage === "stable" && !args.hasVersions) {
340
+ logger_1.logger.info(`${clc.bold(clc.yellow("Warning:"))} It's highly recommended to first upload a pre-release version before choosing stable.`);
341
+ const confirmed = await (0, prompt_1.confirm)({
342
+ nonInteractive: args.nonInteractive,
343
+ force: args.force,
344
+ default: false,
345
+ });
346
+ if (!confirmed) {
347
+ stage = await (0, prompt_1.promptOnce)({
348
+ type: "list",
349
+ message: "Choose the release stage:",
350
+ choices: choices,
351
+ default: stage,
352
+ });
353
+ }
354
+ }
355
+ }
356
+ return stage;
357
+ }
294
358
  async function ensureExtensionsApiEnabled(options) {
295
359
  const projectId = (0, projectUtils_1.getProjectId)(options);
296
360
  if (!projectId) {
@@ -299,6 +363,14 @@ async function ensureExtensionsApiEnabled(options) {
299
363
  return await (0, ensureApiEnabled_1.ensure)(projectId, "firebaseextensions.googleapis.com", "extensions", options.markdown);
300
364
  }
301
365
  exports.ensureExtensionsApiEnabled = ensureExtensionsApiEnabled;
366
+ async function ensureExtensionsPublisherApiEnabled(options) {
367
+ const projectId = (0, projectUtils_1.getProjectId)(options);
368
+ if (!projectId) {
369
+ return;
370
+ }
371
+ return await (0, ensureApiEnabled_1.ensure)(projectId, "firebaseextensionspublisher.googleapis.com", "extensions", options.markdown);
372
+ }
373
+ exports.ensureExtensionsPublisherApiEnabled = ensureExtensionsPublisherApiEnabled;
302
374
  async function archiveAndUploadSource(extPath, bucketName) {
303
375
  const zippedSource = await (0, archiveDirectory_1.archiveDirectory)(extPath, {
304
376
  type: "zip",
@@ -307,75 +379,150 @@ async function archiveAndUploadSource(extPath, bucketName) {
307
379
  const res = await (0, storage_1.uploadObject)(zippedSource, bucketName);
308
380
  return `/${res.bucket}/${res.object}`;
309
381
  }
310
- async function incrementPrereleaseVersion(ref, extensionVersion, stage) {
311
- var _a;
312
- if (!stageOptions.includes(stage)) {
313
- throw new error_1.FirebaseError(`--stage flag only supports the following values: ${stageOptions}`);
382
+ async function getNextVersionByStage(extensionRef, newVersion) {
383
+ let extensionVersions = [];
384
+ try {
385
+ extensionVersions = await (0, publisherApi_1.listExtensionVersions)(extensionRef, `id="${newVersion}"`, true);
314
386
  }
315
- if (stage !== "stable") {
316
- const version = semver.parse(extensionVersion);
317
- if (version.prerelease.length > 0 || version.build.length > 0) {
318
- throw new error_1.FirebaseError(`Cannot combine the --stage flag with a version with a prerelease annotation in extension.yaml.`);
319
- }
320
- let extensionVersions = [];
321
- try {
322
- extensionVersions = await (0, extensionsApi_1.listExtensionVersions)(ref, `id="${version.version}"`, true);
387
+ catch (err) {
388
+ }
389
+ const versionByStage = new Map(["rc", "alpha", "beta"].map((stage) => [
390
+ stage,
391
+ semver.inc(`${newVersion}-${stage}`, "prerelease", undefined, stage),
392
+ ]));
393
+ for (const extensionVersion of extensionVersions) {
394
+ const version = semver.parse(extensionVersion.spec.version);
395
+ if (!version.prerelease.length) {
396
+ continue;
323
397
  }
324
- catch (e) {
398
+ const prerelease = semver.prerelease(version)[0];
399
+ const stage = prerelease.split(".")[0];
400
+ if (versionByStage.has(stage) && semver.gte(version, versionByStage.get(stage))) {
401
+ versionByStage.set(stage, semver.inc(version, "prerelease", undefined, stage));
325
402
  }
326
- const latestVersion = (_a = extensionVersions
327
- .map((version) => semver.parse(version.spec.version))
328
- .filter((version) => version.prerelease.length > 0 && version.prerelease[0] === stage)
329
- .sort((v1, v2) => semver.compare(v1, v2))
330
- .pop()) !== null && _a !== void 0 ? _a : `${version}-${stage}`;
331
- return semver.inc(latestVersion, "prerelease", undefined, stage);
332
403
  }
333
- return extensionVersion;
404
+ versionByStage.set("stable", newVersion);
405
+ return { versionByStage, hasVersions: extensionVersions.length > 0 };
334
406
  }
335
- exports.incrementPrereleaseVersion = incrementPrereleaseVersion;
336
- async function validateExtensionSpec(args) {
337
- const extensionRef = `${args.publisherId}/${args.extensionId}`;
338
- const extensionSpec = await (0, localHelper_1.getLocalExtensionSpec)(args.rootDirectory);
339
- if (extensionSpec.name !== args.extensionId) {
340
- throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
407
+ exports.getNextVersionByStage = getNextVersionByStage;
408
+ async function validateExtensionSpec(rootDirectory, extensionId) {
409
+ const extensionSpec = await (0, localHelper_1.getLocalExtensionSpec)(rootDirectory);
410
+ if (extensionSpec.name !== extensionId) {
411
+ throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
341
412
  }
342
413
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
343
414
  subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
344
415
  validateSpec(subbedSpec);
345
- extensionSpec.version = await incrementPrereleaseVersion(extensionRef, extensionSpec.version, args.stage);
416
+ return extensionSpec;
417
+ }
418
+ function validateReleaseNotes(rootDirectory, newVersion, extension) {
346
419
  let notes;
347
420
  try {
348
- const changes = (0, change_log_1.getLocalChangelog)(args.rootDirectory);
349
- notes = changes[extensionSpec.version];
421
+ const changes = (0, change_log_1.getLocalChangelog)(rootDirectory);
422
+ notes = changes[newVersion];
350
423
  }
351
424
  catch (err) {
352
425
  throw new error_1.FirebaseError("No CHANGELOG.md file found. " +
353
426
  "Please create one and add an entry for this version. " +
354
- (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
427
+ (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/publishers/user-documentation#writing-changelog for more details."));
355
428
  }
356
- if (!notes && !semver.prerelease(extensionSpec.version) && args.latestVersion) {
357
- throw new error_1.FirebaseError(`No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` +
429
+ if (!notes && !semver.prerelease(newVersion) && extension) {
430
+ throw new error_1.FirebaseError(`No entry for version ${newVersion} found in CHANGELOG.md. ` +
358
431
  "Please add one so users know what has changed in this version. " +
359
- (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
432
+ (0, marked_1.marked)("See https://firebase.google.com/docs/extensions/publishers/user-documentation#writing-changelog for more details."));
360
433
  }
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 });
434
+ return notes;
435
+ }
436
+ function validateVersion(extensionRef, newVersion, latestVersion) {
437
+ if (latestVersion) {
438
+ if (semver.lt(newVersion, latestVersion)) {
439
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(newVersion)}) is lower than the current version (${clc.bold(latestVersion)}) for the extension '${clc.bold(extensionRef)}'. Make sure this version is greater than the current version (${clc.bold(latestVersion)}) inside of extension.yaml and try again.\n`, { exit: 104 });
364
440
  }
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 });
441
+ else if (semver.eq(newVersion, latestVersion)) {
442
+ throw new error_1.FirebaseError(`The version you are trying to upload (${clc.bold(newVersion)}) already exists for extension '${clc.bold(extensionRef)}'. Increment the version inside of extension.yaml and try again.\n`, { exit: 103 });
367
443
  }
368
444
  }
369
- return { extensionSpec, notes };
370
445
  }
371
- async function publishExtensionVersionFromRemoteRepo(args) {
446
+ function unpackExtensionState(extension) {
447
+ switch (extension.state) {
448
+ case "PUBLISHED":
449
+ if (extension.latestApprovedVersion) {
450
+ return clc.bold(clc.green("Published"));
451
+ }
452
+ else if (extension.latestVersion) {
453
+ return clc.green("Uploaded");
454
+ }
455
+ else {
456
+ return "Prerelease";
457
+ }
458
+ case "DEPRECATED":
459
+ return clc.red("Deprecated");
460
+ case "SUSPENDED":
461
+ return clc.bold(clc.red("Suspended"));
462
+ default:
463
+ return "-";
464
+ }
465
+ }
466
+ exports.unpackExtensionState = unpackExtensionState;
467
+ function displayExtensionHeader(extensionRef, extension, extensionRoot) {
468
+ var _a, _b;
469
+ if (extension) {
470
+ let source = "Local source";
471
+ if (extension.repoUri) {
472
+ const uri = new URL(extension.repoUri);
473
+ uri.pathname = path.join(uri.pathname, extensionRoot !== null && extensionRoot !== void 0 ? extensionRoot : "");
474
+ source = `${uri.toString()} (use --repo and --root to modify)`;
475
+ }
476
+ logger_1.logger.info(`\n${clc.bold("Extension:")} ${extension.ref}\n` +
477
+ `${clc.bold("State:")} ${unpackExtensionState(extension)}\n` +
478
+ `${clc.bold("Latest Version:")} ${(_a = extension.latestVersion) !== null && _a !== void 0 ? _a : "-"}\n` +
479
+ `${clc.bold("Version in Extensions Hub:")} ${(_b = extension.latestApprovedVersion) !== null && _b !== void 0 ? _b : "-"}\n` +
480
+ `${clc.bold("Source in GitHub:")} ${source}\n`);
481
+ }
482
+ else {
483
+ logger_1.logger.info(`\n${clc.bold("Extension:")} ${extensionRef}\n` +
484
+ `${clc.bold("State:")} ${clc.bold(clc.blue("New"))}\n`);
485
+ }
486
+ }
487
+ async function fetchExtensionSource(repoUri, sourceRef, extensionRoot) {
488
+ const sourceUri = repoUri + path.join("/tree", sourceRef, extensionRoot);
489
+ logger_1.logger.info(`Validating source code at ${clc.bold(sourceUri)}...`);
490
+ const archiveUri = `${repoUri}/archive/${sourceRef}.zip`;
491
+ const tempDirectory = tmp.dirSync({ unsafeCleanup: true });
492
+ try {
493
+ const response = await (0, node_fetch_1.default)(archiveUri);
494
+ if (response.ok) {
495
+ await response.body.pipe((0, unzip_1.createUnzipTransform)(tempDirectory.name)).promise();
496
+ }
497
+ }
498
+ catch (err) {
499
+ throw new error_1.FirebaseError(`Failed to fetch extension archive from ${archiveUri}. Please check the repo URI and source ref. ${err}`);
500
+ }
501
+ const archiveName = fs.readdirSync(tempDirectory.name)[0];
502
+ const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot);
503
+ try {
504
+ (0, localHelper_1.readFile)(path.resolve(rootDirectory, localHelper_1.EXTENSIONS_SPEC_FILE));
505
+ }
506
+ catch (err) {
507
+ 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.`);
508
+ }
509
+ return rootDirectory;
510
+ }
511
+ async function uploadExtensionVersionFromGitHubSource(args) {
512
+ var _a, _b, _c;
372
513
  const extensionRef = `${args.publisherId}/${args.extensionId}`;
373
514
  let extension;
515
+ let latestVersion;
374
516
  try {
375
- extension = await (0, extensionsApi_1.getExtension)(extensionRef);
517
+ extension = await (0, publisherApi_1.getExtension)(extensionRef);
518
+ latestVersion = await (0, publisherApi_1.getExtensionVersion)(`${extensionRef}@latest`);
376
519
  }
377
520
  catch (err) {
378
521
  }
522
+ displayExtensionHeader(extensionRef, extension, latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.extensionRoot);
523
+ if (args.stage && !stageOptions.includes(args.stage)) {
524
+ throw new error_1.FirebaseError(`--stage only supports the following values: ${stageOptions.join(", ")}`);
525
+ }
379
526
  if (args.repoUri && !repoRegex.test(args.repoUri)) {
380
527
  throw new error_1.FirebaseError("Repo URI must follow this format: https://github.com/<user>/<repo>");
381
528
  }
@@ -388,30 +535,14 @@ async function publishExtensionVersionFromRemoteRepo(args) {
388
535
  throw new error_1.FirebaseError("Repo URI is required but not currently set.");
389
536
  }
390
537
  }
391
- if (extension === null || extension === void 0 ? void 0 : extension.repoUri) {
392
- logger_1.logger.info(`Extension ${clc.bold(extensionRef)} is published from ${clc.bold(extension === null || extension === void 0 ? void 0 : extension.repoUri)}. Use --repo to change this repo.`);
393
- }
394
- let extensionRoot = args.extensionRoot;
395
- let defaultRoot = "/";
538
+ let extensionRoot = args.extensionRoot || (latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.extensionRoot);
396
539
  if (!extensionRoot) {
397
- if (extension) {
398
- try {
399
- const extensionVersionRef = `${extensionRef}@${extension.latestVersion}`;
400
- const extensionVersion = await (0, extensionsApi_1.getExtensionVersion)(extensionVersionRef);
401
- if (extensionVersion.extensionRoot) {
402
- defaultRoot = extensionVersion.extensionRoot;
403
- }
404
- }
405
- catch (err) {
406
- }
407
- }
408
- extensionRoot = defaultRoot;
540
+ const defaultRoot = "/";
409
541
  if (!args.nonInteractive) {
410
- extensionRoot = await (0, prompt_1.promptOnce)({
411
- type: "input",
412
- message: "Enter this Extension's root directory in the repo (defaults to previous root if set):",
413
- default: defaultRoot,
414
- });
542
+ extensionRoot = await promptForValidExtensionRoot(defaultRoot);
543
+ }
544
+ else {
545
+ extensionRoot = defaultRoot;
415
546
  }
416
547
  }
417
548
  let sourceRef = args.sourceRef;
@@ -428,50 +559,35 @@ async function publishExtensionVersionFromRemoteRepo(args) {
428
559
  sourceRef = defaultSourceRef;
429
560
  }
430
561
  }
562
+ const rootDirectory = await fetchExtensionSource(repoUri, sourceRef, extensionRoot);
563
+ const extensionSpec = await validateExtensionSpec(rootDirectory, args.extensionId);
564
+ validateVersion(extensionRef, extensionSpec.version, extension === null || extension === void 0 ? void 0 : extension.latestVersion);
565
+ const { versionByStage, hasVersions } = await getNextVersionByStage(extensionRef, extensionSpec.version);
566
+ const autoReview = !!(extension === null || extension === void 0 ? void 0 : extension.latestApprovedVersion) ||
567
+ ((_a = latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.listing) === null || _a === void 0 ? void 0 : _a.state) === "PENDING" ||
568
+ ((_b = latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.listing) === null || _b === void 0 ? void 0 : _b.state) === "APPROVED" ||
569
+ ((_c = latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.listing) === null || _c === void 0 ? void 0 : _c.state) === "REJECTED";
431
570
  let stage = args.stage;
432
- const defaultStage = "rc";
433
571
  if (!stage) {
434
- if (!args.nonInteractive) {
435
- stage = await (0, prompt_1.promptOnce)({
436
- type: "list",
437
- message: "Choose the release stage (pre-release annotations will be auto-incremented):",
438
- choices: stageOptions,
439
- default: defaultStage,
440
- });
441
- }
442
- else {
443
- stage = defaultStage;
444
- }
445
- }
446
- logger_1.logger.info("Downloading and validating source code...");
447
- const archiveUri = `${repoUri}/archive/${sourceRef}.zip`;
448
- const tempDirectory = tmp.dirSync({ unsafeCleanup: true });
449
- try {
450
- const response = await (0, node_fetch_1.default)(archiveUri);
451
- if (response.ok) {
452
- await response.body.pipe(unzipper.Extract({ path: tempDirectory.name })).promise();
453
- }
454
- }
455
- catch (err) {
456
- throw new error_1.FirebaseError(`Failed to fetch Extension archive ${archiveUri}. Please check the repo URI and source ref. ${err}`);
457
- }
458
- const archiveName = fs.readdirSync(tempDirectory.name)[0];
459
- const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot);
460
- try {
461
- (0, localHelper_1.readFile)(path.resolve(rootDirectory, localHelper_1.EXTENSIONS_SPEC_FILE));
462
- }
463
- catch (err) {
464
- 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.`);
572
+ stage = await promptForReleaseStage({
573
+ versionByStage,
574
+ autoReview,
575
+ allowStable: true,
576
+ hasVersions,
577
+ nonInteractive: args.nonInteractive,
578
+ force: args.force,
579
+ });
465
580
  }
466
- const { extensionSpec, notes } = await validateExtensionSpec({
467
- publisherId: args.publisherId,
468
- extensionId: args.extensionId,
469
- rootDirectory: rootDirectory,
470
- latestVersion: extension === null || extension === void 0 ? void 0 : extension.latestVersion,
471
- stage: stage,
581
+ const newVersion = versionByStage.get(stage);
582
+ const releaseNotes = validateReleaseNotes(rootDirectory, extensionSpec.version, extension);
583
+ const sourceUri = repoUri + path.join("/tree", sourceRef, extensionRoot);
584
+ displayReleaseNotes({
585
+ extensionRef,
586
+ newVersion,
587
+ releaseNotes,
588
+ sourceUri,
589
+ autoReview: stage === "stable" && autoReview,
472
590
  });
473
- const sourceUri = path.join(repoUri, "tree", sourceRef, extensionRoot);
474
- displayReleaseNotes(extensionRef, extensionSpec.version, notes, sourceUri);
475
591
  const confirmed = await (0, prompt_1.confirm)({
476
592
  nonInteractive: args.nonInteractive,
477
593
  force: args.force,
@@ -480,22 +596,21 @@ async function publishExtensionVersionFromRemoteRepo(args) {
480
596
  if (!confirmed) {
481
597
  return;
482
598
  }
483
- const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`;
484
- const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`);
599
+ const extensionVersionRef = `${extensionRef}@${newVersion}`;
600
+ const uploadSpinner = ora(`Uploading ${clc.bold(extensionVersionRef)}...`);
485
601
  let res;
486
602
  try {
487
- publishSpinner.start();
488
- res = await (0, extensionsApi_1.publishExtensionVersion)({
603
+ uploadSpinner.start();
604
+ res = await (0, publisherApi_1.createExtensionVersionFromGitHubSource)({
489
605
  extensionVersionRef,
490
- packageUri: "",
491
606
  extensionRoot,
492
607
  repoUri,
493
- sourceRef: args.sourceRef,
608
+ sourceRef: sourceRef,
494
609
  });
495
- publishSpinner.succeed(` Successfully published ${clc.bold(extensionRef)}`);
610
+ uploadSpinner.succeed(`Successfully uploaded ${clc.bold(extensionRef)}`);
496
611
  }
497
612
  catch (err) {
498
- publishSpinner.fail();
613
+ uploadSpinner.fail();
499
614
  if (err.status === 404) {
500
615
  throw getMissingPublisherError(args.publisherId);
501
616
  }
@@ -503,23 +618,44 @@ async function publishExtensionVersionFromRemoteRepo(args) {
503
618
  }
504
619
  return res;
505
620
  }
506
- exports.publishExtensionVersionFromRemoteRepo = publishExtensionVersionFromRemoteRepo;
507
- async function publishExtensionVersionFromLocalSource(args) {
621
+ exports.uploadExtensionVersionFromGitHubSource = uploadExtensionVersionFromGitHubSource;
622
+ async function uploadExtensionVersionFromLocalSource(args) {
623
+ const extensionRef = `${args.publisherId}/${args.extensionId}`;
508
624
  let extension;
625
+ let latestVersion;
509
626
  try {
510
- extension = await (0, extensionsApi_1.getExtension)(`${args.publisherId}/${args.extensionId}`);
627
+ extension = await (0, publisherApi_1.getExtension)(extensionRef);
628
+ latestVersion = await (0, publisherApi_1.getExtensionVersion)(`${extensionRef}@latest`);
511
629
  }
512
630
  catch (err) {
513
631
  }
514
- const { extensionSpec, notes } = await validateExtensionSpec({
515
- publisherId: args.publisherId,
516
- extensionId: args.extensionId,
517
- rootDirectory: args.rootDirectory,
518
- latestVersion: extension === null || extension === void 0 ? void 0 : extension.latestVersion,
519
- stage: args.stage,
520
- });
521
- const extensionRef = `${args.publisherId}/${args.extensionId}`;
522
- displayReleaseNotes(extensionRef, extensionSpec.version, notes);
632
+ displayExtensionHeader(extensionRef, extension, latestVersion === null || latestVersion === void 0 ? void 0 : latestVersion.extensionRoot);
633
+ const localStageOptions = ["rc", "alpha", "beta"];
634
+ if (args.stage && !localStageOptions.includes(args.stage)) {
635
+ throw new error_1.FirebaseError(`--stage only supports the following values when used with --local: ${localStageOptions.join(", ")}`);
636
+ }
637
+ const extensionSpec = await validateExtensionSpec(args.rootDirectory, args.extensionId);
638
+ validateVersion(extensionRef, extensionSpec.version, extension === null || extension === void 0 ? void 0 : extension.latestVersion);
639
+ const { versionByStage } = await getNextVersionByStage(extensionRef, extensionSpec.version);
640
+ let stage = args.stage;
641
+ if (!stage) {
642
+ if (!args.nonInteractive) {
643
+ stage = await promptForReleaseStage({
644
+ versionByStage,
645
+ autoReview: false,
646
+ allowStable: false,
647
+ hasVersions: false,
648
+ nonInteractive: args.nonInteractive,
649
+ force: args.force,
650
+ });
651
+ }
652
+ else {
653
+ stage = "rc";
654
+ }
655
+ }
656
+ const newVersion = versionByStage.get(stage);
657
+ const releaseNotes = validateReleaseNotes(args.rootDirectory, extensionSpec.version, extension);
658
+ displayReleaseNotes({ extensionRef, newVersion, releaseNotes, autoReview: false });
523
659
  const confirmed = await (0, prompt_1.confirm)({
524
660
  nonInteractive: args.nonInteractive,
525
661
  force: args.force,
@@ -528,28 +664,28 @@ async function publishExtensionVersionFromLocalSource(args) {
528
664
  if (!confirmed) {
529
665
  return;
530
666
  }
531
- const extensionVersionRef = `${extensionRef}@${extensionSpec.version}`;
667
+ const extensionVersionRef = `${extensionRef}@${newVersion}`;
532
668
  let packageUri;
533
669
  let objectPath = "";
534
- const uploadSpinner = ora(" Archiving and uploading extension source code");
670
+ const uploadSpinner = ora("Archiving and uploading extension source code...");
535
671
  try {
536
672
  uploadSpinner.start();
537
673
  objectPath = await archiveAndUploadSource(args.rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
538
- uploadSpinner.succeed(" Uploaded extension source code");
674
+ uploadSpinner.succeed("Uploaded extension source code");
539
675
  packageUri = api_1.storageOrigin + objectPath + "?alt=media";
540
676
  }
541
677
  catch (err) {
542
678
  uploadSpinner.fail();
543
- throw new error_1.FirebaseError(`Failed to archive and upload extension source, ${err}`, {
679
+ throw new error_1.FirebaseError(`Failed to archive and upload extension source code, ${err}`, {
544
680
  original: err,
545
681
  });
546
682
  }
547
- const publishSpinner = ora(`Publishing ${clc.bold(extensionVersionRef)}`);
683
+ const publishSpinner = ora(`Uploading ${clc.bold(extensionVersionRef)}...`);
548
684
  let res;
549
685
  try {
550
686
  publishSpinner.start();
551
- res = await (0, extensionsApi_1.publishExtensionVersion)({ extensionVersionRef, packageUri });
552
- publishSpinner.succeed(` Successfully published ${clc.bold(extensionVersionRef)}`);
687
+ res = await (0, publisherApi_1.createExtensionVersionFromLocalSource)({ extensionVersionRef, packageUri });
688
+ publishSpinner.succeed(`Successfully uploaded ${clc.bold(extensionVersionRef)}`);
553
689
  }
554
690
  catch (err) {
555
691
  publishSpinner.fail();
@@ -561,10 +697,11 @@ async function publishExtensionVersionFromLocalSource(args) {
561
697
  await deleteUploadedSource(objectPath);
562
698
  return res;
563
699
  }
564
- exports.publishExtensionVersionFromLocalSource = publishExtensionVersionFromLocalSource;
700
+ exports.uploadExtensionVersionFromLocalSource = uploadExtensionVersionFromLocalSource;
565
701
  function getMissingPublisherError(publisherId) {
566
- 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.`));
702
+ 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. For step-by-step instructions on getting started as a publisher, see https://firebase.google.com/docs/extensions/publishers/get-started.`));
567
703
  }
704
+ exports.getMissingPublisherError = getMissingPublisherError;
568
705
  async function createSourceFromLocation(projectId, sourceUri) {
569
706
  const extensionRoot = "/";
570
707
  let packageUri;
@@ -608,18 +745,18 @@ function getPublisherProjectFromName(publisherName) {
608
745
  throw new error_1.FirebaseError(`Could not find publisher with name '${publisherName}'.`);
609
746
  }
610
747
  exports.getPublisherProjectFromName = getPublisherProjectFromName;
611
- function displayReleaseNotes(extensionRef, versionId, releaseNotes, sourceUri) {
612
- const source = sourceUri || "local source";
613
- const releaseNotesMessage = releaseNotes
614
- ? `${clc.bold("Release notes:")}\n${(0, marked_1.marked)(releaseNotes)}`
615
- : "";
616
- const metadataMessage = `${clc.bold("Extension:")} ${extensionRef}\n` +
617
- `${clc.bold("Version:")} ${clc.bold(clc.green(versionId))}\n` +
748
+ function displayReleaseNotes(args) {
749
+ const source = args.sourceUri || "Local source";
750
+ const releaseNotesMessage = args.releaseNotes
751
+ ? `${clc.bold("Release notes:")}\n${(0, marked_1.marked)(args.releaseNotes)}`
752
+ : "\n";
753
+ const metadataMessage = `${clc.bold("Extension:")} ${args.extensionRef}\n` +
754
+ `${clc.bold("Version:")} ${clc.bold(clc.green(args.newVersion))} ${args.autoReview ? "(automatically sent for review)" : ""}\n` +
618
755
  `${clc.bold("Source:")} ${source}\n`;
619
- const message = `\nYou are about to publish a new version to Firebase's registry of Extensions.\n\n` +
756
+ const message = `\nYou are about to upload a new version to Firebase's registry of extensions.\n\n` +
620
757
  metadataMessage +
621
758
  releaseNotesMessage +
622
- `\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`;
759
+ `Once an extension version is uploaded, it becomes installable by other users and cannot be changed. If you wish to make changes after uploading, you will need to upload a new version.\n`;
623
760
  logger_1.logger.info(message);
624
761
  }
625
762
  exports.displayReleaseNotes = displayReleaseNotes;
@@ -727,7 +864,7 @@ async function canonicalizeRefInput(refInput) {
727
864
  inferredRef = `firebase/${inferredRef}`;
728
865
  }
729
866
  if (refInput.split("@").length < 2) {
730
- inferredRef = `${inferredRef}@latest`;
867
+ inferredRef = `${inferredRef}@latest-approved`;
731
868
  }
732
869
  const ref = refs.parse(inferredRef);
733
870
  ref.version = await (0, planner_1.resolveVersion)(ref);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.showPostDeprecationNotice = exports.readInstanceParam = exports.writeExtensionsToFirebaseJson = exports.getInstanceRef = exports.getInstanceTarget = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeEmptyManifest = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
3
+ exports.readInstanceParam = exports.writeExtensionsToFirebaseJson = exports.getInstanceRef = exports.getInstanceTarget = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeEmptyManifest = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
4
4
  const clc = require("colorette");
5
5
  const path = require("path");
6
6
  const fs = require("fs-extra");
@@ -203,10 +203,3 @@ function readParamsFile(projectDir, fileName) {
203
203
  const params = (0, paramHelper_1.readEnvFile)(paramPath);
204
204
  return params;
205
205
  }
206
- function showPostDeprecationNotice() {
207
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, "The behavior of ext:install, ext:update, ext:configure, and ext:uninstall has changed in firebase-tools@11.0.0. " +
208
- "Instead of deploying extensions directly, " +
209
- "changes to extension instances will be written to firebase.json and ./extensions/*.env. " +
210
- `Then ${clc.bold("firebase deploy (--only extensions)")} will deploy the changes to your Firebase project. See https://firebase.google.com/docs/extensions/manifest for more details.`);
211
- }
212
- exports.showPostDeprecationNotice = showPostDeprecationNotice;