Skip to content

Commit

Permalink
feat: add --loose flag
Browse files Browse the repository at this point in the history
This flag, when paired with `--all`, tells `midnight-smoker` to ignore any missing scripts.

Closes #257

fix(yarn): fix workspace-related problems
  • Loading branch information
boneskull committed Aug 15, 2023
1 parent f1dc282 commit c2ef61f
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 50 deletions.
7 changes: 4 additions & 3 deletions __snapshots__/cli.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ Behavior:
--bail When running scripts, halt on first error [boolean]
--include-root Include the workspace root; must provide '--all' [boolean]
--json Output JSON only [boolean]
--workspace Run script in a specific workspace or workspaces [array]
--pm Run script(s) with a specific package manager;
<npm|yarn|pnpm>[@version] [array] [default: "npm@latest"]
--loose Ignore missing scripts (used with --all) [boolean]
--workspace Run script in a specific workspace or workspaces [array]
Options:
--version Show version number [boolean]
Expand Down Expand Up @@ -120,7 +121,6 @@ exports[
{
"pkgName": "fail",
"script": "smoke",
"error": {},
"rawResult": {
"shortMessage": "Command failed with exit code 1: <path/to/>/bin/node <path/to/>/.bin/corepack npm@<version> run smoke",
"command": "<path/to/>/bin/node <path/to/>/.bin/corepack npm@<version> run smoke",
Expand All @@ -133,7 +133,8 @@ exports[
"isCanceled": false,
"killed": false
},
"cwd": "<cwd>"
"cwd": "<cwd>",
"error": {}
}
],
"manifest": {
Expand Down
26 changes: 20 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SmokerConfig, readConfig} from './config';
import {Events, type SmokerEvents} from './events';
import {Smoker} from './smoker';
import {normalizeStringArray, readPackageJson} from './util';
import {SmokerError} from './error';

const BEHAVIOR_GROUP = 'Behavior:';

Expand Down Expand Up @@ -122,19 +123,32 @@ async function main(args: string[]): Promise<void> {
hidden: true,
type: 'boolean',
},
workspace: {
describe: 'Run script in a specific workspace or workspaces',
group: BEHAVIOR_GROUP,
default: config.workspace,
...arrayOptConfig,
},
pm: {
describe:
'Run script(s) with a specific package manager; <npm|yarn|pnpm>[@version]',
group: BEHAVIOR_GROUP,
default: config.pm ?? 'npm@latest',
...arrayOptConfig,
},
loose: {
describe: 'Ignore missing scripts (used with --all)',
type: 'boolean',
default: config.loose,
group: BEHAVIOR_GROUP,
implies: 'all',
},
workspace: {
describe: 'Run script in a specific workspace or workspaces',
group: BEHAVIOR_GROUP,
default: config.workspace,
...arrayOptConfig,
},
})
.check((argv) => {
if (argv.pm?.some((pm) => pm.startsWith('pnpm'))) {
throw new SmokerError('pnpm is currently unsupported');
}
return true;
});
},
async (argv) => {
Expand Down
49 changes: 48 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,72 @@ const DEFAULT_OPTS: Readonly<LilconfigOpts> = Object.freeze({
});

export interface SmokerConfig {
/**
* Add an extra package to the list of packages to be installed.
*/
add?: string[] | string;
/**
* Operate on all workspaces.
*
* The root workspace is omitted unless `includeRoot` is `true`.
*/
all?: boolean;
/**
* Fail on first script failure.
*/
bail?: boolean;
/**
* Operate on the root workspace.
*
* Only has an effect if `all` is `true`.
*/
includeRoot?: boolean;
/**
* Output JSON only
*/
json?: boolean;
/**
* Do not delete temp directories after completion
*/
linger?: boolean;
/**
* Verbose logging
*/
verbose?: boolean;
/**
* One or more workspaces to run scripts in
*/
workspace?: string[] | string;
/**
* Package manager(s) to use
*/
pm?: string[] | string;
/**
* Script(s) to run.
*
* Alias of `scripts`
*/
script?: string[] | string;
/**
* Script(s) to run.
*
* Alias of `script`
*/
scripts?: string[] | string;
/**
* If `true`, fail if a workspace is missing a script
*/
loose?: boolean;
}

/**
* @internal
*/
export interface NormalizedSmokerConfig extends SmokerConfig {
add?: string[];
workspace?: string[];
pm?: string[];
script?: string[];

scripts?: never;
}

Expand Down
28 changes: 19 additions & 9 deletions src/pm/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ export abstract class GenericNpmPackageManager implements PackageManager {
throw new TypeError('(runScript) "manifest" arg is required');
}
const {script, packedPkg} = manifest;
const npmArgs = ['run', script];
const args = ['run', script];
const {pkgName, installPath: cwd} = packedPkg;

let result: RunScriptResult;
try {
const rawResult = await this.executor.exec(npmArgs, {
const rawResult = await this.executor.exec(args, {
cwd,
});
result = {pkgName, script, rawResult, cwd};
Expand All @@ -47,29 +47,39 @@ export abstract class GenericNpmPackageManager implements PackageManager {
result = {
pkgName,
script,
error: new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed: ${error.message}`,
),
rawResult: error,
cwd,
};
if (this.opts.loose && /missing script:/i.test(error.stderr)) {
result.skipped = true;
} else {
result.error = new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed: ${error.message}`,
);
}
}

if (!result.error && result.rawResult.failed) {
let message: string;
if (!result.error && !result.skipped && result.rawResult.failed) {
if (
result.rawResult.stderr &&
/missing script:/i.test(result.rawResult.stderr)
) {
message = `(runScript) Script "${script}" in package "${pkgName}" failed; script not found`;
if (!this.opts.loose) {
result.error = new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed; script not found`,
);
} else {
result.skipped = true;
}
} else {
let message: string;
if (result.rawResult.exitCode) {
message = `(runScript) Script "${script}" in package "${pkgName}" failed with exit code ${result.rawResult.exitCode}: ${result.rawResult.all}`;
} else {
message = `(runScript) Script "${script}" in package "${pkgName}" failed: ${result.rawResult.all}`;
}
result.error = new SmokerError(message);
}
result.error = new SmokerError(message);
}

if (result.error) {
Expand Down
5 changes: 5 additions & 0 deletions src/pm/pm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export interface PackageManagerOpts {
* If `true`, show STDERR/STDOUT from the package manager
*/
verbose?: boolean;

/**
* If `true`, ignore missing scripts
*/
loose?: boolean;
}

export interface PackOpts {
Expand Down
76 changes: 74 additions & 2 deletions src/pm/yarn-berry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import createDebug from 'debug';
import path from 'node:path';
import type {SemVer} from 'semver';
import {SmokerError} from '../error';
import type {InstallManifest, PackedPackage} from '../types';
import type {
InstallManifest,
PackedPackage,
RunManifest,
RunScriptResult,
} from '../types';
import {readPackageJson} from '../util';
import type {CorepackExecutor} from './corepack';
import type {ExecError, ExecResult} from './executor';
Expand Down Expand Up @@ -193,7 +198,7 @@ export class YarnBerry extends YarnClassic implements PackageManager {
'list',
'--json',
]);
const lines = stdout.split('\r?\n');
const lines = stdout.split(/\r?\n/);
workspaceInfo = lines.reduce(
(acc, line) => {
const {name, location} = JSON.parse(line);
Expand Down Expand Up @@ -282,6 +287,73 @@ export class YarnBerry extends YarnClassic implements PackageManager {

return {packedPkgs, tarballRootDir: dest};
}

public async runScript(manifest: RunManifest): Promise<RunScriptResult> {
if (!manifest) {
throw new TypeError('(runScript) "manifest" arg is required');
}
const {script, packedPkg} = manifest;
const args = ['run', script];
const {pkgName, installPath: cwd} = packedPkg;
let result: RunScriptResult;
try {
const rawResult = await this.executor.exec(args, {
cwd,
});
result = {pkgName, script, rawResult, cwd};
} catch (err) {
const error = err as ExecError;
result = {
pkgName,
script,
rawResult: error,
cwd,
};
if (
this.opts.loose &&
/Couldn't find a script named/i.test(error.stdout)
) {
result.skipped = true;
} else {
result.error = new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed: ${error.message}`,
);
}
}

if (!result.error && !result.skipped && result.rawResult.failed) {
let message: string;
if (
result.rawResult.stdout &&
/Couldn't find a script named/i.test(result.rawResult.stdout)
) {
message = `(runScript) Script "${script}" in package "${pkgName}" failed; script not found`;
} else {
if (result.rawResult.exitCode) {
message = `(runScript) Script "${script}" in package "${pkgName}" failed with exit code ${result.rawResult.exitCode}: ${result.rawResult.all}`;
} else {
message = `(runScript) Script "${script}" in package "${pkgName}" failed: ${result.rawResult.all}`;
}
}
result.error = new SmokerError(message);
}

if (result.error) {
this.debug(
`(runScripts) Script "%s" in package "%s" failed; continuing...`,
script,
pkgName,
);
} else {
this.debug(
'(runScripts) Successfully executed script %s in package %s',
script,
pkgName,
);
}

return result;
}
}

export default YarnBerry satisfies PackageManagerModule;
18 changes: 13 additions & 5 deletions src/pm/yarn-classic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ export class YarnClassic implements PackageManager {
if (shouldUseWorkspaces) {
let workspaceInfo: Record<string, WorkspaceInfo>;
try {
const {stdout} = await this.executor.exec(['workspaces', 'info']);
let {stdout} = await this.executor.exec(['workspaces', 'info']);
const lines = stdout.split(/\r?\n/);
lines.shift();
lines.pop();
stdout = lines.join('\n');
workspaceInfo = JSON.parse(stdout);
} catch (err) {
throw new SmokerError(`(pack) Unable to read workspace information`);
Expand Down Expand Up @@ -251,15 +255,19 @@ export class YarnClassic implements PackageManager {
result = {
pkgName,
script,
error: new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed: ${error.message}`,
),
rawResult: error,
cwd,
};
if (this.opts.loose && /Command ".+?" not found/i.test(error.stderr)) {
result.skipped = true;
} else {
result.error = new SmokerError(
`(runScript) Script "${script}" in package "${pkgName}" failed: ${error.message}`,
);
}
}

if (!result.error && result.rawResult.failed) {
if (!result.error && !result.skipped && result.rawResult.failed) {
let message: string;
if (
result.rawResult.stderr &&
Expand Down
6 changes: 5 additions & 1 deletion src/smoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ export class Smoker extends createStrictEventEmitterClass() {
scripts: string | string[],
opts: SmokeOptions = {},
) {
const pms = await loadPackageManagers(opts.pm, {verbose: opts.verbose});
const {pm, verbose, loose, all} = opts;
const pms = await loadPackageManagers(pm, {
verbose,
loose: all && loose,
});
return new Smoker(scripts, pms, opts);
}

Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface RunScriptResult {
error?: SmokerError;
rawResult: RunScriptValue | ExecaError;
cwd: string;
skipped?: boolean;
}

/**
Expand Down Expand Up @@ -95,6 +96,8 @@ export interface SmokeOptions {
add?: string[];

pm?: string[];

loose?: boolean;
}

export type SmokerOptions = Omit<SmokeOptions, 'verbose'>;
Expand Down
7 changes: 7 additions & 0 deletions test/e2e/fixture/loose/a/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "loose-a",
"version": "1.0.0",
"scripts": {
"smoke": "exit 0"
}
}
Loading

0 comments on commit c2ef61f

Please sign in to comment.