Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion src/commands/functions-kits-install.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
let wrapSpawnStub: sinon.SinonStub;
let spawnWithOutputStub: sinon.SinonStub;
let loggerInfoStub: sinon.SinonStub;
let loggerWarnStub: sinon.SinonStub;

beforeEach(() => {
(command as unknown as { befores: unknown[] }).befores = [];
Expand All @@ -46,7 +47,7 @@
sinon.stub(fs, "writeJson").resolves();
sinon.stub(fs, "writeFile").resolves();
loggerInfoStub = sinon.stub(logger, "info");
sinon.stub(logger, "warn");
loggerWarnStub = sinon.stub(logger, "warn");
});

afterEach(() => {
Expand Down Expand Up @@ -436,6 +437,14 @@
["run", "build"],
"/abs/path",
);
expect(loggerInfoStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/Running npm install\.\.\./),
);
expect(loggerInfoStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/Building TypeScript source\.\.\./),
);
});

it("should run npm install with --ignore-scripts for third-party kit", async () => {
Expand All @@ -452,6 +461,14 @@
["run", "build"],
"/abs/path",
);
expect(loggerInfoStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/Running npm install --ignore-scripts\.\.\./),
);
expect(loggerInfoStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/Building TypeScript source\.\.\./),
);
});

it("should throw FirebaseError if npm install fails", async () => {
Expand All @@ -476,13 +493,13 @@

describe("promptExistingInstanceForProject", () => {
it("should throw if kit has no instances configured", async () => {
const mockOptions = { project: "my-project" } as any;

Check warning on line 496 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type

Check warning on line 496 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
const kit = {
kit: "my-kit",
instances: {},
} as unknown as ValidatedKitSingle;

await expect(promptExistingInstanceForProject(mockOptions, kit)).to.be.rejectedWith(

Check warning on line 502 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe argument of type `any` assigned to a parameter of type `FunctionsKitsInstallOptions`
FirebaseError,
/Kit 'my-kit' has no instances configured\./,
);
Expand All @@ -490,7 +507,7 @@

it("should suggest deploy command directly when only one instance exists", async () => {
const selectStub = sinon.stub(prompt, "select");
const mockOptions = { project: "my-project" } as any;

Check warning on line 510 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type

Check warning on line 510 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
const kit = {
kit: "my-kit",
instances: {
Expand All @@ -498,7 +515,7 @@
},
} as unknown as ValidatedKitSingle;

await promptExistingInstanceForProject(mockOptions, kit);

Check warning on line 518 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe argument of type `any` assigned to a parameter of type `FunctionsKitsInstallOptions`

expect(selectStub).to.not.have.been.called;
expect(loggerInfoStub).to.have.been.calledWith(
Expand All @@ -509,7 +526,7 @@

it("should prompt to select instance when multiple instances exist and nonInteractive is false", async () => {
const selectStub = sinon.stub(prompt, "select").resolves("inst-2");
const mockOptions = { project: "my-project", nonInteractive: false } as any;

Check warning on line 529 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unexpected any. Specify a different type

Check warning on line 529 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
const kit = {
kit: "my-kit",
instances: {
Expand All @@ -518,7 +535,7 @@
},
} as unknown as ValidatedKitSingle;

await promptExistingInstanceForProject(mockOptions, kit);

Check warning on line 538 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe argument of type `any` assigned to a parameter of type `FunctionsKitsInstallOptions`

expect(selectStub).to.have.been.calledOnce;
expect(selectStub).to.have.been.calledWith(
Expand All @@ -538,7 +555,7 @@

it("should suggest deploy command with instance placeholder when multiple instances exist and nonInteractive is true", async () => {
const selectStub = sinon.stub(prompt, "select");
const mockOptions = { project: "my-project", nonInteractive: true } as any;

Check warning on line 558 in src/commands/functions-kits-install.spec.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Unsafe assignment of an `any` value
const kit = {
kit: "my-kit",
instances: {
Expand Down Expand Up @@ -788,6 +805,11 @@
},
],
});

expect(loggerInfoStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/Function kit firestore-bigquery-export successfully installed\./),
);
});

it("should prompt and allow custom kit ID and instance ID", async () => {
Expand Down Expand Up @@ -1038,6 +1060,10 @@
default: false,
nonInteractive: true,
});
expect(loggerWarnStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/does not have an npm-shrinkwrap\.json file/),
);
});

it("should prompt confirmation when a third-party kit has npm-shrinkwrap.json", async () => {
Expand All @@ -1064,6 +1090,10 @@
default: false,
nonInteractive: true,
});
expect(loggerWarnStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/is a third-party kit/),
);
});

it("should prompt confirmation when a third-party kit lacks npm-shrinkwrap.json", async () => {
Expand Down Expand Up @@ -1091,6 +1121,14 @@
default: false,
nonInteractive: true,
});
expect(loggerWarnStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/is a third-party kit/),
);
expect(loggerWarnStub).to.have.been.calledWith(
sinon.match(/functions:/),
sinon.match(/does not have an npm-shrinkwrap\.json file/),
);
});

