Set shell: true when spawning npm commands (#732)

This commit is contained in:
Elizabeth Craig 2024-04-23 15:18:54 -07:00 коммит произвёл GitHub
Родитель 5dfaea6117
Коммит f324cccc43
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
20 изменённых файлов: 78 добавлений и 260 удалений

8
.github/workflows/pr.yml поставляемый
Просмотреть файл

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [16.x]
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
@ -33,19 +33,23 @@ jobs:
- run: yarn
- name: Code Format Check
if: ${{ matrix.os == 'ubuntu-latest' }}
run: yarn format:check
- name: Check Change Files
if: ${{ matrix.os == 'ubuntu-latest' }}
run: yarn checkchange
# @see https://www.npmjs.com/package/syncpack
- name: Check consistent package.json dep versions
if: ${{ matrix.os == 'ubuntu-latest' }}
run: yarn syncpack list-mismatches
- name: Dependency checks
if: ${{ matrix.os == 'ubuntu-latest' }}
run: yarn lage depcheck
- name: Build, Test, Lint (Linux)
- name: Build, Test, Lint
run: yarn ci --concurrency 2 --verbose
env:
BACKFILL_CACHE_PROVIDER: ${{ secrets.backfill_cache_provider }}

32
.github/workflows/windows-scheduled.yml поставляемый
Просмотреть файл

@ -1,32 +0,0 @@
name: Windows Rolling
on:
schedule:
- cron: "30 5,17 * * *"
jobs:
build:
strategy:
matrix:
node-version: [16.x]
os: [windows-2019]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn
- name: Build, Test (Windows)
run: yarn lage build test --concurrency 2 --verbose
env:
BACKFILL_CACHE_PROVIDER: ${{ secrets.backfill_cache_provider }}
BACKFILL_CACHE_PROVIDER_OPTIONS: ${{ secrets.backfill_cache_provider_options }}

Просмотреть файл

@ -0,0 +1,18 @@
{
"changes": [
{
"type": "minor",
"comment": "Set shell: true when spawning npm commands, due to Node security fix. Also remove custom npm client resolution logic, which should be handled based on the PATH in the shell.",
"packageName": "@lage-run/cli",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
},
{
"type": "minor",
"comment": "Set shell: true when spawning npm commands, due to Node security fix",
"packageName": "@lage-run/scheduler",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
]
}

Просмотреть файл

@ -22,7 +22,6 @@
"dependencies": {
"@lage-run/cache": "^1.1.5",
"@lage-run/config": "^0.3.5",
"@lage-run/find-npm-client": "^0.1.4",
"@lage-run/hasher": "^1.1.0",
"@lage-run/logger": "^1.3.0",
"@lage-run/reporters": "^1.2.7",

Просмотреть файл

@ -102,7 +102,7 @@ async function installLage(cwd: string, workspaceManager: WorkspaceManager, pipe
packageJson.devDependencies.lage = lageVersion;
writePackageJson(cwd, packageJson);
await execa(workspaceManager, ["install"], { stdio: "inherit" });
await execa(workspaceManager, ["install"], { stdio: "inherit", shell: true });
}
}

Просмотреть файл

@ -2,7 +2,6 @@ import type { Command } from "commander";
import { createTargetGraph } from "./createTargetGraph.js";
import { filterArgsForTasks } from "./filterArgsForTasks.js";
import { filterPipelineDefinitions } from "./filterPipelineDefinitions.js";
import { findNpmClient } from "@lage-run/find-npm-client";
import { getConfig, getMaxWorkersPerTask, getMaxWorkersPerTaskFromOptions, getConcurrency } from "@lage-run/config";
import { getPackageInfos, getWorkspaceRoot } from "workspace-tools";
import { initializeReporters } from "../initializeReporters.js";
@ -97,7 +96,7 @@ export async function runAction(options: RunOptions, command: Command) {
options: {
nodeArg: options.nodeArg,
taskArgs,
npmCmd: findNpmClient(config.npmClient),
npmCmd: config.npmClient,
},
},
worker: {

Просмотреть файл

@ -1,7 +1,6 @@
import type { Command } from "commander";
import { createTargetGraph } from "./createTargetGraph.js";
import { filterArgsForTasks } from "./filterArgsForTasks.js";
import { findNpmClient } from "@lage-run/find-npm-client";
import { getConfig, getMaxWorkersPerTask, getMaxWorkersPerTaskFromOptions, getConcurrency } from "@lage-run/config";
import { getPackageInfosAsync, getWorkspaceRoot } from "workspace-tools";
import { filterPipelineDefinitions } from "./filterPipelineDefinitions.js";
@ -93,7 +92,7 @@ export async function watchAction(options: RunOptions, command: Command) {
options: {
nodeArg: options.nodeArg,
taskArgs,
npmCmd: findNpmClient(config.npmClient),
npmCmd: config.npmClient,
},
},
worker: {

Просмотреть файл

@ -2,8 +2,15 @@ import { Monorepo } from "./mock/monorepo.js";
import { filterEntry, parseNdJson } from "./parseNdJson.js";
describe("basics", () => {
let repo: Monorepo | undefined;
afterEach(() => {
repo?.cleanup();
repo = undefined;
});
it("basic test case", () => {
const repo = new Monorepo("basics");
repo = new Monorepo("basics");
repo.init();
repo.addPackage("a", ["b"]);
@ -20,12 +27,10 @@ describe("basics", () => {
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "build", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "test", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "lint", "success"))).toBeFalsy();
repo.cleanup();
});
it("basic with missing script names - logging should not include those targets", () => {
const repo = new Monorepo("basics-missing-scripts");
repo = new Monorepo("basics-missing-scripts");
repo.init();
repo.addPackage("a", ["b"]);
@ -47,12 +52,10 @@ describe("basics", () => {
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "build", "success"))).toBeFalsy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "test", "success"))).toBeFalsy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "lint", "success"))).toBeFalsy();
repo.cleanup();
});
it("basic test case - with task args", () => {
const repo = new Monorepo("basics-with-task-args");
repo = new Monorepo("basics-with-task-args");
repo.init();
repo.addPackage("a", ["b"]);
@ -106,7 +109,25 @@ describe("basics", () => {
expect(jsonOutput4.find((entry) => filterEntry(entry.data, "a", "build", "skipped"))).toBeTruthy();
expect(jsonOutput4.find((entry) => filterEntry(entry.data, "a", "test", "skipped"))).toBeTruthy();
expect(jsonOutput4.find((entry) => filterEntry(entry.data, "a", "lint", "skipped"))).toBeFalsy();
});
repo.cleanup();
it("works in repo with spaces", () => {
repo = new Monorepo("spaces why");
repo.init();
repo.addPackage("a", ["b"]);
repo.addPackage("b");
repo.install();
const results = repo.run("test");
const output = results.stdout + results.stderr;
const jsonOutput = parseNdJson(output);
expect(jsonOutput.find((entry) => filterEntry(entry.data, "b", "build", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "b", "test", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "build", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "test", "success"))).toBeTruthy();
expect(jsonOutput.find((entry) => filterEntry(entry.data, "a", "lint", "success"))).toBeFalsy();
});
});

