firebase-tools 12.4.1 → 12.4.3

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 (34) hide show
  1. package/lib/commands/ext-configure.js +0 -1
  2. package/lib/commands/ext-install.js +56 -56
  3. package/lib/commands/ext-update.js +0 -1
  4. package/lib/commands/index.js +0 -2
  5. package/lib/commands/internaltesting-frameworks-compose.js +4 -2
  6. package/lib/commands/open.js +2 -2
  7. package/lib/deploy/extensions/planner.js +8 -6
  8. package/lib/deploy/functions/ensure.js +0 -3
  9. package/lib/deploy/functions/prepare.js +12 -17
  10. package/lib/deploy/functions/release/index.js +1 -1
  11. package/lib/deploy/functions/release/reporter.js +41 -24
  12. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +0 -3
  13. package/lib/deploy/functions/runtimes/node/versioning.js +0 -2
  14. package/lib/deploy/lifecycleHooks.js +25 -6
  15. package/lib/emulator/downloadableEmulators.js +3 -3
  16. package/lib/extensions/displayExtensionInfo.js +104 -42
  17. package/lib/extensions/emulator/triggerHelper.js +1 -1
  18. package/lib/extensions/extensionsApi.js +1 -1
  19. package/lib/extensions/extensionsHelper.js +19 -30
  20. package/lib/extensions/publisherApi.js +1 -2
  21. package/lib/extensions/updateHelper.js +2 -2
  22. package/lib/frameworks/compose/discover/filesystem.js +1 -1
  23. package/lib/frameworks/compose/discover/index.js +27 -18
  24. package/lib/frameworks/compose/discover/runtime/node.js +149 -0
  25. package/lib/frameworks/compose/driver/docker.js +23 -13
  26. package/lib/frameworks/compose/driver/local.js +13 -4
  27. package/lib/frameworks/compose/index.js +9 -8
  28. package/lib/frameworks/next/index.js +13 -4
  29. package/lib/{api → gcp}/frameworks.js +7 -5
  30. package/lib/init/features/frameworks/index.js +31 -1
  31. package/lib/init/features/{composer → frameworks}/repo.js +1 -4
  32. package/package.json +1 -1
  33. package/schema/firebase-config.json +4 -0
  34. package/lib/commands/internaltesting-frameworks-init.js +0 -14
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.retrieveRoleInfo = exports.printSourceDownloadLink = exports.displayExtInfo = void 0;
3
+ exports.retrieveRoleInfo = exports.displayResources = exports.displayEvents = exports.displayExternalServices = exports.displayExtensionVersionInfo = void 0;
4
4
  const clc = require("colorette");
5
5
  const marked_1 = require("marked");
6
+ const semver = require("semver");
6
7
  const TerminalRenderer = require("marked-terminal");
7
- const utils = require("../utils");
8
- const extensionsHelper_1 = require("./extensionsHelper");
8
+ const path = require("path");
9
+ const refs = require("../extensions/refs");
9
10
  const logger_1 = require("../logger");
10
- const error_1 = require("../error");
11
11
  const types_1 = require("./types");
12
12
  const iam = require("../gcp/iam");
13
13
  const secretsUtils_1 = require("./secretsUtils");
@@ -16,23 +16,59 @@ marked_1.marked.setOptions({
16
16
  });
17
17
  const TASKS_ROLE = "cloudtasks.enqueuer";
18
18
  const TASKS_API = "cloudtasks.googleapis.com";