it("should cancel installation if user declines confirmation for first-party kit without shrinkwrap", async () => {
Expand Down
22 changes: 10 additions & 12 deletions src/commands/functions-kits-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Command } from "../command";
import { FirebaseError, getErrMsg } from "../error";
import { KitFunctionConfig, FunctionsConfig } from "../firebaseConfig";
import { getProjectId } from "../projectUtils";
import { logLabeledBullet, logLabeledSuccess } from "../utils";
import { logLabeledBullet, logLabeledSuccess, logLabeledWarning } from "../utils";

import {
isKitConfig,
Expand Down Expand Up @@ -306,19 +306,17 @@ export async function promptSecurityConfirmation(
): Promise<boolean> {
const isThirdParty = isThirdPartyPackage(packageName);
if (isThirdParty) {
logger.warn(
clc.yellow(
`Warning: Package ${clc.bold(packageName)} is a third-party kit (outside the @firebase-functions-kits scope).`,
),
logLabeledWarning(
"functions",
`Package ${clc.bold(packageName)} is a third-party kit (outside the @firebase-functions-kits scope).`,
);
}

const hasShrinkwrap = await self.checkPackageHasShrinkwrap(rawPkgName);
if (!hasShrinkwrap) {
logger.warn(
clc.yellow(
`Warning: Package ${clc.bold(packageName)} does not have an npm-shrinkwrap.json file. npm-shrinkwrap guarantees that you deploy the same version of dependencies that the publisher tested against. Since this kit does not have an npm-shrinkwrap, it is possible that deploys or updates may introduce bugs or vulnerabilities in newer dependency versions that the publisher did not test against.`,
),
logLabeledWarning(
"functions",
`Package ${clc.bold(packageName)} does not have an npm-shrinkwrap.json file. npm-shrinkwrap guarantees that you deploy the same version of dependencies that the publisher tested against. Since this kit does not have an npm-shrinkwrap, it is possible that deploys or updates may introduce bugs or vulnerabilities in newer dependency versions that the publisher did not test against.`,
);
}

Expand Down Expand Up @@ -560,14 +558,14 @@ export async function buildAndInstallKit(
isThirdParty: boolean,
): Promise<void> {
const installArgs = isThirdParty ? ["install", "--ignore-scripts"] : ["install"];
logger.info(clc.bold(`Running npm ${installArgs.join(" ")}...`));
logLabeledBullet("functions", `Running npm ${installArgs.join(" ")}...`);
try {
await wrapSpawn("npm", installArgs, absSourcePath);
} catch (err: unknown) {
throw new FirebaseError(`NPM install failed: ${getErrMsg(err)}`);
}

logger.info(clc.bold("Building TypeScript source..."));
logLabeledBullet("functions", "Building TypeScript source...");
try {
await wrapSpawn("npm", ["run", "build"], absSourcePath);
} catch (err: unknown) {
Expand Down Expand Up @@ -686,5 +684,5 @@ export const command = new Command("functions:kits:install")

addKitToConfig(options.config, kitId, instanceId, packageName, sourcePath, configDirPath);

logger.info(clc.green(`✔ Function kit ${clc.bold(kitId)} successfully installed.`));
logLabeledSuccess("functions", `Function kit ${clc.bold(kitId)} successfully installed.`);
});
Loading