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

Support many package managers #166

Merged
merged 15 commits into from
Sep 18, 2023
Merged
7 changes: 7 additions & 0 deletions .changeset/two-rocks-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler-action": minor
---

Support for package managers other than npm, such as pnpm and yarn.

fixes #156
41 changes: 40 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,43 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: delete --name wrangler-action-test --force
# END Setup and teardown of Workers w/ Secrets Tests
# END Setup and teardown of Workers w/ Secrets Tests

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

- name: Support npm package manager
uses: ./
with:
workingDirectory: "./test/npm"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run

- name: Install yarn
run: npm i -g yarn

- name: Support yarn package manager
uses: ./
with:
workingDirectory: "./test/yarn"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run

- name: Install pnpm
run: npm i -g pnpm

- name: Support pnpm package manager
uses: ./
with:
workingDirectory: "./test/pnpm"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm-lock.yaml
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ inputs:
vars:
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]"
required: false
74 changes: 58 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
import {
getBooleanInput,
getInput,
getMultilineInput,
setFailed,
info as originalInfo,
error as originalError,
endGroup as originalEndGroup,
error as originalError,
info as originalInfo,
startGroup as originalStartGroup,
getBooleanInput,
setFailed,
} from "@actions/core";
import { execSync, exec } from "node:child_process";
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./utils";
import { exec, execSync } from "node:child_process";
import * as util from "node:util";
import {
PackageManager,
checkWorkingDirectory,
detectPackageManager,
isValidPackageManager,
semverCompare,
} from "./utils";
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 @@ -28,8 +54,24 @@ const config = {
VARS: getMultilineInput("vars"),
COMMANDS: getMultilineInput("command"),
QUIET_MODE: getBooleanInput("quiet"),
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()];

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

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

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

const cmd = `${getNpxCmd()} wrangler ${command} ${
const cmd = `${pkgManagerCmd.exec} wrangler ${command} ${
(command.startsWith("deploy") || command.startsWith("publish")) &&
!command.includes(`--var`)
? getVarArgs()
Expand All @@ -271,9 +313,9 @@ async function wranglerCommands() {
main();

export {
wranglerCommands,
execCommands,
uploadSecrets,
authenticationSetup,
execCommands,
installWrangler,
uploadSecrets,
wranglerCommands,
};
53 changes: 38 additions & 15 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { expect, test, describe } from "vitest";
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./utils";
import path from "node:path";

test("getNpxCmd ", async () => {
process.env.RUNNER_OS = "Windows";
expect(getNpxCmd()).toBe("npx.cmd");

process.env.RUNNER_OS = "Mac";
expect(getNpxCmd()).toBe("npx");

process.env.RUNNER_OS = "Linux";
expect(getNpxCmd()).toBe("npx");

delete process.env.RUNNER_OS;
});
import { describe, expect, test } from "vitest";
import {
checkWorkingDirectory,
detectPackageManager,
isValidPackageManager,
semverCompare,
} from "./utils";

describe("semverCompare", () => {
test("should return false if the second argument is equal to the first argument", () => {
Expand Down Expand Up @@ -43,3 +35,34 @@ 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);
});
25 changes: 21 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { existsSync } from "node:fs";
import * as path from "node:path";

export function getNpxCmd() {
return process.env.RUNNER_OS === "Windows" ? "npx.cmd" : "npx";
}

/**
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
*/
Expand Down Expand Up @@ -33,3 +29,24 @@ 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";
}
10 changes: 10 additions & 0 deletions test/base/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/empty/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default {};
3 changes: 3 additions & 0 deletions test/empty/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "wrangler-action-detect-package-manager-test"
}
4 changes: 4 additions & 0 deletions test/empty/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = "wrangler-action-test"
main = "./index.ts"
compatibility_date = "2023-07-07"
workers_dev = true
10 changes: 10 additions & 0 deletions test/environment/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions test/npm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};

export default {
fetch(request: Request, env: Env) {
const url = new URL(request.url);

if (url.pathname === "/secret-health-check") {
const { SECRET1, SECRET2 } = env;

if (SECRET1 !== "SECRET_1_VALUE" || SECRET2 !== "SECRET_2_VALUE") {
throw new Error("SECRET1 or SECRET2 is not defined");
}

return new Response("OK");
}

// @ts-expect-error
return Response.json({
...request,
headers: Object.fromEntries(request.headers),
});
},
};
10 changes: 10 additions & 0 deletions test/npm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/npm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "wrangler-action-npm-test"
}
4 changes: 4 additions & 0 deletions test/npm/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = "wrangler-action-test"
main = "./index.ts"
compatibility_date = "2023-07-07"
workers_dev = true
Loading
Loading