Skip to content

Commit

Permalink
Refactor to use npm as a fallback if no packageManager specified...
Browse files Browse the repository at this point in the history
and no lockfile is present. Fixes #180
  • Loading branch information
1000hz committed Oct 9, 2023
1 parent bd50d10 commit 528687a
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 105 deletions.
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";
}

0 comments on commit 528687a

Please sign in to comment.