19
- async function displayExtInfo(extensionName, publisher, spec, published = false) {
19
+ async function displayExtensionVersionInfo(args) {
20
+ var _a, _b, _c, _d, _e, _f, _g;
21
+ const { spec, extensionVersion, latestApprovedVersion, latestVersion } = args;
20
22
  const lines = [];
21
- lines.push(`**Name**: ${spec.displayName}`);
22
- if (publisher) {
23
- lines.push(`**Publisher**: ${publisher}`);
24
- }
23
+ const extensionRef = extensionVersion
24
+ ? refs.toExtensionRef(refs.parse(extensionVersion === null || extensionVersion === void 0 ? void 0 : extensionVersion.ref))
25
+ : "";
26
+ lines.push(`${clc.bold("Extension:")} ${(_a = spec.displayName) !== null && _a !== void 0 ? _a : "Unnamed extension"} ${extensionRef ? `(${extensionRef})` : ""}`);
25
27
  if (spec.description) {
26
- lines.push(`**Description**: ${spec.description}`);
28
+ lines.push(`${clc.bold("Description:")} ${spec.description}`);
29
+ }
30
+ let versionNote = "";
31
+ const latestRelevantVersion = latestApprovedVersion || latestVersion;
32
+ if (latestRelevantVersion && semver.eq(spec.version, latestRelevantVersion)) {
33
+ versionNote = `- ${clc.green("Latest")}`;
34
+ }
35
+ if ((extensionVersion === null || extensionVersion === void 0 ? void 0 : extensionVersion.state) === "DEPRECATED") {
36
+ versionNote = `- ${clc.red("Deprecated")}`;
27
37
  }
28
- if (published) {
29
- if (spec.license) {
30
- lines.push(`**License**: ${spec.license}`);
38
+ lines.push(`${clc.bold("Version:")} ${spec.version} ${versionNote}`);
39
+ if (extensionVersion) {
40
+ let reviewStatus;
41
+ switch ((_b = extensionVersion.listing) === null || _b === void 0 ? void 0 : _b.state) {
42
+ case "APPROVED":
43
+ reviewStatus = clc.bold(clc.green("Accepted"));
44
+ break;
45
+ case "REJECTED":
46
+ reviewStatus = clc.bold(clc.red("Rejected"));
47
+ break;
48
+ default:
49
+ reviewStatus = clc.bold(clc.yellow("Unreviewed"));
50
+ }
51
+ lines.push(`${clc.bold("Review status:")} ${reviewStatus}`);
52
+ if (latestApprovedVersion) {
53
+ lines.push(`${clc.bold("View in Extensions Hub:")} https://extensions.dev/extensions/${extensionRef}`);
54
+ }
55
+ if (extensionVersion.buildSourceUri) {
56
+ const buildSourceUri = new URL(extensionVersion.buildSourceUri);
57
+ buildSourceUri.pathname = path.join(buildSourceUri.pathname, (_c = extensionVersion.extensionRoot) !== null && _c !== void 0 ? _c : "");
58
+ lines.push(`${clc.bold("Source in GitHub:")} ${buildSourceUri}`);
31
59
  }
32
- if (spec.sourceUrl) {
33
- lines.push(`**Source code**: ${spec.sourceUrl}`);
60
+ else {
61
+ lines.push(`${clc.bold("Source download URI:")} ${(_d = extensionVersion.sourceDownloadUri) !== null && _d !== void 0 ? _d : "-"}`);
34
62
  }
35
63
  }
64
+ lines.push(`${clc.bold("License:")} ${(_e = spec.license) !== null && _e !== void 0 ? _e : "-"}`);
65
+ lines.push(displayResources(spec));
66
+ if ((_f = spec.events) === null || _f === void 0 ? void 0 : _f.length) {
67
+ lines.push(displayEvents(spec));
68
+ }
69
+ if ((_g = spec.externalServices) === null || _g === void 0 ? void 0 : _g.length) {
70
+ lines.push(displayExternalServices(spec));
71
+ }
36
72
  const apis = impliedApis(spec);
37
73
  if (apis.length) {
38
74
  lines.push(displayApis(apis));
@@ -41,44 +77,70 @@ async function displayExtInfo(extensionName, publisher, spec, published = false)
41
77
  if (roles.length) {
42
78
  lines.push(await displayRoles(roles));
43
79
  }
44
- if (lines.length > 0) {
45
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, `information about '${clc.bold(extensionName)}':`);
46
- const infoStr = lines.join("\n");
47
- const formatted = (0, marked_1.marked)(infoStr).replace(/\n+$/, "\n");
48
- logger_1.logger.info(formatted);
49
- return lines;
50
- }
51
- else {
52
- throw new error_1.FirebaseError("Error occurred during installation: cannot parse info from source spec", {
53
- context: {
54
- spec: spec,
55
- extensionName: extensionName,
56
- },
57
- });
58
- }
80
+ logger_1.logger.info(`\n${lines.join("\n")}`);
81
+ return lines;
82
+ }
83
+ exports.displayExtensionVersionInfo = displayExtensionVersionInfo;
84
+ function displayExternalServices(spec) {
85
+ var _a, _b;
86
+ const lines = (_b = (_a = spec.externalServices) === null || _a === void 0 ? void 0 : _a.map((service) => {
87
+ return ` - ${clc.cyan(`${service.name} (${service.pricingUri})`)}`;
88
+ })) !== null && _b !== void 0 ? _b : [];
89
+ return clc.bold("External services used:\n") + lines.join("\n");
90
+ }
91
+ exports.displayExternalServices = displayExternalServices;
92
+ function displayEvents(spec) {
93
+ var _a, _b;
94
+ const lines = (_b = (_a = spec.events) === null || _a === void 0 ? void 0 : _a.map((event) => {
95
+ return ` - ${clc.magenta(event.type)}${event.description ? `: ${event.description}` : ""}`;
96
+ })) !== null && _b !== void 0 ? _b : [];
97
+ return clc.bold("Events emitted:\n") + lines.join("\n");
59
98
  }
60
- exports.displayExtInfo = displayExtInfo;
61
- function printSourceDownloadLink(sourceDownloadUri) {
62
- const sourceDownloadMsg = `Want to review the source code that will be installed? Download it here: ${sourceDownloadUri}`;
63
- utils.logBullet((0, marked_1.marked)(sourceDownloadMsg));
99
+ exports.displayEvents = displayEvents;
100
+ function displayResources(spec) {
101
+ var _a;
102
+ const lines = spec.resources.map((resource) => {
103
+ let type = resource.type;
104
+ switch (resource.type) {
105
+ case "firebaseextensions.v1beta.function":
106
+ type = "Cloud Function (1st gen)";
107
+ break;
108
+ case "firebaseextensions.v1beta.v2function":
109
+ type = "Cloud Function (2nd gen)";
110
+ break;
111
+ default:
112
+ }
113
+ return ` - ${clc.blue(`${resource.name} (${type})`)}${resource.description ? `: ${resource.description}` : ""}`;
114
+ });
115
+ lines.push(...new Set((_a = spec.lifecycleEvents) === null || _a === void 0 ? void 0 : _a.map((event) => {
116
+ return ` - ${clc.blue(`${event.taskQueueTriggerFunction} (Cloud Task queue)`)}`;
117
+ })));
118
+ lines.push(...spec.params
119
+ .filter((param) => {
120
+ return param.type === "SECRET";
121
+ })
122
+ .map((param) => {
123
+ return ` - ${clc.blue(`${param.param} (Cloud Secret Manager secret)`)}`;
124
+ }));
125
+ return clc.bold("Resources created:\n") + (lines.length ? lines.join("\n") : " - None");
64
126
  }
65
- exports.printSourceDownloadLink = printSourceDownloadLink;
127
+ exports.displayResources = displayResources;
66
128
  async function retrieveRoleInfo(role) {
67
129
  const res = await iam.getRole(role);
68
- return ` ${res.title} (${res.description})`;
130
+ return ` - ${clc.yellow(res.title)}${res.description ? `: ${res.description}` : ""}`;
69
131
  }
70
132
  exports.retrieveRoleInfo = retrieveRoleInfo;
71
133
  async function displayRoles(roles) {
72
134
  const lines = await Promise.all(roles.map((role) => {
73
135
  return retrieveRoleInfo(role.role);
74
136
  }));
75
- return clc.bold("**Roles granted to this Extension**:\n") + lines.join("\n");
137
+ return clc.bold("Roles granted:\n") + lines.join("\n");
76
138
  }
77
139
  function displayApis(apis) {
78
140
  const lines = apis.map((api) => {
79
- return ` ${api.apiName} (${api.reason})`;
141
+ return ` - ${clc.cyan(api.apiName)}: ${api.reason}`;
80
142
  });
81
- return "**APIs used by this Extension**:\n" + lines.join("\n");
143
+ return clc.bold("APIs used:\n") + lines.join("\n");
82
144
  }
83
145
  function usesTasks(spec) {
84
146
  return spec.resources.some((r) => { var _a; return r.type === types_1.FUNCTIONS_RESOURCE_TYPE && ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.taskQueueTrigger) !== undefined; });
@@ -89,13 +151,13 @@ function impliedRoles(spec) {
89
151
  if ((0, secretsUtils_1.usesSecrets)(spec) && !((_a = spec.roles) === null || _a === void 0 ? void 0 : _a.some((r) => r.role === secretsUtils_1.SECRET_ROLE))) {
90
152
  roles.push({
91
153
  role: secretsUtils_1.SECRET_ROLE,
92
- reason: "Allows the extension to read secret values from Cloud Secret Manager",
154
+ reason: "Allows the extension to read secret values from Cloud Secret Manager.",
93
155
  });
94
156
  }
95
157
  if (usesTasks(spec) && !((_b = spec.roles) === null || _b === void 0 ? void 0 : _b.some((r) => r.role === TASKS_ROLE))) {
96
158
  roles.push({
97
159
  role: TASKS_ROLE,
98
- reason: "Allows the extension to enqueue Cloud Tasks",
160
+ reason: "Allows the extension to enqueue Cloud Tasks.",
99
161
  });
100
162
  }
101
163
  return roles.concat((_c = spec.roles) !== null && _c !== void 0 ? _c : []);
@@ -106,7 +168,7 @@ function impliedApis(spec) {
106
168
  if (usesTasks(spec) && !((_a = spec.apis) === null || _a === void 0 ? void 0 : _a.some((a) => a.apiName === TASKS_API))) {
107
169
  apis.push({
108
170
  apiName: TASKS_API,
109
- reason: "Allows the extension to enqueue Cloud Tasks",
171
+ reason: "Allows the extension to enqueue Cloud Tasks.",
110
172
  });
111
173
  }
112
174
  return apis.concat((_b = spec.apis) !== null && _b !== void 0 ? _b : []);
@@ -37,7 +37,7 @@ function functionResourceToEmulatedTriggerDefintion(resource, systemParams = {})
37
37
  proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
38
38
  proto.convertIfPresent(etd, properties, "regions", "location", (str) => [str]);
39
39
  proto.copyIfPresent(etd, properties, "availableMemoryMb");
40
- if (properties.httpsTrigger) {
40
+ if (properties.httpsTrigger !== undefined) {
41
41
  etd.httpsTrigger = properties.httpsTrigger;
42
42
  }
43
43
  if (properties.eventTrigger) {
@@ -31,7 +31,7 @@ async function createInstanceHelper(projectId, instanceId, config, validateOnly
31
31
  apiOrigin: api_1.extensionsOrigin,
32
32
  apiVersion: EXTENSIONS_API_VERSION,
33
33
  operationResourceName: createRes.body.name,
34
- masterTimeout: 600000,
34
+ masterTimeout: 3600000,
35
35
  });
36
36
  return pollRes;
37
37
  }
@@ -1,6 +1,6 @@
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.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;
3
+ 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.promptForExtensionRoot = 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");
@@ -36,7 +36,6 @@ const utils_2 = require("../utils");
36
36
  const change_log_1 = require("./change-log");
37
37
  const getProjectNumber_1 = require("../getProjectNumber");
38
38
  const constants_1 = require("../emulator/constants");
39
- const planner_1 = require("../deploy/extensions/planner");
40
39
  var SpecParamType;
41
40
  (function (SpecParamType) {
42
41
  SpecParamType["SELECT"] = "select";
@@ -301,20 +300,14 @@ async function promptForValidRepoURI() {
301
300
  return extensionRoot;
302
301
  }
303
302
  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;
303
+ async function promptForExtensionRoot(defaultRoot) {
304
+ return await (0, prompt_1.promptOnce)({
305
+ type: "input",
306
+ message: "Enter this extension's root directory in the repo (defaults to previous root if set):",
307
+ default: defaultRoot,
308
+ });
316
309
  }
317
- exports.promptForValidExtensionRoot = promptForValidExtensionRoot;
310
+ exports.promptForExtensionRoot = promptForExtensionRoot;
318
311
  async function promptForReleaseStage(args) {
319
312
  let stage = "rc";
320
313
  if (!args.nonInteractive) {
@@ -489,6 +482,7 @@ async function fetchExtensionSource(repoUri, sourceRef, extensionRoot) {
489
482
  logger_1.logger.info(`Validating source code at ${clc.bold(sourceUri)}...`);
490
483
  const archiveUri = `${repoUri}/archive/${sourceRef}.zip`;
491
484
  const tempDirectory = tmp.dirSync({ unsafeCleanup: true });
485
+ const archiveErrorMessage = `Failed to extract archive from ${clc.bold(archiveUri)}. Please check that the repo is public and that the source ref is valid.`;
492
486
  try {
493
487
  const response = await (0, node_fetch_1.default)(archiveUri);
494
488
  if (response.ok) {
@@ -496,9 +490,12 @@ async function fetchExtensionSource(repoUri, sourceRef, extensionRoot) {
496
490
  }
497
491
  }
498
492
  catch (err) {
499
- throw new error_1.FirebaseError(`Failed to fetch extension archive from ${archiveUri}. Please check the repo URI and source ref. ${err}`);
493
+ throw new error_1.FirebaseError(archiveErrorMessage);
500
494
  }
501
495
  const archiveName = fs.readdirSync(tempDirectory.name)[0];
496
+ if (!archiveName) {
497
+ throw new error_1.FirebaseError(archiveErrorMessage);
498
+ }
502
499
  const rootDirectory = path.join(tempDirectory.name, archiveName, extensionRoot);
503
500
  try {
504
501
  (0, localHelper_1.readFile)(path.resolve(rootDirectory, localHelper_1.EXTENSIONS_SPEC_FILE));
@@ -539,12 +536,17 @@ async function uploadExtensionVersionFromGitHubSource(args) {
539
536
  if (!extensionRoot) {
540
537
  const defaultRoot = "/";
541
538
  if (!args.nonInteractive) {
542
- extensionRoot = await promptForValidExtensionRoot(defaultRoot);
539
+ extensionRoot = await promptForExtensionRoot(defaultRoot);
543
540
  }
544
541
  else {
545
542
  extensionRoot = defaultRoot;
546
543
  }
547
544
  }
545
+ const normalizedRoot = path
546
+ .normalize(extensionRoot)
547
+ .replaceAll(/^\/|\/$/g, "")
548
+ .replaceAll(/^(\.\.\/)*/g, "");
549
+ extensionRoot = normalizedRoot || "/";
548
550
  let sourceRef = args.sourceRef;
549
551
  const defaultSourceRef = "HEAD";
550
552
  if (!sourceRef) {
@@ -858,16 +860,3 @@ async function diagnoseAndFixProject(options) {
858
860
  }
859
861
  }
860
862
  exports.diagnoseAndFixProject = diagnoseAndFixProject;
861
- async function canonicalizeRefInput(refInput) {
862
- let inferredRef = refInput;
863
- if (refInput.split("/").length < 2) {
864
- inferredRef = `firebase/${inferredRef}`;
865
- }
866
- if (refInput.split("@").length < 2) {
867
- inferredRef = `${inferredRef}@latest-approved`;
868
- }
869
- const ref = refs.parse(inferredRef);
870
- ref.version = await (0, planner_1.resolveVersion)(ref);
871
- return refs.toExtensionVersionRef(ref);
872
- }
873
- exports.canonicalizeRefInput = canonicalizeRefInput;
@@ -106,14 +106,13 @@ async function createExtensionVersionFromLocalSource(args) {
106
106
  }
107
107
  exports.createExtensionVersionFromLocalSource = createExtensionVersionFromLocalSource;
108
108
  async function createExtensionVersionFromGitHubSource(args) {
109
- var _a;
110
109
  const ref = refs.parse(args.extensionVersionRef);
111
110
  if (!ref.version) {
112
111
  throw new error_1.FirebaseError(`Extension version ref "${args.extensionVersionRef}" must supply a version.`);
113
112
  }
114
113
  const uploadRes = await extensionsPublisherApiClient.post(`/${refs.toExtensionName(ref)}/versions:createFromSource`, {
115
114
  versionId: ref.version,
116
- extensionRoot: (_a = args.extensionRoot) !== null && _a !== void 0 ? _a : "/",
115
+ extensionRoot: args.extensionRoot || "/",
117
116
  githubRepositorySource: {
118
117
  uri: args.repoUri,
119
118
  sourceRef: args.sourceRef,
@@ -78,7 +78,7 @@ async function update(updateOptions) {
78
78
  }
79
79
  exports.update = update;
80
80
  async function updateFromLocalSource(projectId, instanceId, localSource, existingSpec) {
81
- await (0, displayExtensionInfo_1.displayExtInfo)(instanceId, "", existingSpec, false);
81
+ await (0, displayExtensionInfo_1.displayExtensionVersionInfo)({ spec: existingSpec });
82
82
  let source;
83
83
  try {
84
84
  source = await (0, extensionsHelper_1.createSourceFromLocation)(projectId, localSource);
@@ -93,7 +93,7 @@ async function updateFromLocalSource(projectId, instanceId, localSource, existin
93
93
  }
94
94
  exports.updateFromLocalSource = updateFromLocalSource;
95
95
  async function updateFromUrlSource(projectId, instanceId, urlSource, existingSpec) {
96
- await (0, displayExtensionInfo_1.displayExtInfo)(instanceId, "", existingSpec, false);
96
+ await (0, displayExtensionInfo_1.displayExtensionVersionInfo)({ spec: existingSpec });
97
97
  let source;
98
98
  try {
99
99
  source = await (0, extensionsHelper_1.createSourceFromLocation)(projectId, urlSource);
@@ -4,7 +4,7 @@ exports.readOrNull = exports.LocalFileSystem = void 0;
4
4
  const fs_extra_1 = require("fs-extra");
5
5
  const path = require("path");
6
6
  const error_1 = require("../../../error");
7
- const logger_1 = require("../../../../src/logger");
7
+ const logger_1 = require("../../../logger");
8
8
  class LocalFileSystem {
9
9
  constructor(cwd) {
10
10
  this.cwd = cwd;
@@ -1,23 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.discover = void 0;
4
- function discover() {
5
- return {
6
- baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest",
7
- environmentVariables: {
8
- NODE_ENV: "PRODUCTION",
9
- },
10
- installCommand: "npm install",
11
- buildCommand: "npm run build",
12
- startCommand: "npm run start",
13
- afterInstall: (b) => {
14
- console.log("HOOK: AFTER INSTALL");
15
- return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterInstall" });
16
- },
17
- afterBuild(b) {
18
- console.log("HOOK: AFTER BUILD");
19
- return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterBuild" });
20
- },
21
- };
4
+ const node_1 = require("./runtime/node");
5
+ const error_1 = require("../../../error");
6
+ const supportedRuntimes = [new node_1.NodejsRuntime()];
7
+ async function discover(fs, allFrameworkSpecs) {
8
+ try {
9
+ let discoveredRuntime = undefined;
10
+ for (const runtime of supportedRuntimes) {
11
+ if (await runtime.match(fs)) {
12
+ if (!discoveredRuntime) {
13
+ discoveredRuntime = runtime;
14
+ }
15
+ else {
16
+ throw new error_1.FirebaseError(`Conflit occurred as multiple runtimes ${discoveredRuntime.getRuntimeName()}, ${runtime.getRuntimeName()} are discovered in the application.`);
17
+ }
18
+ }
19
+ }
20
+ if (!discoveredRuntime) {
21
+ throw new error_1.FirebaseError(`Unable to determine the specific runtime for the application. The supported runtime options include ${supportedRuntimes
22
+ .map((x) => x.getRuntimeName())
23
+ .join(" , ")}.`);
24
+ }
25
+ const runtimeSpec = await discoveredRuntime.analyseCodebase(fs, allFrameworkSpecs);
26
+ return runtimeSpec;
27
+ }
28
+ catch (error) {
29
+ throw new error_1.FirebaseError(`Failed to identify required specifications to execute the application: ${error}`);
30
+ }
22
31
  }
23
32
  exports.discover = discover;
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodejsRuntime = void 0;
4
+ const filesystem_1 = require("../filesystem");
5
+ const frameworkMatcher_1 = require("../frameworkMatcher");
6
+ const error_1 = require("../../../../error");
7
+ const logger_1 = require("../../../../logger");
8
+ const utils_1 = require("../../../utils");
9
+ const supportedNodeVersions = ["18"];
10
+ const NODE_RUNTIME_ID = "nodejs";
11
+ const PACKAGE_JSON = "package.json";
12
+ const YARN_LOCK = "yarn.lock";
13
+ class NodejsRuntime {
14
+ constructor() {
15
+ this.runtimeRequiredFiles = [PACKAGE_JSON];
16
+ }
17
+ async match(fs) {
18
+ const areAllFilesPresent = await Promise.all(this.runtimeRequiredFiles.map((file) => fs.exists(file)));
19
+ return areAllFilesPresent.every((present) => present);
20
+ }
21
+ getRuntimeName() {
22
+ return NODE_RUNTIME_ID;
23
+ }
24
+ getNodeImage(engine) {
25
+ if (!engine || !engine.node) {
26
+ return "us-docker.pkg.dev/firestack-build/test/run";
27
+ }
28
+ const versionNumber = engine.node;
29
+ if (!supportedNodeVersions.includes(versionNumber)) {
30
+ throw new error_1.FirebaseError(`This integration expects Node version ${(0, utils_1.conjoinOptions)(supportedNodeVersions, "or")}. You're running version ${versionNumber}, which is not compatible.`);
31
+ }
32
+ return "us-docker.pkg.dev/firestack-build/test/run";
33
+ }
34
+ async getPackageManager(fs) {
35
+ try {
36
+ if (await fs.exists(YARN_LOCK)) {
37
+ return "yarn";
38
+ }
39
+ return "npm";
40
+ }
41
+ catch (error) {
42
+ logger_1.logger.error("Failed to check files to identify package manager");
43
+ throw error;
44
+ }
45
+ }
46
+ getDependencies(packageJSON) {
47
+ return Object.assign(Object.assign({}, packageJSON.dependencies), packageJSON.devDependencies);
48
+ }
49
+ packageManagerInstallCommand(packageManager) {
50
+ const packages = [];
51
+ if (packageManager === "yarn") {
52
+ packages.push("yarn");
53
+ }
54
+ if (!packages.length) {
55
+ return undefined;
56
+ }
57
+ return `npm install --global ${packages.join(" ")}`;
58
+ }
59
+ installCommand(fs, packageManager) {
60
+ let installCmd = "npm install";
61
+ if (packageManager === "yarn") {
62
+ installCmd = "yarn install";
63
+ }
64
+ return installCmd;
65
+ }
66
+ async detectedCommands(packageManager, scripts, matchedFramework, fs) {
67
+ return {
68
+ build: this.getBuildCommand(packageManager, scripts, matchedFramework),
69
+ dev: this.getDevCommand(packageManager, scripts, matchedFramework),
70
+ run: await this.getRunCommand(packageManager, scripts, matchedFramework, fs),
71
+ };
72
+ }
73
+ executeScript(packageManager, scriptName) {
74
+ return `${packageManager} run ${scriptName}`;
75
+ }
76
+ executeFrameworkCommand(packageManager, command) {
77
+ if (packageManager === "npm" || packageManager === "yarn") {
78
+ command.cmd = "npx " + command.cmd;
79
+ }
80
+ return command;
81
+ }
82
+ getBuildCommand(packageManager, scripts, matchedFramework) {
83
+ var _a;
84
+ let buildCommand = { cmd: "" };
85
+ if (scripts === null || scripts === void 0 ? void 0 : scripts.build) {
86
+ buildCommand.cmd = this.executeScript(packageManager, "build");
87
+ }
88
+ else if (matchedFramework && ((_a = matchedFramework.commands) === null || _a === void 0 ? void 0 : _a.build)) {
89
+ buildCommand = matchedFramework.commands.build;
90
+ buildCommand = this.executeFrameworkCommand(packageManager, buildCommand);
91
+ }
92
+ return buildCommand.cmd === "" ? undefined : buildCommand;
93
+ }
94
+ getDevCommand(packageManager, scripts, matchedFramework) {
95
+ var _a;
96
+ let devCommand = { cmd: "", env: { NODE_ENV: "dev" } };
97
+ if (scripts === null || scripts === void 0 ? void 0 : scripts.dev) {
98
+ devCommand.cmd = this.executeScript(packageManager, "dev");
99
+ }
100
+ else if (matchedFramework && ((_a = matchedFramework.commands) === null || _a === void 0 ? void 0 : _a.dev)) {
101
+ devCommand = matchedFramework.commands.dev;
102
+ devCommand = this.executeFrameworkCommand(packageManager, devCommand);
103
+ }
104
+ return devCommand.cmd === "" ? undefined : devCommand;
105
+ }
106
+ async getRunCommand(packageManager, scripts, matchedFramework, fs) {
107
+ var _a;
108
+ let runCommand = { cmd: "", env: { NODE_ENV: "production" } };
109
+ if (scripts === null || scripts === void 0 ? void 0 : scripts.start) {
110
+ runCommand.cmd = this.executeScript(packageManager, "start");
111
+ }
112
+ else if (matchedFramework && ((_a = matchedFramework.commands) === null || _a === void 0 ? void 0 : _a.run)) {
113
+ runCommand = matchedFramework.commands.run;
114
+ runCommand = this.executeFrameworkCommand(packageManager, runCommand);
115
+ }
116
+ else if (scripts === null || scripts === void 0 ? void 0 : scripts.main) {
117
+ runCommand.cmd = `node ${scripts.main}`;
118
+ }
119
+ else if (await fs.exists("index.js")) {
120
+ runCommand.cmd = `node index.js`;
121
+ }
122
+ return runCommand.cmd === "" ? undefined : runCommand;
123
+ }
124
+ async analyseCodebase(fs, allFrameworkSpecs) {
125
+ try {
126
+ const packageJSONRaw = await (0, filesystem_1.readOrNull)(fs, PACKAGE_JSON);
127
+ let packageJSON = {};
128
+ if (packageJSONRaw) {
129
+ packageJSON = JSON.parse(packageJSONRaw);
130
+ }
131
+ const packageManager = await this.getPackageManager(fs);
132
+ const nodeImage = this.getNodeImage(packageJSON.engines);
133
+ const dependencies = this.getDependencies(packageJSON);
134
+ const matchedFramework = await (0, frameworkMatcher_1.frameworkMatcher)(NODE_RUNTIME_ID, fs, allFrameworkSpecs, dependencies);
135
+ const runtimeSpec = {
136
+ id: NODE_RUNTIME_ID,
137
+ baseImage: nodeImage,
138
+ packageManagerInstallCommand: this.packageManagerInstallCommand(packageManager),
139
+ installCommand: this.installCommand(fs, packageManager),
140
+ detectedCommands: await this.detectedCommands(packageManager, packageJSON.scripts, matchedFramework, fs),
141
+ };
142
+ return runtimeSpec;
143
+ }
144
+ catch (error) {
145
+ throw new error_1.FirebaseError(`Failed to parse engine: ${error}`);
146
+ }
147
+ }
148
+ }
149
+ exports.NodejsRuntime = NodejsRuntime;
@@ -89,7 +89,9 @@ class DockerDriver {
89
89
  this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase");
90
90
  }
91
91
  execDockerPush(args) {
92
- console.log(`executing docker build: ${args.join(" ")}`);
92
+ console.debug(JSON.stringify({ message: `executing docker build: ${args.join(" ")}` }));
93
+ console.info(JSON.stringify({ foo: "bar", message: `executing docker build: ${args.join(" ")}` }));
94
+ console.error(JSON.stringify({ message: `executing docker build: ${args.join(" ")}` }));
93
95
  return spawn.sync("docker", ["push", ...args], {
94
96
  stdio: ["pipe", "inherit", "inherit"],
95
97
  });
@@ -126,20 +128,28 @@ class DockerDriver {
126
128
  return JSON.parse(fs.readFileSync("./.firebase/.output/bundle.json", "utf8"));
127
129
  }
128
130
  install() {
129
- this.dockerfileBuilder
130
- .fromLastStage(DOCKER_STAGE_INSTALL)
131
- .workdir("/home/firebase/app")
132
- .envs(this.spec.environmentVariables || {})
133
- .copyForFirebase("package.json", ".")
134
- .run(this.spec.installCommand);
135
- this.buildStage(DOCKER_STAGE_INSTALL, ".");
131
+ if (this.spec.installCommand) {
132
+ this.dockerfileBuilder
133
+ .fromLastStage(DOCKER_STAGE_INSTALL)
134
+ .workdir("/home/firebase/app")
135
+ .envs(this.spec.environmentVariables || {})
136
+ .copyForFirebase("package.json", ".");
137
+ if (this.spec.packageManagerInstallCommand) {
138
+ this.dockerfileBuilder.run(this.spec.packageManagerInstallCommand);
139
+ }
140
+ this.dockerfileBuilder.run(this.spec.installCommand);
141
+ this.buildStage(DOCKER_STAGE_INSTALL, ".");
142
+ }
136
143
  }
137
144
  build() {
138
- this.dockerfileBuilder
139
- .fromLastStage(DOCKER_STAGE_BUILD)
140
- .copyForFirebase(".", ".")
141
- .run(this.spec.buildCommand);
142
- this.buildStage(DOCKER_STAGE_BUILD, ".");
145
+ var _a;
146
+ if ((_a = this.spec.detectedCommands) === null || _a === void 0 ? void 0 : _a.build) {
147
+ this.dockerfileBuilder
148
+ .fromLastStage(DOCKER_STAGE_BUILD)
149
+ .copyForFirebase(".", ".")
150
+ .run(this.spec.detectedCommands.build.cmd);
151
+ this.buildStage(DOCKER_STAGE_BUILD, ".");
152
+ }
143
153
  }
144
154
  export(bundle) {
145
155
  var _a;