Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use npm as fallback if no packageManager specified #190

Merged
merged 1 commit into from
Oct 10, 2023
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
5 changes: 5 additions & 0 deletions .changeset/slimy-cameras-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler-action": patch
---

Fixed action failure when no `packageManager` specified and no lockfile is found. The action now falls back to using npm.
8 changes: 8 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ jobs:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run

- name: Support unspecified packageManager with no lockfile
uses: ./
with:
workingDirectory: "./test/empty"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run

- name: Support npm package manager
uses: ./
with:
Expand Down
3 changes: 2 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ inputs:
description: "A string of environment variable names, separated by newlines. These will be bound to your Worker using the values of matching environment variables declared in `env` of this workflow."
required: false
packageManager:
description: "The name of the package manager to install and run wrangler. If not provided, it will be detected via the lock file. Valid values: [npm, pnpm, yarn]"
description: "The package manager you'd like to use to install and run wrangler. If not specified, a value will be inferred based on the presence of a lockfile. Valid values: [npm, pnpm, yarn]"
required: false
default: npm
56 changes: 10 additions & 46 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,12 @@ import {
} from "@actions/core";
import { exec, execSync } from "node:child_process";
import * as util from "node:util";
import {
PackageManager,
checkWorkingDirectory,
detectPackageManager,
isValidPackageManager,
semverCompare,
} from "./utils";
import { checkWorkingDirectory, semverCompare } from "./utils";
import { getPackageManager } from "./packageManagers";
const execAsync = util.promisify(exec);

const DEFAULT_WRANGLER_VERSION = "3.5.1";

interface PackageManagerCommands {
install: string;
exec: string;
}

const PACKAGE_MANAGER_COMMANDS = {
npm: {
install: "npm i",
exec: "npx",
},
yarn: {
install: "yarn add",
exec: "yarn",
},
pnpm: {
install: "pnpm add",
exec: "pnpm exec",
},
} as const satisfies Readonly<Record<PackageManager, PackageManagerCommands>>;

/**
* A configuration object that contains all the inputs & immutable state for the action.
*/
Expand All @@ -57,20 +32,9 @@ const config = {
PACKAGE_MANAGER: getInput("packageManager"),
} as const;

function realPackageManager(): PackageManager {
if (isValidPackageManager(config.PACKAGE_MANAGER)) {
return config.PACKAGE_MANAGER;
}

const packageManager = detectPackageManager(config.workingDirectory);
if (packageManager !== null) {
return packageManager;
}

throw new Error("Package manager is not detected");
}

const pkgManagerCmd = PACKAGE_MANAGER_COMMANDS[realPackageManager()];
const packageManager = getPackageManager(config.PACKAGE_MANAGER, {
workingDirectory: config.workingDirectory,
});