Просмотреть файл

@ -44,26 +44,27 @@ export class Monorepo {
fs.cpSync(packagePath, path.join(this.root, "node_modules", name), { recursive: true });
}
fs.cpSync(path.join(__dirname, "..", "..", "yarn"), path.dirname(this.yarnPath), { recursive: true });
execa.sync("node", [this.yarnPath, "install"], { cwd: this.root });
fs.cpSync(path.resolve(__dirname, "..", "..", "yarn"), path.dirname(this.yarnPath), { recursive: true });
execa.sync("yarn", ["install"], { cwd: this.root });
}
generateRepoFiles() {
this.commitFiles({
".yarnrc": `yarn-path "${this.yarnPath}"`,
"package.json": {
name: this.name,
name: this.name.replace(/ /g, "-"),
version: "0.1.0",
private: true,
workspaces: ["packages/*"],
scripts: {
bundle: `node ${this.yarnPath} lage bundle --reporter json --log-level silly`,
transpile: `node ${this.yarnPath} lage transpile --reporter json --log-level silly`,
build: `node ${this.yarnPath} lage build --reporter json --log-level silly`,
writeInfo: `node ${this.yarnPath} lage info`,
test: `node ${this.yarnPath} lage test --reporter json --log-level silly`,
lint: `node ${this.yarnPath} lage lint --reporter json --log-level silly`,
clear: `node ${this.yarnPath} lage cache --clear --reporter json --log-level silly`,
extra: `node ${this.yarnPath} lage extra --clear --reporter json --log-level silly`,
bundle: `lage bundle --reporter json --log-level silly`,
transpile: `lage transpile --reporter json --log-level silly`,
build: `lage build --reporter json --log-level silly`,
writeInfo: `lage info`,
test: `lage test --reporter json --log-level silly`,
lint: `lage lint --reporter json --log-level silly`,
clear: `lage cache --clear --reporter json --log-level silly`,
extra: `lage extra --clear --reporter json --log-level silly`,
},
devDependencies: {
lage: path.resolve(__dirname, "..", "..", "..", "lage"),
@ -75,7 +76,8 @@ export class Monorepo {
test: ['build'],
lint: [],
extra: []
}
},
npmClient: 'yarn'
};`,
".gitignore": "node_modules",
});
@ -151,6 +153,7 @@ export class Monorepo {
run(command: string, args?: string[], silent?: boolean) {
return execa.sync("yarn", [...(silent === true ? ["--silent"] : []), command, ...(args || [])], {
cwd: this.root,
shell: true,
});
}

Просмотреть файл

@ -1,80 +0,0 @@
{
"name": "@lage-run/find-npm-client",
"entries": [
{
"date": "Thu, 21 Dec 2023 08:37:41 GMT",
"version": "0.1.4",
"tag": "@lage-run/find-npm-client_v0.1.4",
"comments": {
"none": [
{
"author": "elcraig@microsoft.com",
"package": "@lage-run/find-npm-client",
"commit": "0752bad677868d982719f792f6692ab43ad325e0",
"comment": "Use ranges for devDependencies"
}
]
}
},
{
"date": "Tue, 01 Nov 2022 22:25:59 GMT",
"tag": "@lage-run/find-npm-client_v0.1.4",
"version": "0.1.4",
"comments": {
"patch": [
{
"author": "kchau@microsoft.com",
"package": "@lage-run/find-npm-client",
"commit": "1664f38eca34da2d51b6a581c92caba5fc51e5fd",
"comment": "adds import extensions of .js to prepare of esmodule switchover"
}
]
}
},
{
"date": "Tue, 01 Nov 2022 20:43:17 GMT",
"tag": "@lage-run/find-npm-client_v0.1.3",
"version": "0.1.3",
"comments": {
"patch": [
{
"author": "kchau@microsoft.com",
"package": "@lage-run/find-npm-client",
"commit": "d93ffd227f46718fafd1062f9107bde2c98d4f37",
"comment": "cleaning up the tsconfig files"
}
]
}
},
{
"date": "Mon, 31 Oct 2022 21:27:52 GMT",
"tag": "@lage-run/find-npm-client_v0.1.2",
"version": "0.1.2",
"comments": {
"patch": [
{
"author": "kchau@microsoft.com",
"package": "@lage-run/find-npm-client",
"commit": "e8946dc08fb76dc616d61f3b67b0ca99e15d9a7e",
"comment": "adds depcheck and fixes"
}
]
}
},
{
"date": "Sat, 01 Oct 2022 16:21:41 GMT",
"tag": "@lage-run/find-npm-client_v0.1.1",
"version": "0.1.1",
"comments": {
"patch": [
{
"author": "ken@gizzar.com",
"package": "@lage-run/find-npm-client",
"commit": "8f3016548ae7d9b05d0356a5a52d7602843e9ab2",
"comment": "new find-npm-client package"
}
]
}
}
]
}

Просмотреть файл

@ -1,37 +0,0 @@
# Change Log - @lage-run/find-npm-client
This log was last generated on Tue, 01 Nov 2022 22:25:59 GMT and should not be manually modified.
<!-- Start content -->
## 0.1.4
Tue, 01 Nov 2022 22:25:59 GMT
### Patches
- adds import extensions of .js to prepare of esmodule switchover (kchau@microsoft.com)
## 0.1.3
Tue, 01 Nov 2022 20:43:17 GMT
### Patches
- cleaning up the tsconfig files (kchau@microsoft.com)
## 0.1.2
Mon, 31 Oct 2022 21:27:52 GMT
### Patches
- adds depcheck and fixes (kchau@microsoft.com)
## 0.1.1
Sat, 01 Oct 2022 16:21:41 GMT
### Patches
- new find-npm-client package (ken@gizzar.com)

Просмотреть файл

@ -1,9 +0,0 @@
# @lage-run/cli
This is the command line interface (CLI) for lage. It contains the logic to tie everything together:
1. parses CLI arguments via `commander`
2. initializes the various commands
3. for running the targets, there are some reserved options for lage, but the rest are passed through to the scripts
4. figures out the filtered packages as entry points (dependencies are also run, unless --no-dependents are specified)
5. scheduler, reporter, cache, logger are initialized and run

Просмотреть файл

@ -1 +0,0 @@
module.exports = require("@lage-run/monorepo-scripts/config/jest.config.js");

Просмотреть файл

@ -1,22 +0,0 @@
{
"name": "@lage-run/find-npm-client",
"version": "0.1.4",
"description": "Finds the npm client for Lage",
"repository": {
"url": "https://github.com/microsoft/lage"
},
"license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"build": "tsc",
"start": "tsc -w --preserveWatchOutput",
"lint": "monorepo-scripts lint"
},
"devDependencies": {
"@lage-run/monorepo-scripts": "*"
},
"publishConfig": {
"access": "public"
}
}

Просмотреть файл

@ -1,31 +0,0 @@
import path from "path";
import fs from "fs";
export function findNpmClient(npmClient: string) {
const found = findInPath(npmClient);
if (!found) {
throw new Error(`npm client not found: ${npmClient}`);
}
return found;
}
function findInPath(target: string) {
const pathEnv = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
const pathExtEnv = Object.keys(process.env).find((key) => key.toLowerCase() === "pathext") ?? "PATHEXT";
const envPath = process.env[pathEnv] ?? "";
const pathExt = process.env[pathExtEnv] ?? "";
for (const search of envPath.split(path.delimiter)) {
const found = pathExt
.split(path.delimiter)
.map((ext) => path.join(search, `${target}${ext}`))
.find((p) => fs.existsSync(p));
if (found) {
return found;
}
}
}

Просмотреть файл

@ -1 +0,0 @@
export { findNpmClient } from "./findNpmClient.js";

Просмотреть файл

@ -1,7 +0,0 @@
{
"extends": "../tsconfig.lage2.json",
"compilerOptions": {
"outDir": "./lib"
},
"include": ["src/cli.ts", "src/index.ts"]
}

Просмотреть файл

@ -211,6 +211,7 @@ export class Monorepo {
run(command: string, args?: string[], silent?: boolean) {
return execa("yarn", [...(silent === true ? ["--silent"] : []), command, ...(args || [])], {
cwd: this.root,
shell: true,
});
}

Просмотреть файл

@ -1,9 +1,8 @@
import { existsSync } from "fs";
import { join } from "path";
import { readFile } from "fs/promises";
import { spawn } from "child_process";
import { spawn, type ChildProcess } from "child_process";
import type { TargetRunner, TargetRunnerOptions } from "@lage-run/scheduler-types";
import type { ChildProcess } from "child_process";
import type { Target } from "@lage-run/target-graph";
export interface NpmScriptRunnerOptions {
@ -33,9 +32,7 @@ export interface NpmScriptRunnerOptions {
export class NpmScriptRunner implements TargetRunner {
static gracefulKillTimeout = 2500;
constructor(private options: NpmScriptRunnerOptions) {
this.validateOptions(options);
}
constructor(private options: NpmScriptRunnerOptions) {}
private getNpmArgs(task: string, taskTargs: string[]) {
const extraArgs = taskTargs.length > 0 ? ["--", ...taskTargs] : [];
@ -49,12 +46,6 @@ export class NpmScriptRunner implements TargetRunner {
return !!packageJson.scripts?.[task];
}
private validateOptions(options: NpmScriptRunnerOptions) {
if (!existsSync(options.npmCmd)) {
throw new Error(`NPM Script Runner: ${this.options.npmCmd} does not exist`);
}
}
async shouldRun(target: Target) {
// By convention, do not run anything if there is no script for this task defined in package.json (counts as "success")
return await this.hasNpmScript(target);
@ -112,9 +103,11 @@ export class NpmScriptRunner implements TargetRunner {
childProcess = spawn(npmCmd, npmRunArgs, {
cwd: target.cwd,
stdio: ["inherit", "pipe", "pipe"],
// This is required for Windows due to https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
shell: true,
env: {
...(process.stdout.isTTY && { FORCE_COLOR: "1" }), // allow user env to override this
...process.env,
...(process.stdout.isTTY && { FORCE_COLOR: "1" }),
...(npmRunNodeOptions && { NODE_OPTIONS: npmRunNodeOptions }),
LAGE_PACKAGE_NAME: target.packageName,
LAGE_TASK: target.task,

Просмотреть файл

@ -17,6 +17,7 @@ if (spawnCommand.endsWith(".js")) {
console.log(`Running ${command} as: ${spawnCommand} ${spawnArgs.join(" ")}`);
cp = spawn(spawnCommand, spawnArgs, {
stdio: "inherit",
shell: true,
});
}