@exodus/stasis 1.0.0-beta.3 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/bin/stasis.js +253 -22
- package/package.json +42 -10
- package/src/audit-corrections.js +72 -0
- package/src/audit.js +181 -112
- package/src/bundle-from-lockfile.js +3 -5
- package/src/bundle-reader.js +77 -0
- package/src/cmd/build.js +3 -5
- package/src/cmd/bundle.js +677 -402
- package/src/cmd/github-bundle.js +39 -0
- package/src/cmd/output.js +36 -0
- package/src/cmd/sbom.js +3 -11
- package/src/diff.js +22 -14
- package/src/json-stream.js +283 -0
- package/src/loaders/bash.js +17 -36
- package/src/loaders/cargo.js +1716 -0
- package/src/loaders/foundry.js +923 -0
- package/src/loaders/paths.js +33 -0
- package/src/loaders/php.js +212 -127
- package/src/loaders/rust.js +3234 -166
- package/src/loaders/solidity-ownership.js +531 -0
- package/src/loaders/solidity.js +412 -137
- package/src/loaders/toml.js +68 -0
- package/src/metro-resolver.js +9 -24
- package/src/mock.js +3 -5
- package/src/resolve-fields.js +338 -0
- package/src/resolve-node.js +416 -0
- package/src/resolve-typescript.js +541 -0
- package/src/rollup.js +5 -0
- package/src/sbom.js +35 -19
- package/src/scan.js +211 -90
- package/src/vfs-bundle/entries.js +140 -0
- package/src/vfs-bundle/github.js +199 -0
- package/src/vfs-bundle/prisma.js +439 -0
- package/src/vfs-bundle/soldeer.js +125 -0
- package/src/vfs-bundle/tree.js +613 -0
- package/src/vfs-bundle.js +75 -0
- package/src/why.js +434 -0
- package/src/apis/npm/index.js +0 -36
- package/src/apis/npm/semver.cjs +0 -42
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://esbuild.github.io/)
|
|
7
7
|
[](https://metrobundler.dev/)
|
|
8
|
+
[](https://rollupjs.org/)
|
|
8
9
|
[](https://webpack.js.org/)
|
|
9
10
|
|
|
10
11
|
Both lockfiles and bundles only include what is actually _used_.\
|
|
@@ -57,7 +58,8 @@ _Lockfiles (npm/pnpm/etc) not mentioned: they are like the "tarball" column, but
|
|
|
57
58
|
| `stasis run --lock=add --bundle=add --mock app.js` | build without the app's side effects (network, fs writes) |
|
|
58
59
|
| `stasis run --lock=add --child-process app.js` | also attest modules loaded in forked child processes (e.g. Metro transform workers) |
|
|
59
60
|
| `stasis run --bundle=add --fs=sync app.js` | build a bundle that also captures sync `fs.readFileSync`/`readdirSync` reads |
|
|
60
|
-
| `stasis
|
|
61
|
+
| `stasis run --lock=add --import=./instrument.mjs app.js` | forward extra preload modules (repeatable) to the node process running the entry: a preload's own module graph is runner infrastructure like stasis's loader itself — evaluated, but not captured. Modules the app graph *also* reaches (or that execute through a preload-transplanted `require()` pipeline) are still attested like any other app code, so frozen replays and `--bundle=load` stay fail-closed |
|
|
62
|
+
| `stasis bundle src/index.js` | build a bundle statically, without executing it, into the `stasis.code.br` of the project root its paths are relative to (from a workspace package, the workspace root), where `stasis run --bundle=load` finds it |
|
|
61
63
|
| `stasis bundle --add src/worker.js` | merge more entries (and their import graph) into an existing `stasis.code.br` instead of replacing it |
|
|
62
64
|
| `stasis add a.js icon.svg` | add the listed files to the project's bundles (config-driven), with no dependency resolution |
|
|
63
65
|
| `stasis add src assets` | same, expanding each directory to every file under it, minus an auto-excluded set unless explicitly named |
|
|
@@ -66,17 +68,22 @@ _Lockfiles (npm/pnpm/etc) not mentioned: they are like the "tarball" column, but
|
|
|
66
68
|
| `stasis bundle --metro --platforms=ios,android app.js` | statically bundle the way Metro resolves: RN conditions + mainFields + `.ios`/`.android`/`.native` suffixes, all platforms at once, plus each bundled native dependency's `ios/`/`android/` sources + podspec |
|
|
67
69
|
| `stasis bundle --metro --metro-resolver --platforms=ios,android app.js` | same, but resolve through the project's own `metro-resolver` (byte-for-byte Metro fidelity) instead of the built-in approximation; requires `metro-resolver` installed (it ships with `react-native`/`metro`) |
|
|
68
70
|
| `stasis bundle --flow app.js` | statically bundle Flow-typed sources: when oxc can't parse a `.js`/`.cjs`/`.mjs` file, strip its Flow syntax and retry so the import graph resolves (needs the optional `flow-remove-types` dependency; the stored source stays the original bytes). Combines with `--jsx`/`--conditions`/`--mainFields`/`--metro` |
|
|
71
|
+
| `stasis bundle --typescript src/index.ts` | statically bundle TS sources that import by output name, the way tsc resolves: a `./x.js` (or `.jsx`/`.mjs`/`.cjs`) specifier with no such file on disk lands on its on-disk `./x.ts` or `./x.tsx` (`.tsx`/`.mts`/`.cts`) source, and an extensionless `./x` probes `./x.ts`, `./x.tsx` and `./x/index.ts(x)`; the same substitution applies to package `main`/`exports`/`imports` targets and bare subpaths, and an existing `.js` always wins over its `.ts` twin. None of this applies from or into `node_modules`, by real path: an installed package is used as published, while a monorepo's workspace packages, linked in through `node_modules`, really live outside it and resolve like the project's own files. On without the flag when every entry is TypeScript and their relative imports of JS outputs (`./x.js`) are each only on disk as the TS source (`./x.ts`) -- tsc's convention; a project of Node-compatible `.ts` files importing `./x.ts` keeps Node's resolution. Otherwise an import that fails but would resolve under it names the file and `--typescript` in the error. Combines with `--jsx`/`--conditions`/`--mainFields`/`--metro` (not `--metro-resolver`, which can't substitute) |
|
|
72
|
+
| `stasis bundle --typescript --tsconfig=tsconfig.build.json src/index.ts` | same, honoring tsconfig `compilerOptions.paths` aliases (e.g. `"@/*": ["./src/*"]`, resolved against `baseUrl`, `extends` followed) for bare specifiers nothing else resolves, an extensionless target completing as tsc's does (`.ts`, `.tsx`, then `.js`, `.jsx`, then the directory's `index`); without `--tsconfig`, the `tsconfig.json` beside the importing file's `package.json` applies when present: in a monorepo (a `pnpm-workspace.yaml` or `package.json` `workspaces` at or above the cwd) the nearest named `package.json` at or above the file, else the project's one (the nearest named `package.json` at or above the cwd); never for a file in `node_modules`, and no lookup walks through one |
|
|
73
|
+
| `stasis github-bundle --github=owner/name [--sha=<commit>] [--tag=<name>] [--directory=path] [--package-manager=pnpm] [src/index.js]` | statically bundle a GitHub repo at a commit, or the one `--tag` names (the default branch's head without either), as `stasis bundle` bundles a clone of it once installed: the tree is fetched (with `GITHUB_TOKEN` where set) and held to its git tree id, and the dependencies are laid out in memory from the lockfile alone (`pnpm`, `yarn1`, `npm`, or `soldeer` for `.sol` entries, a Soldeer git dependency on GitHub fetched as the repo is; without `--package-manager`, the one whose lockfile installs the directory, where only one's does), with nothing installed and no package script run; where no entries are given, they are the JS files the directory's `package.json` names (`main`, `exports`, `bin`), resolved as the build resolves (`--conditions`, `--mainFields`, `--metro --platforms`, `--typescript`), or for Soldeer its `.sol` files directly in it, under `contracts/`, and under its source directory (`foundry.toml`'s `src`, else `src/`), but tests, scripts, mocks and dependency or build directories; its paths, and the directory its `repo` names, are the directory's, or for a JS bundle, the innermost package's at or above it that holds every file it bundles (the project root's, where one is outside it, such as a sibling workspace package or the root's `node_modules`); written to `owner-name.<commit's first 7>.stasis.code.br` by default, `owner-name.<directory, its / made ->.<commit's first 7>.stasis.code.br` with `--directory` (each character outside `[A-Za-z0-9._-]` made `_`, and a directory too deep to fit in 255 characters cut, with a hash of it); takes `stasis bundle`'s options for the entries and `--lockfile` (as a library: `buildGitHubBundle` in `@exodus/stasis/vfs-bundle`, and `suggestedEntries` for the entries it would take, of a GitHub repo or a Vfs) |
|
|
74
|
+
| `stasis github-bundle --github=owner/name --generate=prisma packages/api/src/index.ts` | same, with the Prisma Client each project's `prisma generate` would write first: for each project the lockfile installs with a `prisma-client` generator in its schema (the one its Prisma config names as a string literal or `path.join` of them, else `schema.prisma` or `prisma/schema.prisma`), as the Prisma it installs (7.4.0 to 7.10.0: its `prisma`, or beside Prisma 8, the one its `@prisma/prisma7` runs) writes it, byte for byte, where the generator's `output` says, the bundle then carrying it as a source like any other. Nothing of the repo runs: the schema, the config's `schema` path, and the `tsconfig.json` and `package.json` the generator infers its module format and import extensions from are read as data, its installed `prisma` only for its version (and, for an edge runtime's client, the query compiler it ships). The client is generated by the optional `@prisma/client-generator-ts` 7.10.0 dependency and rewritten as the version installed writes it; a project it can't generate for is skipped with a warning |
|
|
69
75
|
| `stasis build --output=out.js app.stasis.code.br [entry]` | rebuild a runnable JS bundle with esbuild, following the bundle's recorded import graph exactly (entry optional when the bundle has one) |
|
|
70
76
|
| `stasis build --output=out.js stasis.lock.json [entry]` | same, reading + verifying sources from disk against the lockfile |
|
|
71
77
|
| `stasis extract app.stasis.code.br` | unpack a bundle back to sources + a `stasis.lock.json` |
|
|
72
78
|
| `stasis diff --stat a.lock.json b.stasis.code.br` | summarize module/file differences between two lockfiles/bundles |
|
|
73
79
|
| `stasis prune` | trim `node_modules` to the lockfile, verifying the rest |
|
|
74
80
|
| `stasis audit stasis.lock.json` | report npm advisories for a lockfile's dependencies |
|
|
75
|
-
| `stasis audit app.stasis.code.br` | report
|
|
81
|
+
| `stasis audit app.stasis.code.br` | report advisories for a bundle's dependencies: npm packages by npm's advisories, crates vendored from a registry and Composer packages by OSV's, and Soldeer packages and GitHub repos (Foundry's `lib/`) by those their GitHub repository publishes, asked with `GITHUB_TOKEN` where it is set; a Composer dev version, and a crate vendored from git or with no `.cargo-checksum.json` (never sent to OSV), are listed as not audited |
|
|
76
82
|
| `stasis audit --why app.stasis.code.br` | same, with the cross-module import paths that pull each flagged package in (`run: a -> b -> c`), prefixed by the consumer that imports each chain at the top level; a chain is skipped when its full tail is already listed as its own chain |
|
|
77
83
|
| `stasis audit --why-deep app.stasis.code.br` | like `--why`, but keep those longer chains too (every path, shared tails collapsed to `a -> b -> ... -> d`) |
|
|
78
84
|
| `stasis audit --why-full app.stasis.code.br` | like `--why`, but spell every chain out with no `...` collapse (combine with `--why-deep` for the complete raw listing) |
|
|
79
85
|
| `stasis audit --reason=run app.stasis.code.br` | show only advisories related to one consumer (`run`); with `--why`, keep only that consumer's chains |
|
|
86
|
+
| `GITHUB_TOKEN=... stasis audit --repo-advisories stasis.lock.json` | also ask each dependency's GitHub repository for the advisories its maintainers published there, which npm's database has only once GitHub reviews them; needs a GitHub token in `GITHUB_TOKEN`. Each package's repository is the one its artifact records (its `package.json`'s, see `repo` in [file formats](https://github.com/ExodusOSS/stasis/blob/main/doc/file-formats.md)), else it is looked up in npm's registry, Packagist or crates.io and cached for a month in the user cache directory (`$XDG_CACHE_HOME/stasis`, `~/.cache/stasis`, `~/Library/Caches/stasis` on macOS, `%LOCALAPPDATA%\stasis\Cache` on Windows) |
|
|
80
87
|
| `stasis sbom --format=spdx stasis.lock.json` | export an SPDX SBOM for a lockfile or bundle |
|
|
81
88
|
| `stasis sbom --format=cyclonedx app.stasis.code.br` | export a CycloneDX SBOM for a lockfile or bundle |
|
|
82
89
|
|
package/bin/stasis.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { spawn } from 'node:child_process'
|
|
4
4
|
import { once } from 'node:events'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { basename, dirname, resolve } from 'node:path'
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
|
|
7
7
|
import { existsSync, realpathSync } from 'node:fs'
|
|
8
|
-
import { constants as osConstants } from 'node:os'
|
|
8
|
+
import { homedir, constants as osConstants } from 'node:os'
|
|
9
9
|
import assert from 'node:assert/strict'
|
|
10
10
|
import { parseBrotliQuality, parseLeadingOptions, parseResourcesOption } from '@exodus/stasis-core/util'
|
|
11
11
|
import pkg from '../package.json' with { type: 'json' }
|
|
@@ -20,19 +20,79 @@ assert(basename(jsname) === 'stasis' || pathsEqual(jsname, fileURLToPath(import.
|
|
|
20
20
|
|
|
21
21
|
function usage(prefix = '') {
|
|
22
22
|
console.error(`${prefix}\nUsage:
|
|
23
|
-
stasis run --lock=(add|replace|frozen|ignore) [--bundle=(add|replace|load|frozen|ignore)] [--bundle-file=path/to/bundle.br] [--resources-bundle-file=path/to/resources.br] [--dependencies] [--child-process] [--package-json] [--mock] [--fs=(sync|async)] [--resources=ext,ext] [--brotli-quality=0..11] path/to/file.js ...
|
|
24
|
-
|
|
23
|
+
stasis run --lock=(add|replace|frozen|ignore) [--bundle=(add|replace|load|frozen|ignore)] [--bundle-file=path/to/bundle.br] [--resources-bundle-file=path/to/resources.br] [--dependencies] [--child-process] [--package-json] [--mock] [--import=module ...] [--fs=(sync|async)] [--resources=ext,ext] [--brotli-quality=0..11] path/to/file.js ...
|
|
24
|
+
(--import forwards extra preload modules (repeatable) to the node process running the entry,
|
|
25
|
+
e.g. --import=./instrument.mjs for a setup/instrumentation preload; a preload's own module
|
|
26
|
+
graph is runner infrastructure like stasis's loader itself, so it stays out of the
|
|
27
|
+
lockfile/bundle -- except modules the app graph also reaches, which are attested like any
|
|
28
|
+
other app code; with --mock, preloads run under the mock's side-effect denials, so a preload
|
|
29
|
+
that spawns helper processes cannot work there)
|
|
30
|
+
stasis bundle [--mapping=path/to/remappings(.txt|.toml)] [--manifests] [--add] [--output=(path|-)] path/to/(file.sol|dir) ...
|
|
31
|
+
(Solidity: a directory stands for every .sol file under it (a missing or empty one is skipped) --
|
|
32
|
+
"stasis bundle src test script" is what "forge build" compiles, "stasis bundle contracts" what
|
|
33
|
+
Hardhat does; imports resolve through the remappings "forge build" uses (remappings.txt,
|
|
34
|
+
foundry.toml's profile, FOUNDRY_PROFILE, lib/ auto-detection incl. dependencies' own configs),
|
|
35
|
+
else a root remappings.txt, then node_modules by file path; --mapping instead takes exactly the
|
|
36
|
+
remappings that one file lists; an import must reach a .sol file inside the project, and a
|
|
37
|
+
dependency's only other dependencies' files; --manifests also carries foundry.toml,
|
|
38
|
+
remappings.txt, foundry.lock, soldeer.lock, .gitmodules and package.json of the project and its
|
|
39
|
+
bundled dependencies, as written: RPC/Etherscan keys and URL credentials in them included)
|
|
25
40
|
stasis bundle [--add] [--output=(path|-)] path/to/file.php ...
|
|
26
|
-
stasis bundle [--scope=(node_modules|full)] [--conditions=cond1,cond2] [--mainFields=field1,field2] [--jsx] [--flow] [--resources=ext,ext] [--package-json] [--lockfile=path/to/stasis.lock.json] [--add] [--output=(path|-)] path/to/file.(js|ts) ...
|
|
27
|
-
stasis bundle --metro [--metro-resolver] --platforms=ios,android [--platforms=web] [--jsx] [--flow] [--resources=ext,ext] [--package-json] [--lockfile=path/to/stasis.lock.json] [--add] [--output=(path|-)] path/to/file.(js|ts) ...
|
|
41
|
+
stasis bundle [--scope=(node_modules|full)] [--conditions=cond1,cond2] [--mainFields=field1,field2] [--jsx] [--flow] [--typescript [--tsconfig=path/to/tsconfig.json]] [--resources=ext,ext] [--package-json] [--lockfile=path/to/stasis.lock.json] [--add] [--output=(path|-)] path/to/file.(js|ts|jsx|tsx) ...
|
|
42
|
+
stasis bundle --metro [--metro-resolver] --platforms=ios,android [--platforms=web] [--jsx] [--flow] [--typescript [--tsconfig=path/to/tsconfig.json]] [--resources=ext,ext] [--package-json] [--lockfile=path/to/stasis.lock.json] [--add] [--output=(path|-)] path/to/file.(js|ts|jsx|tsx) ...
|
|
28
43
|
stasis bundle [--add] [--output=(path|-)] path/to/file.(sh|bash) ...
|
|
29
|
-
stasis bundle [--add] [--output=(path|-)] path/to/file.rs ...
|
|
30
|
-
(
|
|
44
|
+
stasis bundle [--cargo] [--cargo-features=a,b,pkg/c] [--cargo-no-default-features] [--cargo-all-features] [--cargo-target=(triple|host)] [--cargo-manifests] [--add] [--output=(path|-)] path/to/file.rs ...
|
|
45
|
+
(Rust: each crate's Cargo features are resolved from Cargo.toml/Cargo.lock like "cargo build" of the
|
|
46
|
+
entries' packages, so #[cfg(feature = ...)] code that is off stays out -- by cargo's own resolver
|
|
47
|
+
when there is a Cargo.lock (version 3 or 4; an older one stops the build), --cargo-target and every
|
|
48
|
+
locked package in-tree, by replaying the manifests otherwise (said, with why); --cargo takes the resolution
|
|
49
|
+
from "cargo metadata" instead -- it runs cargo, so only on a project you trust; the --cargo-*
|
|
50
|
+
feature flags are cargo's --features / --no-default-features / --all-features for those packages;
|
|
51
|
+
--cargo-target asks rustc for the target's cfgs so #[cfg(unix)]-style code for other targets stays
|
|
52
|
+
out too, otherwise it is all kept; --cargo-manifests also bundles each bundled package's Cargo.toml
|
|
53
|
+
and build script (a vendored crate's .cargo-checksum.json too), the workspace Cargo.toml, Cargo.lock
|
|
54
|
+
and cargo configs, as written: tokens and URL credentials in them included)
|
|
55
|
+
(writes to stasis.code.br by default, a JS bundle's in the project root its paths are relative to,
|
|
56
|
+
where stasis run --bundle=load finds it; --output=- streams to stdout; --add merges into an
|
|
31
57
|
existing bundle instead of replacing it (not with --output=-); --brotli-quality=0..11, default 9;
|
|
32
|
-
--jsx parses JSX in .js/.cjs/.mjs files, e.g. React Native source (
|
|
58
|
+
--jsx parses JSX in .js/.cjs/.mjs files, e.g. React Native source (.jsx/.tsx files need no flag;
|
|
59
|
+
put JSX-in-TS in a .tsx file);
|
|
33
60
|
--flow strips Flow types from .js/.cjs/.mjs sources oxc can't parse (needs the optional flow-remove-types dep);
|
|
61
|
+
--typescript resolves TS the way tsc does: an import of ./x.js lands on ./x.ts when no .js is on disk
|
|
62
|
+
(likewise .mjs/.cjs -> .mts/.cts, extensionless ./x -> ./x.ts, incl. exports/imports/main targets),
|
|
63
|
+
honouring tsconfig compilerOptions.paths aliases (from the tsconfig.json beside the importing
|
|
64
|
+
file's package.json -- its workspace package's in a monorepo, else the project's -- or --tsconfig=path);
|
|
65
|
+
never from or into node_modules (by real path, so linked workspace packages still are); not with --metro-resolver; implied when every entry is TS and each relative ./x.js-style import
|
|
66
|
+
in them exists only as its TS source (otherwise a failing import it would resolve names it);
|
|
34
67
|
--resources carries reached assets (e.g. --resources=png,svg) as resources instead of failing to bundle them;
|
|
35
|
-
--package-json auto-includes each bundled module's package.json, even ones the scan never reached)
|
|
68
|
+
--package-json auto-includes each bundled module's package.json, even ones the scan never reached)
|
|
69
|
+
stasis github-bundle --github=owner/name [--sha=commit|--tag=name] [--directory=path] [--package-manager=(pnpm|yarn1|npm|soldeer)] [--package-manager-version=version] [--generate=prisma] [--lockfile=path/to/stasis.lock.json] [--output=(path|-)] [stasis bundle's options for the entries] [path/in/repo/to/(file.(js|ts|jsx|tsx)|file.sol|dir) ...]
|
|
70
|
+
(bundles a GitHub repo at a commit, or the one --tag names, its default branch's head without
|
|
71
|
+
either, as "stasis bundle" bundles a clone of it once installed: the tree is fetched (with
|
|
72
|
+
GITHUB_TOKEN where set) and held to its git tree id, and the dependencies are laid out in memory
|
|
73
|
+
from the lockfile alone, as "pnpm install --frozen-lockfile --ignore-scripts" (pnpm 9, 10, 11 or
|
|
74
|
+
12), "yarn install --frozen-lockfile --ignore-scripts" (yarn 1.22), "npm ci --ignore-scripts"
|
|
75
|
+
(npm 10.9.3 to 10.9.9, or 11.11.1 to 11.21.0) or "soldeer install" (0.12, for .sol entries)
|
|
76
|
+
would: nothing is installed, no package script runs, and every tarball and zip is held to the
|
|
77
|
+
lockfile, all cached where "stasis audit" caches. Without --package-manager, the one whose
|
|
78
|
+
lockfile installs --directory (or the repo's root), if only one's does; at
|
|
79
|
+
--package-manager-version, else at the root package.json's packageManager pin, else pnpm
|
|
80
|
+
10.33.4, yarn 1.22.22, npm 11.21.0 (which no packageManager pins) or Soldeer 0.12.0. The entries
|
|
81
|
+
are paths in --directory, or in the repo; without them, the JS files its package.json names as
|
|
82
|
+
main, exports and bin, resolved as the build resolves them (--conditions, --mainFields, --metro
|
|
83
|
+
--platforms, --typescript), or for Soldeer its .sol files directly in it, under contracts/, and
|
|
84
|
+
under its source directory (foundry.toml's src, else src/), but tests, scripts, mocks and
|
|
85
|
+
dependency or build directories; its paths, and the directory its repo names, are
|
|
86
|
+
--directory's, or of a JS bundle, the innermost package's at or above it holding every file it
|
|
87
|
+
bundles, the project root's where one is outside; not with --metro-resolver, --cargo* or --add;
|
|
88
|
+
--generate=prisma first writes, for each project with a "prisma-client" generator, the Prisma
|
|
89
|
+
Client its "prisma generate" would (Prisma 7.4.0 to 7.10.0), with the optional
|
|
90
|
+
@prisma/client-generator-ts 7.10.0 dependency: nothing of the repo runs, its schema and config
|
|
91
|
+
are read as data, and its prisma for its version alone;
|
|
92
|
+
writes to owner-name.<commit's first 7>.stasis.code.br by default,
|
|
93
|
+
owner-name.<--directory, its / made ->.<commit's first 7>.stasis.code.br with --directory, each
|
|
94
|
+
character outside [A-Za-z0-9._-] made _, and a directory too deep to fit in 255 characters cut,
|
|
95
|
+
with a hash of it; --output=- streams to stdout)
|
|
36
96
|
stasis add path/to/(file|dir) ...
|
|
37
97
|
(adds the listed files to the project's bundle(s) with no dependency resolution;
|
|
38
98
|
a directory expands to its files. Requires a stasis.config.json (all fields optional).)
|
|
@@ -43,12 +103,20 @@ function usage(prefix = '') {
|
|
|
43
103
|
stasis extract [--output=path/to/dir] path/to/bundle.stasis.code.br
|
|
44
104
|
stasis diff --stat [--imports] path/to/(lockfile|bundle) path/to/(lockfile|bundle)
|
|
45
105
|
stasis prune [path/to/project]
|
|
46
|
-
stasis audit [--why|--why-deep] [--why-full] [--reason=consumer] path/to/file ...
|
|
47
|
-
(
|
|
106
|
+
stasis audit [--why|--why-deep] [--why-full] [--reason=consumer] [--repo-advisories] path/to/file ...
|
|
107
|
+
(audits the dependencies the files hold: npm packages by npm's advisories, vendored crates and
|
|
108
|
+
Composer packages by OSV's, and Soldeer packages and GitHub repos (Foundry's lib/) by those their
|
|
109
|
+
GitHub repository publishes, asked with GITHUB_TOKEN where it is set; a Composer dev version is
|
|
110
|
+
listed as not audited, no database having one;
|
|
111
|
+
--why lists, per advisory, the cross-module import paths that pull an npm package in,
|
|
48
112
|
prefixed by the bundle consumer that imports each chain at the top level, e.g. "run: a -> b -> c";
|
|
49
113
|
it skips chains whose full tail is already listed as its own chain -- --why-deep keeps them all;
|
|
50
114
|
--why-full spells every chain out instead of collapsing repeated tails to "a -> b -> ... -> d";
|
|
51
|
-
--reason=consumer shows only advisories related to that consumer, and with --why only its chains
|
|
115
|
+
--reason=consumer shows only advisories related to that consumer, and with --why only its chains;
|
|
116
|
+
--repo-advisories also asks each package's GitHub repository for the advisories its maintainers
|
|
117
|
+
published there, before GitHub reviews them into the databases above; it needs a token in
|
|
118
|
+
GITHUB_TOKEN, and asks the repository each package's artifact records, else looks one up and
|
|
119
|
+
caches it for a month in the user cache dir, e.g. ~/.cache/stasis)
|
|
52
120
|
stasis sbom --format=(spdx|cyclonedx) [--output=(path|-)] path/to/(lockfile|bundle) ...
|
|
53
121
|
(streams to stdout by default; --output=- is explicit stdout)
|
|
54
122
|
`.trim())
|
|
@@ -61,6 +129,14 @@ function setEnv(name, value) {
|
|
|
61
129
|
process.env[name] = value === undefined ? '' : value
|
|
62
130
|
}
|
|
63
131
|
|
|
132
|
+
// The per-user cache directory, as each platform names it (XDG ignores a relative path).
|
|
133
|
+
function userCacheDir() {
|
|
134
|
+
if (isAbsolute(process.env.XDG_CACHE_HOME ?? '')) return join(process.env.XDG_CACHE_HOME, 'stasis')
|
|
135
|
+
if (process.platform === 'darwin') return join(homedir(), 'Library', 'Caches', 'stasis')
|
|
136
|
+
if (process.platform === 'win32') return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'stasis', 'Cache')
|
|
137
|
+
return join(homedir(), '.cache', 'stasis')
|
|
138
|
+
}
|
|
139
|
+
|
|
64
140
|
const command = argv.shift()
|
|
65
141
|
|
|
66
142
|
if (command === '-v' || command === '--version') {
|
|
@@ -77,12 +153,13 @@ if (command === '-v' || command === '--version') {
|
|
|
77
153
|
'child-process': { type: 'boolean' },
|
|
78
154
|
'package-json': { type: 'boolean' },
|
|
79
155
|
mock: { type: 'boolean' },
|
|
156
|
+
import: { type: 'string', multiple: true },
|
|
80
157
|
fs: { type: 'string' },
|
|
81
158
|
resources: { type: 'string' },
|
|
82
159
|
'brotli-quality': { type: 'string' },
|
|
83
160
|
}
|
|
84
161
|
const values = parseLeadingOptions(argv, options, {
|
|
85
|
-
valueFlags: ['--bundle', '--bundle-file', '--resources-bundle-file', '--lock', '--resources', '--brotli-quality'],
|
|
162
|
+
valueFlags: ['--bundle', '--bundle-file', '--resources-bundle-file', '--lock', '--import', '--resources', '--brotli-quality'],
|
|
86
163
|
onError: usage,
|
|
87
164
|
})
|
|
88
165
|
if (argv.length === 0) usage('Nothing to run: no path to file given')
|
|
@@ -115,6 +192,15 @@ if (command === '-v' || command === '--version') {
|
|
|
115
192
|
usage(`Error: ${cause.message}`)
|
|
116
193
|
}
|
|
117
194
|
}
|
|
195
|
+
// --import: extra preload module(s) passed through to the spawned node. Node resolves each
|
|
196
|
+
// against the project cwd; they ride AFTER stasis's own loader import.
|
|
197
|
+
const imports = values.import ?? []
|
|
198
|
+
if (imports.some((s) => s === '')) usage('Error: --import requires a module specifier (e.g. --import=./instrument.mjs)')
|
|
199
|
+
// Not a usage error: plain instrumentation/setup preloads still work under the mock; only ones
|
|
200
|
+
// that need the denied surfaces (spawning helper processes, network) die, and they do so loudly.
|
|
201
|
+
if (values.mock && imports.length > 0) {
|
|
202
|
+
console.warn("[stasis] Warning: --import preloads run under --mock's side-effect denials; a preload that spawns helper processes will fail there")
|
|
203
|
+
}
|
|
118
204
|
// --child-process: forward forked-child (e.g. Metro worker) capture to the root via per-pid shards.
|
|
119
205
|
const childProcess = values['child-process'] ? '1' : ''
|
|
120
206
|
// --package-json: auto-include every bundled module's package.json. Only meaningful while WRITING
|
|
@@ -123,7 +209,7 @@ if (command === '-v' || command === '--version') {
|
|
|
123
209
|
usage('Error: --package-json requires --bundle=(add|replace)')
|
|
124
210
|
}
|
|
125
211
|
const packageJSON = values['package-json'] ? '1' : ''
|
|
126
|
-
console.warn('[stasis] Running stasis with config:', { lock, scope, bundle, ...(bundleFile && { bundleFile }), ...(resourcesBundleFile && { resourcesBundleFile }), ...(childProcess && { childProcess: true }), ...(packageJSON && { packageJSON: true }), ...(values.mock && { mock: true }), ...(values.fs && { fs: values.fs }), ...(resources && { resources }), ...(brotliQuality !== undefined && { brotliQuality }) })
|
|
212
|
+
console.warn('[stasis] Running stasis with config:', { lock, scope, bundle, ...(bundleFile && { bundleFile }), ...(resourcesBundleFile && { resourcesBundleFile }), ...(childProcess && { childProcess: true }), ...(packageJSON && { packageJSON: true }), ...(values.mock && { mock: true }), ...(imports.length > 0 && { import: imports }), ...(values.fs && { fs: values.fs }), ...(resources && { resources }), ...(brotliQuality !== undefined && { brotliQuality }) })
|
|
127
213
|
if (debug) console.warn(`[stasis] Warning: stasis debug mode active`)
|
|
128
214
|
setEnv('EXODUS_STASIS_LOCK', lock)
|
|
129
215
|
setEnv('EXODUS_STASIS_SCOPE', scope)
|
|
@@ -166,6 +252,10 @@ if (command === '-v' || command === '--version') {
|
|
|
166
252
|
// --mock uses stasis's loader-mock entry (composes the mock before installing the hooks).
|
|
167
253
|
const loaderEntry = values.mock ? '../src/loader-mock.js' : '@exodus/stasis-core/loader'
|
|
168
254
|
nodeArgs.push('--import', import.meta.resolve(loaderEntry))
|
|
255
|
+
// Passthrough preloads ride after the loader (and after --mock's denials), so they evaluate
|
|
256
|
+
// under its hooks; their module graphs pass through uncaptured unless the app graph reaches
|
|
257
|
+
// them -- then they're promoted into the capture (see core hooks.js).
|
|
258
|
+
for (const specifier of imports) nodeArgs.push('--import', specifier)
|
|
169
259
|
const child = spawn(process.execPath, [...nodeArgs, ...argv], { stdio: 'inherit' })
|
|
170
260
|
const [code, signal] = await once(child, 'close')
|
|
171
261
|
// code is null when the child died from a signal; report 128+signo (shell convention) instead of the implicit 0
|
|
@@ -173,6 +263,7 @@ if (command === '-v' || command === '--version') {
|
|
|
173
263
|
} else if (command === 'bundle') {
|
|
174
264
|
const options = {
|
|
175
265
|
mapping: { type: 'string' },
|
|
266
|
+
manifests: { type: 'boolean' },
|
|
176
267
|
output: { type: 'string', short: 'o' },
|
|
177
268
|
scope: { type: 'string' },
|
|
178
269
|
lockfile: { type: 'string' },
|
|
@@ -183,25 +274,67 @@ if (command === '-v' || command === '--version') {
|
|
|
183
274
|
platforms: { type: 'string', multiple: true },
|
|
184
275
|
jsx: { type: 'boolean' },
|
|
185
276
|
flow: { type: 'boolean' },
|
|
277
|
+
typescript: { type: 'boolean' },
|
|
278
|
+
tsconfig: { type: 'string' },
|
|
186
279
|
resources: { type: 'string' },
|
|
187
280
|
'package-json': { type: 'boolean' },
|
|
281
|
+
cargo: { type: 'boolean' },
|
|
282
|
+
'cargo-features': { type: 'string', multiple: true },
|
|
283
|
+
'cargo-no-default-features': { type: 'boolean' },
|
|
284
|
+
'cargo-all-features': { type: 'boolean' },
|
|
285
|
+
'cargo-target': { type: 'string' },
|
|
286
|
+
'cargo-manifests': { type: 'boolean' },
|
|
188
287
|
'brotli-quality': { type: 'string' },
|
|
189
288
|
add: { type: 'boolean' },
|
|
190
289
|
}
|
|
191
290
|
const values = parseLeadingOptions(argv, options, {
|
|
192
|
-
valueFlags: ['--mapping', '--output', '--scope', '--lockfile', '--conditions', '--mainFields', '--platforms', '--resources', '--brotli-quality', '-o'],
|
|
291
|
+
valueFlags: ['--mapping', '--output', '--scope', '--lockfile', '--conditions', '--mainFields', '--platforms', '--resources', '--tsconfig', '--cargo-features', '--cargo-target', '--brotli-quality', '-o'],
|
|
193
292
|
onError: usage,
|
|
194
293
|
})
|
|
195
294
|
if (argv.length === 0) usage('Nothing to bundle: no entry file given')
|
|
196
|
-
|
|
295
|
+
// A directory entry stands for the .sol files under it (Solidity only); an extensionless path
|
|
296
|
+
// that doesn't exist is a missing one (skipped with a warning).
|
|
297
|
+
const { directoryEntryError, isSolidityEntry } = await import('../src/cmd/bundle.js')
|
|
298
|
+
const dirError = directoryEntryError(argv)
|
|
299
|
+
if (dirError !== null) usage(`Error: ${dirError}`)
|
|
300
|
+
const allSol = argv.every((f) => isSolidityEntry(f))
|
|
197
301
|
const allPhp = argv.every((f) => f.endsWith('.php'))
|
|
198
|
-
const allJs = argv.every((f) => /\.(?:js|cjs|mjs|ts|cts|mts)$/u.test(f))
|
|
302
|
+
const allJs = argv.every((f) => /\.(?:js|cjs|mjs|ts|cts|mts|jsx|tsx)$/u.test(f))
|
|
199
303
|
const allBash = argv.every((f) => /\.(?:sh|bash)$/u.test(f))
|
|
200
304
|
const allRust = argv.every((f) => f.endsWith('.rs'))
|
|
201
305
|
if (!allSol && !allPhp && !allJs && !allBash && !allRust) {
|
|
202
|
-
usage('Error: bundle entries must all be .sol, all be .php, all be .js/.cjs/.mjs/.ts/.cts/.mts, all be .sh/.bash, or all be .rs')
|
|
306
|
+
usage('Error: bundle entries must all be .sol, all be .php, all be .js/.cjs/.mjs/.ts/.cts/.mts/.jsx/.tsx, all be .sh/.bash, or all be .rs')
|
|
203
307
|
}
|
|
204
308
|
if (values.mapping && !allSol) usage('Error: --mapping is only valid for .sol bundles')
|
|
309
|
+
// --manifests: carry the Solidity build's description files (foundry.toml, remappings.txt, ...).
|
|
310
|
+
const manifests = Boolean(values.manifests)
|
|
311
|
+
if (manifests && !allSol) usage('Error: --manifests is only valid for .sol bundles')
|
|
312
|
+
// --cargo: take the Rust feature/dependency resolution from `cargo metadata` (runs cargo; opt-in).
|
|
313
|
+
const cargo = Boolean(values.cargo)
|
|
314
|
+
if (cargo && !allRust) usage('Error: --cargo is only valid for Rust bundles')
|
|
315
|
+
// --cargo-features / --cargo-no-default-features / --cargo-all-features: cargo's own feature flags
|
|
316
|
+
// for the entries' packages (`pkg/feat` targets one, or a dependency). --cargo-features is
|
|
317
|
+
// repeatable and/or comma-separated; parseFeatureList is the one splitter (the resolver reuses it).
|
|
318
|
+
const { parseFeatureList } = await import('../src/loaders/cargo.js')
|
|
319
|
+
const cargoFeatures = parseFeatureList(values['cargo-features'] ?? [])
|
|
320
|
+
if (values['cargo-features'] !== undefined && cargoFeatures.length === 0) {
|
|
321
|
+
usage('Error: --cargo-features must list at least one feature (e.g. --cargo-features=serde,app/tls)')
|
|
322
|
+
}
|
|
323
|
+
const cargoNoDefaultFeatures = Boolean(values['cargo-no-default-features'])
|
|
324
|
+
const cargoAllFeatures = Boolean(values['cargo-all-features'])
|
|
325
|
+
// --cargo-target=<triple|host>: the build's target, whose cfgs (from `rustc --print cfg`) decide
|
|
326
|
+
// `#[cfg(unix)]`-style code. A triple is letters, digits, `-`, `_` and `.` (`x86_64-unknown-linux-gnu`).
|
|
327
|
+
const cargoTarget = values['cargo-target'] ?? null
|
|
328
|
+
if (cargoTarget !== null && !/^[\w.-]+$/u.test(cargoTarget)) {
|
|
329
|
+
usage('Error: --cargo-target must be a target triple or "host" (e.g. --cargo-target=aarch64-apple-darwin)')
|
|
330
|
+
}
|
|
331
|
+
// --cargo-manifests: carry each bundled package's Cargo.toml and build script, the workspace
|
|
332
|
+
// Cargo.toml, Cargo.lock and .cargo/config.toml too (the Rust counterpart of --package-json).
|
|
333
|
+
const cargoManifests = Boolean(values['cargo-manifests'])
|
|
334
|
+
if (!allRust) {
|
|
335
|
+
const given = { 'cargo-features': cargoFeatures.length > 0, 'cargo-no-default-features': cargoNoDefaultFeatures, 'cargo-all-features': cargoAllFeatures, 'cargo-target': cargoTarget !== null, 'cargo-manifests': cargoManifests }
|
|
336
|
+
for (const [flag, on] of Object.entries(given)) if (on) usage(`Error: --${flag} is only valid for Rust bundles`)
|
|
337
|
+
}
|
|
205
338
|
if (values.scope && !allJs) usage('Error: --scope is only valid for JS bundles')
|
|
206
339
|
if (values.scope && !['node_modules', 'full'].includes(values.scope)) {
|
|
207
340
|
usage('Error: --scope must be node_modules or full')
|
|
@@ -223,6 +356,13 @@ if (command === '-v' || command === '--version') {
|
|
|
223
356
|
// --flow: strip Flow type syntax (via the optional flow-remove-types dep) before parsing; JS-only.
|
|
224
357
|
if (values.flow && !allJs) usage('Error: --flow is only valid for JS bundles')
|
|
225
358
|
const flow = Boolean(values.flow)
|
|
359
|
+
// --typescript: resolve like tsc -- a specifier naming a missing JS output (./x.js, .mjs, .cjs,
|
|
360
|
+
// extensionless) lands on its on-disk TS source (./x.ts, .mts, .cts); an existing .js always wins.
|
|
361
|
+
if (values.typescript && !allJs) usage('Error: --typescript is only valid for JS bundles')
|
|
362
|
+
const typescript = Boolean(values.typescript)
|
|
363
|
+
// --tsconfig: the config whose compilerOptions.paths aliases --typescript honours (default: the
|
|
364
|
+
// tsconfig.json beside each importing file's package.json). Meaningless without --typescript.
|
|
365
|
+
if (values.tsconfig !== undefined && !typescript) usage('Error: --tsconfig is only valid with --typescript')
|
|
226
366
|
// --mainFields: legacy package entry fields (e.g. react-native,browser,main) for the non-exports resolver.
|
|
227
367
|
if (values.mainFields !== undefined && !allJs) usage('Error: --mainFields is only valid for JS bundles')
|
|
228
368
|
const mainFields = values.mainFields === undefined
|
|
@@ -244,7 +384,7 @@ if (command === '-v' || command === '--version') {
|
|
|
244
384
|
}
|
|
245
385
|
if ((metro || platforms.length > 0) && !allJs) usage('Error: --metro is only valid for JS bundles')
|
|
246
386
|
// --jsx: parse React Native's JSX-in-.js source in the static scanner (off by default; oxc,
|
|
247
|
-
// like tsc,
|
|
387
|
+
// like tsc, auto-enables JSX for .jsx/.tsx only). Orthogonal to the resolver, so it pairs with
|
|
248
388
|
// plain/--mainFields/--metro alike -- JS-only, since no other entry language is scanned.
|
|
249
389
|
const jsx = Boolean(values.jsx)
|
|
250
390
|
if (jsx && !allJs) usage('Error: --jsx is only valid for JS bundles')
|
|
@@ -275,6 +415,8 @@ if (command === '-v' || command === '--version') {
|
|
|
275
415
|
usage('Error: --platforms is only valid with --metro')
|
|
276
416
|
}
|
|
277
417
|
if (metroResolver && !metro) usage('Error: --metro-resolver is only valid with --metro')
|
|
418
|
+
// metro-resolver can't substitute .js -> .ts, so --typescript would silently not apply -- reject it.
|
|
419
|
+
if (typescript && metroResolver) usage("Error: --typescript is not supported with --metro-resolver (the project's metro-resolver doesn't substitute .js -> .ts)")
|
|
278
420
|
// --brotli-quality: valid for every entry language (no allJs gate).
|
|
279
421
|
let brotliQuality
|
|
280
422
|
if (values['brotli-quality'] !== undefined) {
|
|
@@ -292,6 +434,7 @@ if (command === '-v' || command === '--version') {
|
|
|
292
434
|
cwd: process.cwd(),
|
|
293
435
|
entries: argv,
|
|
294
436
|
mappingFile: values.mapping,
|
|
437
|
+
manifests,
|
|
295
438
|
output: values.output,
|
|
296
439
|
scope: values.scope,
|
|
297
440
|
lockfile: values.lockfile,
|
|
@@ -302,11 +445,90 @@ if (command === '-v' || command === '--version') {
|
|
|
302
445
|
platforms,
|
|
303
446
|
jsx,
|
|
304
447
|
flow,
|
|
448
|
+
typescript,
|
|
449
|
+
tsconfig: values.tsconfig,
|
|
305
450
|
resources,
|
|
306
451
|
packageJSON,
|
|
452
|
+
cargo,
|
|
453
|
+
cargoFeatures,
|
|
454
|
+
cargoNoDefaultFeatures,
|
|
455
|
+
cargoAllFeatures,
|
|
456
|
+
cargoTarget,
|
|
457
|
+
cargoManifests,
|
|
307
458
|
brotliQuality,
|
|
308
459
|
add,
|
|
309
460
|
})
|
|
461
|
+
} else if (command === 'github-bundle') {
|
|
462
|
+
const values = parseLeadingOptions(argv, {
|
|
463
|
+
github: { type: 'string' },
|
|
464
|
+
sha: { type: 'string' },
|
|
465
|
+
tag: { type: 'string' },
|
|
466
|
+
directory: { type: 'string' },
|
|
467
|
+
'package-manager': { type: 'string' },
|
|
468
|
+
'package-manager-version': { type: 'string' },
|
|
469
|
+
mapping: { type: 'string' },
|
|
470
|
+
manifests: { type: 'boolean' },
|
|
471
|
+
output: { type: 'string', short: 'o' },
|
|
472
|
+
scope: { type: 'string' },
|
|
473
|
+
lockfile: { type: 'string' },
|
|
474
|
+
conditions: { type: 'string' },
|
|
475
|
+
mainFields: { type: 'string' },
|
|
476
|
+
metro: { type: 'boolean' },
|
|
477
|
+
platforms: { type: 'string', multiple: true },
|
|
478
|
+
jsx: { type: 'boolean' },
|
|
479
|
+
flow: { type: 'boolean' },
|
|
480
|
+
typescript: { type: 'boolean' },
|
|
481
|
+
tsconfig: { type: 'string' },
|
|
482
|
+
resources: { type: 'string' },
|
|
483
|
+
'package-json': { type: 'boolean' },
|
|
484
|
+
'brotli-quality': { type: 'string' },
|
|
485
|
+
generate: { type: 'string', multiple: true },
|
|
486
|
+
}, {
|
|
487
|
+
valueFlags: ['--github', '--sha', '--tag', '--directory', '--package-manager', '--package-manager-version', '--mapping', '--output', '--scope', '--lockfile', '--conditions', '--mainFields', '--platforms', '--tsconfig', '--resources', '--brotli-quality', '--generate', '-o'],
|
|
488
|
+
onError: usage,
|
|
489
|
+
})
|
|
490
|
+
if (values.github === undefined) usage('Error: github-bundle requires --github=owner/name, the repo to bundle')
|
|
491
|
+
if (values.sha !== undefined && values.tag !== undefined) usage('Error: github-bundle takes --sha or --tag, not both')
|
|
492
|
+
// Whether each option applies to the entries is buildGitHubBundle's to say.
|
|
493
|
+
const list = (value) => [value ?? []].flat().flatMap((v) => v.split(',')).map((s) => s.trim()).filter(Boolean)
|
|
494
|
+
let brotliQuality
|
|
495
|
+
if (values['brotli-quality'] !== undefined) {
|
|
496
|
+
try {
|
|
497
|
+
brotliQuality = parseBrotliQuality('--brotli-quality', values['brotli-quality'])
|
|
498
|
+
} catch (cause) {
|
|
499
|
+
usage(`Error: ${cause.message}`)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Tarballs, zips and GitHub trees are cached where `stasis audit` caches.
|
|
503
|
+
const { setCacheDir } = await import('../src/vfs-bundle.js')
|
|
504
|
+
setCacheDir(userCacheDir())
|
|
505
|
+
const { githubBundleCommand } = await import('../src/cmd/github-bundle.js')
|
|
506
|
+
await githubBundleCommand({
|
|
507
|
+
github: values.github,
|
|
508
|
+
sha: values.sha,
|
|
509
|
+
tag: values.tag,
|
|
510
|
+
directory: values.directory,
|
|
511
|
+
packageManager: values['package-manager'],
|
|
512
|
+
packageManagerVersion: values['package-manager-version'],
|
|
513
|
+
entries: argv.length === 0 ? undefined : argv,
|
|
514
|
+
mappingFile: values.mapping,
|
|
515
|
+
manifests: Boolean(values.manifests),
|
|
516
|
+
output: values.output,
|
|
517
|
+
lockfile: values.lockfile,
|
|
518
|
+
scope: values.scope,
|
|
519
|
+
conditions: list(values.conditions),
|
|
520
|
+
mainFields: values.mainFields === undefined ? undefined : list(values.mainFields),
|
|
521
|
+
metro: Boolean(values.metro),
|
|
522
|
+
platforms: list(values.platforms),
|
|
523
|
+
jsx: Boolean(values.jsx),
|
|
524
|
+
flow: Boolean(values.flow),
|
|
525
|
+
typescript: Boolean(values.typescript),
|
|
526
|
+
tsconfig: values.tsconfig,
|
|
527
|
+
resources: list(values.resources),
|
|
528
|
+
packageJSON: Boolean(values['package-json']),
|
|
529
|
+
generate: list(values.generate),
|
|
530
|
+
brotliQuality,
|
|
531
|
+
})
|
|
310
532
|
} else if (command === 'add') {
|
|
311
533
|
// add packs the listed files (no resolver), taking split targets + resource allowlist from
|
|
312
534
|
// stasis.config.json; it takes no flags.
|
|
@@ -409,11 +631,20 @@ if (command === '-v' || command === '--version') {
|
|
|
409
631
|
const { removed, validated } = prune({ root })
|
|
410
632
|
console.warn(`[stasis] prune: validated ${validated.length} file(s), removed ${removed.length} file(s)`)
|
|
411
633
|
} else if (command === 'audit') {
|
|
412
|
-
const values = parseLeadingOptions(argv, { why: { type: 'boolean' }, 'why-deep': { type: 'boolean' }, 'why-full': { type: 'boolean' }, reason: { type: 'string' } }, { valueFlags: ['--reason'], onError: usage })
|
|
634
|
+
const values = parseLeadingOptions(argv, { why: { type: 'boolean' }, 'why-deep': { type: 'boolean' }, 'why-full': { type: 'boolean' }, reason: { type: 'string' }, 'repo-advisories': { type: 'boolean' } }, { valueFlags: ['--reason'], onError: usage })
|
|
413
635
|
if (argv.length === 0) usage('Nothing to audit: no path to file given')
|
|
636
|
+
const repoAdvisories = Boolean(values['repo-advisories'])
|
|
637
|
+
if (repoAdvisories && !process.env.GITHUB_TOKEN) usage('Error: --repo-advisories requires a GitHub token in GITHUB_TOKEN')
|
|
638
|
+
// A Soldeer package's and a GitHub repo's advisories are their repository's, asked of GitHub with
|
|
639
|
+
// GITHUB_TOKEN where it is set; the client asks nothing for any other package by itself.
|
|
640
|
+
const github = (await import('@preventive/upstream/github.js')).createClient({ token: process.env.GITHUB_TOKEN || null })
|
|
641
|
+
// Upstream caches what it looks up (a package's GitHub repo, a month each) only where told to, and
|
|
642
|
+
// answers from it without asking: a per-user directory, never one shared with less trusted jobs.
|
|
643
|
+
const { setCacheDir } = await import('@preventive/upstream/npm.js')
|
|
644
|
+
setCacheDir(userCacheDir())
|
|
414
645
|
const { audit, printAuditReport } = await import('../src/audit.js')
|
|
415
646
|
const files = argv.map((f) => resolve(f))
|
|
416
|
-
const report = await audit(files, { why: Boolean(values.why), whyDeep: Boolean(values['why-deep']), whyFull: Boolean(values['why-full']), reason: values.reason })
|
|
647
|
+
const report = await audit(files, { why: Boolean(values.why), whyDeep: Boolean(values['why-deep']), whyFull: Boolean(values['why-full']), reason: values.reason, repoAdvisories, github })
|
|
417
648
|
printAuditReport(report)
|
|
418
649
|
process.exitCode = report.rows.length === 0 ? 0 : 1
|
|
419
650
|
} else if (command === 'sbom') {
|
package/package.json
CHANGED
|
@@ -1,46 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/stasis",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.6",
|
|
4
4
|
"description": "File-level source bundles and lockfiles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./esbuild": "./src/esbuild.js",
|
|
8
8
|
"./webpack": "./src/webpack.js",
|
|
9
|
+
"./rollup": "./src/rollup.js",
|
|
9
10
|
"./metro": "./src/metro.js",
|
|
10
11
|
"./metro-transformer": "./src/metro-transformer.js",
|
|
11
12
|
"./loader": "./src/loader.js",
|
|
12
13
|
"./mock": "./src/mock.js",
|
|
13
14
|
"./bundle": "./src/bundle.js",
|
|
15
|
+
"./bundle-reader": "./src/bundle-reader.js",
|
|
14
16
|
"./cmd/bundle": "./src/cmd/bundle.js",
|
|
15
17
|
"./cmd/build": "./src/cmd/build.js",
|
|
16
18
|
"./diff": "./src/diff.js",
|
|
17
19
|
"./lockfile": "./src/lockfile.js",
|
|
18
20
|
"./prune": "./src/prune.js",
|
|
19
21
|
"./sbom": "./src/sbom.js",
|
|
22
|
+
"./vfs-bundle": "./src/vfs-bundle.js",
|
|
20
23
|
"./logo.svg": "./logo.svg"
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
26
|
"bin/stasis.js",
|
|
24
27
|
"logo.svg",
|
|
25
|
-
"src/
|
|
26
|
-
"src/apis/npm/semver.cjs",
|
|
28
|
+
"src/audit-corrections.js",
|
|
27
29
|
"src/audit.js",
|
|
28
30
|
"src/babel.js",
|
|
29
31
|
"src/babel-worker.cjs",
|
|
30
32
|
"src/bundle.js",
|
|
31
33
|
"src/bundle-from-lockfile.js",
|
|
34
|
+
"src/bundle-reader.js",
|
|
32
35
|
"src/cmd/bundle.js",
|
|
33
36
|
"src/cmd/build.js",
|
|
34
37
|
"src/cmd/diff.js",
|
|
38
|
+
"src/cmd/github-bundle.js",
|
|
39
|
+
"src/cmd/output.js",
|
|
35
40
|
"src/cmd/sbom.js",
|
|
36
41
|
"src/diff.js",
|
|
37
42
|
"src/esbuild.js",
|
|
43
|
+
"src/json-stream.js",
|
|
38
44
|
"src/loader.js",
|
|
39
45
|
"src/loader-mock.js",
|
|
40
46
|
"src/loaders/bash.js",
|
|
47
|
+
"src/loaders/cargo.js",
|
|
48
|
+
"src/loaders/foundry.js",
|
|
41
49
|
"src/loaders/rust.js",
|
|
42
50
|
"src/loaders/solidity.js",
|
|
51
|
+
"src/loaders/solidity-ownership.js",
|
|
52
|
+
"src/loaders/toml.js",
|
|
43
53
|
"src/loaders/php.js",
|
|
54
|
+
"src/loaders/paths.js",
|
|
44
55
|
"src/lockfile.js",
|
|
45
56
|
"src/metro.js",
|
|
46
57
|
"src/metro-resolver.js",
|
|
@@ -48,9 +59,20 @@
|
|
|
48
59
|
"src/mock.js",
|
|
49
60
|
"src/parse.js",
|
|
50
61
|
"src/prune.js",
|
|
62
|
+
"src/resolve-fields.js",
|
|
63
|
+
"src/resolve-node.js",
|
|
64
|
+
"src/resolve-typescript.js",
|
|
65
|
+
"src/rollup.js",
|
|
51
66
|
"src/sbom.js",
|
|
52
67
|
"src/scan.js",
|
|
53
|
-
"src/
|
|
68
|
+
"src/vfs-bundle.js",
|
|
69
|
+
"src/vfs-bundle/entries.js",
|
|
70
|
+
"src/vfs-bundle/github.js",
|
|
71
|
+
"src/vfs-bundle/prisma.js",
|
|
72
|
+
"src/vfs-bundle/soldeer.js",
|
|
73
|
+
"src/vfs-bundle/tree.js",
|
|
74
|
+
"src/webpack.js",
|
|
75
|
+
"src/why.js"
|
|
54
76
|
],
|
|
55
77
|
"engines": {
|
|
56
78
|
"node": ">=24.14.0"
|
|
@@ -60,25 +82,35 @@
|
|
|
60
82
|
},
|
|
61
83
|
"repository": {
|
|
62
84
|
"type": "git",
|
|
63
|
-
"url": "git+https://github.com/
|
|
85
|
+
"url": "git+https://github.com/PreventiveMeasures/stasis.git",
|
|
64
86
|
"directory": "stasis"
|
|
65
87
|
},
|
|
66
88
|
"author": "Exodus Movement, Inc.",
|
|
67
89
|
"license": "MIT",
|
|
68
90
|
"bugs": {
|
|
69
|
-
"url": "https://github.com/
|
|
91
|
+
"url": "https://github.com/PreventiveMeasures/stasis/issues"
|
|
70
92
|
},
|
|
71
|
-
"homepage": "https://github.com/
|
|
93
|
+
"homepage": "https://github.com/PreventiveMeasures/stasis#readme",
|
|
72
94
|
"dependencies": {
|
|
73
|
-
"@exodus/
|
|
74
|
-
"@exodus/stasis-
|
|
75
|
-
"
|
|
95
|
+
"@exodus/bytes": "^1.16.0",
|
|
96
|
+
"@exodus/stasis-core": "1.0.0-beta.6",
|
|
97
|
+
"@exodus/stasis-plugins": "1.0.0-beta.6",
|
|
98
|
+
"@preventive/archive": "1.0.0-beta.4",
|
|
99
|
+
"@preventive/deptree": "1.0.0-alpha.8",
|
|
100
|
+
"@preventive/lockfile": "1.0.0-alpha.7",
|
|
101
|
+
"@preventive/upstream": "1.0.0-alpha.10",
|
|
102
|
+
"@preventive/vfs": "1.0.0-beta.4",
|
|
103
|
+
"oxc-parser": "^0.152.0"
|
|
76
104
|
},
|
|
77
105
|
"peerDependencies": {
|
|
106
|
+
"@prisma/client-generator-ts": "7.10.0",
|
|
78
107
|
"esbuild": "*",
|
|
79
108
|
"flow-remove-types": "*"
|
|
80
109
|
},
|
|
81
110
|
"peerDependenciesMeta": {
|
|
111
|
+
"@prisma/client-generator-ts": {
|
|
112
|
+
"optional": true
|
|
113
|
+
},
|
|
82
114
|
"esbuild": {
|
|
83
115
|
"optional": true
|
|
84
116
|
},
|