function info(message: string, bypass?: boolean): void {
if (!config.QUIET_MODE || bypass) {
Expand Down Expand Up @@ -136,7 +100,7 @@ function installWrangler() {
);
}
startGroup("📥 Installing Wrangler");
const command = `${pkgManagerCmd.install} wrangler@${config["WRANGLER_VERSION"]}`;
const command = `${packageManager.install} wrangler@${config["WRANGLER_VERSION"]}`;
info(`Running command: ${command}`);
execSync(command, { cwd: config["workingDirectory"], env: process.env });
info(`✅ Wrangler installed`, true);
Expand All @@ -157,7 +121,7 @@ async function execCommands(commands: string[], cmdType: string) {
try {
const arrPromises = commands.map(async (command) => {
const cmd = command.startsWith("wrangler")
? `${pkgManagerCmd.exec} ${command}`
? `${packageManager.exec} ${command}`
: command;

info(`🚀 Executing command: ${cmd}`);
Expand Down Expand Up @@ -198,7 +162,7 @@ async function legacyUploadSecrets(
const arrPromises = secrets
.map((secret) => {
const command = `echo ${getSecret(secret)} | ${
pkgManagerCmd.exec
packageManager.exec
} wrangler secret put ${secret}`;
return environment ? command.concat(` --env ${environment}`) : command;
})
Expand Down Expand Up @@ -240,7 +204,7 @@ async function uploadSecrets() {
const secretCmd = `echo "${JSON.stringify(secretObj).replaceAll(
'"',
'\\"',
)}" | ${pkgManagerCmd.exec} wrangler secret:bulk ${environmentSuffix}`;
)}" | ${packageManager.exec} wrangler secret:bulk ${environmentSuffix}`;

execSync(secretCmd, {
cwd: workingDirectory,
Expand Down Expand Up @@ -289,7 +253,7 @@ async function wranglerCommands() {
command = command.concat(` --env ${environment}`);
}

const cmd = `${pkgManagerCmd.exec} wrangler ${command} ${
const cmd = `${packageManager.exec} wrangler ${command} ${
(command.startsWith("deploy") || command.startsWith("publish")) &&
!command.includes(`--var`)
? getVarArgs()
Expand Down
76 changes: 76 additions & 0 deletions src/packageManagers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, expect, test } from "vitest";
import { getPackageManager } from "./packageManagers";

describe("getPackageManager", () => {
test("should use provided value instead of inferring from lockfile", () => {
expect(getPackageManager("npm", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
"install": "npm i",
}
`);

expect(getPackageManager("yarn", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "yarn",
"install": "yarn add",
}
`);

expect(getPackageManager("pnpm", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "pnpm exec",
"install": "pnpm add",
}
`);
});

test("should use npm if no value provided and package-lock.json exists", () => {
expect(getPackageManager("", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
"install": "npm i",
}
`);
});

test("should use yarn if no value provided and yarn.lock exists", () => {
expect(getPackageManager("", { workingDirectory: "test/yarn" }))
.toMatchInlineSnapshot(`
{
"exec": "yarn",
"install": "yarn add",
}
`);
});

test("should use pnpm if no value provided and pnpm-lock.yaml exists", () => {
expect(getPackageManager("", { workingDirectory: "test/pnpm" }))
.toMatchInlineSnapshot(`
{
"exec": "pnpm exec",
"install": "pnpm add",
}
`);
});

test("should use npm if no value provided and no lockfile is present", () => {
expect(getPackageManager("", { workingDirectory: "test/empty" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
"install": "npm i",
}
`);
});

test("should throw if an invalid value is provided", () => {
expect(() =>
getPackageManager("cargo", { workingDirectory: "test/npm" }),
).toThrowError();
});
});
61 changes: 61 additions & 0 deletions src/packageManagers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { existsSync } from "node:fs";
import * as path from "node:path";

interface PackageManager {
install: string;
exec: string;
}

const PACKAGE_MANAGERS = {
npm: {
install: "npm i",
exec: "npx",
},
yarn: {
install: "yarn add",
exec: "yarn",
},
pnpm: {
install: "pnpm add",
exec: "pnpm exec",
},
} as const satisfies Readonly<Record<string, PackageManager>>;

type PackageManagerValue = keyof typeof PACKAGE_MANAGERS;

function detectPackageManager(
workingDirectory = ".",
): PackageManagerValue | null {
if (existsSync(path.join(workingDirectory, "package-lock.json"))) {
return "npm";
}
if (existsSync(path.join(workingDirectory, "yarn.lock"))) {
return "yarn";
}
if (existsSync(path.join(workingDirectory, "pnpm-lock.yaml"))) {
return "pnpm";
}
return null;
}

function assertValidPackageManagerValue(
name: string,
): asserts name is PackageManagerValue | "" {
if (name && !Object.keys(PACKAGE_MANAGERS).includes(name)) {
throw new TypeError(
`invalid value provided for "packageManager": ${name}
Value must be one of: [${Object.keys(PACKAGE_MANAGERS).join(", ")}]`,
);
}
}

export function getPackageManager(
name: string,
{ workingDirectory = "." }: { workingDirectory?: string } = {},
) {
assertValidPackageManagerValue(name);

return PACKAGE_MANAGERS[
name || detectPackageManager(workingDirectory) || "npm"
];
}
38 changes: 1 addition & 37 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import path from "node:path";
import { describe, expect, test } from "vitest";
import {
checkWorkingDirectory,
detectPackageManager,
isValidPackageManager,
semverCompare,
} from "./utils";
import { checkWorkingDirectory, semverCompare } from "./utils";

describe("semverCompare", () => {
test("should return false if the second argument is equal to the first argument", () => {
Expand Down Expand Up @@ -35,34 +30,3 @@ describe("checkWorkingDirectory", () => {
);
});
});

describe("detectPackageManager", () => {
test("should return name of package manager for current workspace", () => {
expect(detectPackageManager()).toBe("npm");
});

test("should return npm if package-lock.json exists", () => {
expect(detectPackageManager("test/npm")).toBe("npm");
});

test("should return yarn if yarn.lock exists", () => {
expect(detectPackageManager("test/yarn")).toBe("yarn");
});

test("should return pnpm if pnpm-lock.yaml exists", () => {
expect(detectPackageManager("test/pnpm")).toBe("pnpm");
});

test("should return null if no package manager is detected", () => {
expect(detectPackageManager("test/empty")).toBe(null);
});
});

test("isValidPackageManager", () => {
expect(isValidPackageManager("npm")).toBe(true);
expect(isValidPackageManager("pnpm")).toBe(true);
expect(isValidPackageManager("yarn")).toBe(true);

expect(isValidPackageManager("")).toBe(false);
expect(isValidPackageManager("ppnpm")).toBe(false);
});
21 changes: 0 additions & 21 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,3 @@ export function checkWorkingDirectory(workingDirectory = ".") {
throw new Error(`Directory ${workingDirectory} does not exist.`);
}
}

export type PackageManager = "npm" | "yarn" | "pnpm";

export function detectPackageManager(
workingDirectory = ".",
): PackageManager | null {
if (existsSync(path.join(workingDirectory, "package-lock.json"))) {
return "npm";
}
if (existsSync(path.join(workingDirectory, "yarn.lock"))) {
return "yarn";
}
if (existsSync(path.join(workingDirectory, "pnpm-lock.yaml"))) {
return "pnpm";
}
return null;
}

export function isValidPackageManager(name: string): name is PackageManager {
return name === "npm" || name === "yarn" || name === "pnpm";
}