Make package.json scripts work on macOS, Linux, and Windows—before CI or users find the breakage.
ScriptSpect is a preflight checker for npm-style package.json scripts. Point
it at a Node.js project or monorepo: without running the scripts, it shows the
exact command fragment that will break in posix-sh, Windows cmd, or optional
powershell, explains the affected platform, and offers a fix only when its
safety conditions are proved.
Important
This repository is a pre-release source evaluation. The npm package and public Action tag do not exist yet; the copy-paste paths below deliberately use an immutable source commit.
See the real demo · Evaluate from source · GitHub Actions · Rules
Use ScriptSpect if you maintain a JS/TS app, library, CLI, or monorepo; own CI or releases; or support contributors on more than one operating system. It solves the familiar failure where a script works on its author's Mac or Linux machine but fails for Windows users—or contains Windows-only syntax that fails on Unix CI.
| Question | Direct answer |
|---|---|
| Who uses it? | JS/TS maintainers, monorepo owners, Windows contributors, and CI/release teams. |
| What problem does it catch? | Shell-specific environment assignment, commands such as rm/cp/mv, expansion, redirection, operators, paths, explicit shell dependencies, and undeclared executables. |
| How does it check? | It discovers the root package and workspaces, structurally parses every script for the selected shell targets, and never executes the target commands. |
| What do you get? | An exact rule ID, package and script name, source span, affected shell/OS, severity, confidence, explanation, and terminal/JSON/PR-annotation output. |
| How does it help fix it? | --fix-dry-run previews a patch. --fix applies only proven safe or precondition-satisfied changes; ambiguous cases stay manual. |
The mental model is: repository → static target-shell analysis → exact
findings → reviewable fix plan. For the current pre-release, follow
Evaluate from source, run
node dist/cli.mjs <your-project>, and add --fix-dry-run to preview changes.
For pull requests, copy the GitHub Actions preview.
| Catch the breakage | Explain the target | Keep fixes reviewable |
|---|---|---|
| Finds shell-dependent commands, operators, expansion, redirection, paths, and undeclared executables before another OS runs them. | Every finding carries a stable rule ID, package/script path, source span, severity, confidence, and affected targets. | safe, conditional, and manual classes prevent “helpful” rewrites when equivalence cannot be proved. |
ScriptSpect uses a target-specific structural parser rather than scanning quoted text with a stack of regular expressions. It is intentionally not a full shell interpreter: findings should still be reviewed in the project that owns the script.
scripts-doctor is the adjacent analyzer baseline.
cross-env, shx, and rimraf are remedies ScriptSpect may recommend when
their preconditions are satisfied, not competing analyzers.
Everything here is generated from the versioned demo fixture, so the screenshot and patch cannot drift away from executable behavior.
Before — two scripts that assume a POSIX shell:
{
"name": "portable-demo",
"private": true,
"scripts": {
"build": "NODE_ENV=production vite build",
"clean": "rm -rf dist"
},
"devDependencies": {
"cross-env": "^7.0.3",
"rimraf": "^6.0.1",
"vite": "^7.0.0"
}
}Result — PS001 and PS010 identify the exact cmd-incompatible spans:
Selectable terminal text · Full generated patch · Verified after file
After — the conditional rewrites use dependencies already declared by the project:
-"build": "NODE_ENV=production vite build"
-"clean": "rm -rf dist"
+"build": "cross-env NODE_ENV=production vite build"
+"clean": "rimraf dist"--fix-dry-run prints this patch without writing. --fix uses staged writes,
post-write analysis, and a recovery journal; it never installs dependencies or
rewrites a lockfile. Regenerate all demo assets with
pnpm exec tsx tools/generate-readme-demo.ts.
Requires Node.js 22 or newer and pnpm via Corepack. Clone the repository, check
out the reviewed commit, install exactly from the lockfile, build, and scan the
versioned demo fixture. Findings exit 1; a clean scan exits 0; invalid input,
configuration, or I/O exits 2.
git clone https://github.com/Tom409114/scriptspect.git
cd scriptspect
git checkout c9c671c8e150705d78d9169d4c5a8f22cb37fad0
corepack enable
pnpm install --frozen-lockfile
pnpm build
node dist/cli.mjs tests/fixtures/readme-demoThere is deliberately no npx scriptspect quick start yet. The machine-readable
release state is docs/readme-status.json.
The source build supports human, JSON, and GitHub-friendly output, focused rule runs, explicit target matrices, and opt-in fixes.
node dist/cli.mjs [path]
node dist/cli.mjs [path] --format json
node dist/cli.mjs [path] --target posix-sh,cmd,powershell
node dist/cli.mjs [path] --rule PS001,PS010
node dist/cli.mjs [path] --fix-dry-run
node dist/cli.mjs [path] --fix
node dist/cli.mjs explain PS010Presentation filters do not hide failure semantics: any configured error
fails, and the unfiltered warning count is compared with --max-warnings.
This complete example checks out both the consumer and an immutable ScriptSpect
source commit, then runs the bundled local Action. Do not replace the commit
with the nonexistent Tom409114/scriptspect@v0.1 tag. After a verified release,
security-sensitive workflows should continue pinning a full commit SHA.
name: scriptspect pre-release evaluation
on: [pull_request]
permissions:
contents: read
jobs:
scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: Tom409114/scriptspect
ref: c9c671c8e150705d78d9169d4c5a8f22cb37fad0
path: .scriptspect
persist-credentials: false
- uses: ./.scriptspect
with:
path: .The Action writes annotations, a job summary, and numeric outputs named
exit-code, packages, scripts, errors, warnings, and advisories before
marking a finding run as failed. Its default mode is read-only.
Real hosted proof — not a mock screenshot. On main at c9c671c8, public
CI run #33482453059
consumed uses: ./ against both clean and broken fixtures. The clean consumer
reported 1 package · 1 script · 0 errors; the broken fixture emitted 2 check
annotations, including PS010: scripts.clean on package.json.
Selectable Action evidence · Committed source evidence · Open the hosted job
Defaults target posix-sh and cmd. Put the same small contract in the root
package.json under scriptspect, or in scriptspect.config.json:
{
"targets": ["posix-sh", "cmd"],
"severity": { "PS015": "advisory" },
"ignore": [
{ "packages": ["examples/**"], "rules": ["PS030"] },
{ "scripts": ["docs:unix"], "rules": ["PS010", "PS011"] }
]
}Precedence is deterministic and replacement-based:
--config → package.json#scriptspect → scriptspect.config.json → defaults.
--target then replaces only the selected config's target list. Config sources
are never merged. Ignore entries must name rules and should stay narrow enough
to explain an intentional platform-specific script.
Contracts: config JSON Schema · JSON output Schema
| Area | Current source-evaluation behavior |
|---|---|
| Projects | root package.json plus npm/Yarn/Bun workspaces and pnpm-workspace.yaml |
| Targets | posix-sh + cmd by default; opt-in powershell evidence |
| Findings | error, warning, and advisory with high/medium confidence |
| Output | stylish terminal text, versioned JSON, GitHub annotations + summary |
| Fixes | dry-run plus provable safe/conditional rewrites; ambiguous cases stay manual |
| Privacy | offline analysis; scripts are not executed; no telemetry |
Release: pre-release; no npm package or public Action reference yet.
The homepage does not claim external adoption, measured precision, comparative superiority, or hosted performance. Even after a verified release, external validation and adoption gates remain evidence-led. The validation ledger keeps repository-controlled work separate from evidence that only real users can create.
Does it run my scripts? No. It reads package manifests and performs static structural analysis.
Why did the scan exit 1 when I filtered warnings from the display? Failure
is calculated before presentation filtering: configured errors and the full
warning budget still count. Use --format json to inspect the complete contract.
Why was no automatic fix offered? The parser must agree on the replacement's structural role across active targets, and conditional fixes require the exact dependency to be declared. Otherwise the finding remains explanatory and manual.
Which config won? Explicit --config wins, followed by the package.json
field, the standalone file, then defaults. Non-default sources are reported in
human-readable output.
Can I use it in production CI today? Treat this source checkout as an evaluation build. Wait for public npm, Release, provenance, checksum, and immutable Action-consumer evidence before depending on a released reference.