vinder 0.0.0-stage → 0.1.3
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/LICENSE +21 -0
- package/README.md +223 -2
- package/bin/vinder.js +36 -0
- package/package.json +29 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jamison Lahman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,224 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vinder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vendored npm dependencies for projects that have no build step.
|
|
4
|
+
|
|
5
|
+
A project that serves third-party browser code from a committed `vendor/`
|
|
6
|
+
directory usually has no record of where that code came from, so nothing
|
|
7
|
+
can tell it is out of date or vulnerable. vinder makes `vendor/` a generated
|
|
8
|
+
artifact: package.json and its lockfile pin the versions, `vinder.toml` says
|
|
9
|
+
what goes into `vendor/`, and vinder regenerates it, checks it, and audits
|
|
10
|
+
it against [OSV](https://osv.dev). The project still runs straight from its
|
|
11
|
+
files; node_modules exists only while vinder runs.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
go install github.com/jmelahman/vinder@latest
|
|
17
|
+
# or, from npm, as a prebuilt binary
|
|
18
|
+
bun add -d vinder # npm install -D vinder, pnpm add -D vinder
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The npm package runs the binary for the platform from an optional
|
|
22
|
+
dependency (`@jmelahman/vinder-linux-x64` and so on), the way esbuild does,
|
|
23
|
+
so it needs no install script and works with bun and pnpm, which skip them.
|
|
24
|
+
|
|
25
|
+
vinder bundles with [esbuild](https://esbuild.github.io), which is built
|
|
26
|
+
in, and installs with the project's own package manager, which has to be on
|
|
27
|
+
`PATH`. It supports bun, npm, [Nub](https://nubjs.com), pnpm and Yarn, and
|
|
28
|
+
picks one the way corepack does: the `packageManager` field of package.json
|
|
29
|
+
(or `devEngines.packageManager`), and otherwise whichever lockfile is
|
|
30
|
+
present. Lockfiles from two package managers and no `packageManager` field
|
|
31
|
+
is an error rather than a guess.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
vinder sync # install, regenerate vendor/, update the lockfile
|
|
37
|
+
vinder check # fail if vendor/ differs from what the lockfile makes
|
|
38
|
+
vinder check --all # ...in any project, or if two vendor different versions
|
|
39
|
+
vinder audit # report OSV advisories for everything vendored
|
|
40
|
+
vinder audit --all # every project in the git repository
|
|
41
|
+
vinder sync --align --all # move projects onto one version of each shared package
|
|
42
|
+
vinder upgrade --all # bump to latest, re-resolve, align; prints a PR body
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Each command takes project directories as arguments (the current directory
|
|
46
|
+
by default) or `--all`, which finds every `vinder.toml` in the enclosing git
|
|
47
|
+
repository, skipping `node_modules` and hidden directories.
|
|
48
|
+
|
|
49
|
+
- **sync** installs in the project without running lifecycle scripts
|
|
50
|
+
(`--ignore-scripts`, or `YARN_ENABLE_SCRIPTS=false` for Yarn 2+), builds
|
|
51
|
+
and copies every output, and writes `vendor/vinder.json`. It replaces only
|
|
52
|
+
the directories it owns (one per `[deps.<key>]`) and the manifest, and
|
|
53
|
+
removes the directory of a key that has been dropped from the config.
|
|
54
|
+
Anything else in `vendor/` is left alone.
|
|
55
|
+
- **check** does the same from the lockfile alone (`--frozen-lockfile`,
|
|
56
|
+
`npm ci` or `yarn install --immutable`) into a temporary directory and
|
|
57
|
+
lists every file that is `modified`, `missing` or `extra`.
|
|
58
|
+
It exits 1 if there are any, so it catches hand edits, a config change
|
|
59
|
+
that was never synced, and a package.json bump without a lockfile update.
|
|
60
|
+
Given several projects (or `--all`), it also compares their manifests
|
|
61
|
+
and exits 1 if one package, vendored or bundled in, appears at more than
|
|
62
|
+
one version, listing which projects have which. Each project keeps its
|
|
63
|
+
own package.json and lockfile, so it still installs on its own; this is
|
|
64
|
+
what keeps them in step.
|
|
65
|
+
- **audit** reads only the manifests, so it needs no install. It sends one
|
|
66
|
+
OSV batch query for every package in them and exits 1 if any has an
|
|
67
|
+
advisory that is not ignored.
|
|
68
|
+
- **sync --align** syncs, then settles every package `check` would call
|
|
69
|
+
skewed. It picks the highest version vendored anywhere that satisfies
|
|
70
|
+
every range asked of the package: by each project's package.json and
|
|
71
|
+
overrides, and by every vendored package that depends on it. A project on
|
|
72
|
+
another version is moved to it, by its dependency spec if the package is
|
|
73
|
+
a direct dependency (keeping `^` or `~`), and otherwise by an override
|
|
74
|
+
(`overrides` for npm, bun and nub, `pnpm.overrides`, or Yarn's
|
|
75
|
+
`resolutions`), which `vinder.json` records under `aligned` as vinder's.
|
|
76
|
+
The changed projects are synced again. A package no one version satisfies
|
|
77
|
+
is reported and left alone.
|
|
78
|
+
- **upgrade** is Dependabot for vendored dependencies. It raises each
|
|
79
|
+
direct dependency with a plain spec (`1.2.3`, `^1.2.3`, `~1.2.3`) to the
|
|
80
|
+
registry's latest release (`--registry`, npmjs.org by default), drops the
|
|
81
|
+
overrides an earlier alignment added, deletes the lockfile so every
|
|
82
|
+
transitive dependency resolves to the newest release its ranges allow,
|
|
83
|
+
syncs, and aligns. Ranges, tags and `file:`/git specs are left alone, as
|
|
84
|
+
are overrides you wrote. It prints what changed as Markdown. If a step
|
|
85
|
+
fails, package.json and the lockfile are put back.
|
|
86
|
+
|
|
87
|
+
### Scheduled upgrades
|
|
88
|
+
|
|
89
|
+
Dependabot can bump package.json and the lockfile but can't regenerate
|
|
90
|
+
`vendor/`, so its PRs fail `vinder check`. Run `upgrade` from a workflow
|
|
91
|
+
instead:
|
|
92
|
+
|
|
93
|
+
A pull request opened with `GITHUB_TOKEN` doesn't trigger CI, so the
|
|
94
|
+
workflow opens it as a GitHub App. `vinder github-app` creates one: it opens
|
|
95
|
+
GitHub on an App manifest (contents and pull requests write), takes the
|
|
96
|
+
credentials back, and stores the client ID as the `VINDER_APP_CLIENT_ID`
|
|
97
|
+
variable and the private key as the `VINDER_APP_PRIVATE_KEY` secret of the
|
|
98
|
+
repository (`--repo`, the `origin` remote by default) with `gh`. Then
|
|
99
|
+
install the App on the repository from the page it prints. `--org` creates
|
|
100
|
+
it under an organization, and `--no-secrets` writes the key to a file and
|
|
101
|
+
prints the `gh` commands instead.
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
on:
|
|
105
|
+
schedule:
|
|
106
|
+
- cron: "23 6 * * 1"
|
|
107
|
+
workflow_dispatch:
|
|
108
|
+
permissions: {}
|
|
109
|
+
jobs:
|
|
110
|
+
upgrade:
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
permissions:
|
|
113
|
+
contents: read
|
|
114
|
+
steps:
|
|
115
|
+
- uses: actions/checkout@v6
|
|
116
|
+
with:
|
|
117
|
+
persist-credentials: false
|
|
118
|
+
- uses: oven-sh/setup-bun@v2 # or the projects' package managers
|
|
119
|
+
- run: bunx vinder@latest upgrade --all > "$RUNNER_TEMP/body.md"
|
|
120
|
+
- uses: actions/create-github-app-token@v3
|
|
121
|
+
id: app
|
|
122
|
+
with:
|
|
123
|
+
client-id: ${{ vars.VINDER_APP_CLIENT_ID }}
|
|
124
|
+
private-key: ${{ secrets.VINDER_APP_PRIVATE_KEY }}
|
|
125
|
+
permission-contents: write
|
|
126
|
+
permission-pull-requests: write
|
|
127
|
+
- uses: peter-evans/create-pull-request@v8
|
|
128
|
+
with:
|
|
129
|
+
token: ${{ steps.app.outputs.token }}
|
|
130
|
+
branch: vinder/upgrade
|
|
131
|
+
title: Upgrade vendored npm dependencies
|
|
132
|
+
body-path: ${{ runner.temp }}/body.md
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Leave the vinder projects out of `dependabot.yml`.
|
|
136
|
+
|
|
137
|
+
### pre-commit
|
|
138
|
+
|
|
139
|
+
To enforce this on commit with [pre-commit](https://pre-commit.com) or
|
|
140
|
+
[prek](https://prek.j178.dev):
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
repos:
|
|
144
|
+
- repo: https://github.com/jmelahman/vinder
|
|
145
|
+
rev: v0.1.0
|
|
146
|
+
hooks:
|
|
147
|
+
- id: vinder-check
|
|
148
|
+
- id: vinder-audit
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`vinder-check` runs `check --all` on every commit; with the package
|
|
152
|
+
manager's cache warm it takes a fraction of a second. `vinder-audit` runs
|
|
153
|
+
`audit --all` only when a `vinder.json` or `vinder.toml` changes, since it
|
|
154
|
+
goes to the network; run it on a schedule in CI to hear about new
|
|
155
|
+
advisories. Both need the projects' package managers on `PATH`.
|
|
156
|
+
`vinder-check-system` and `vinder-audit-system` use a vinder already on
|
|
157
|
+
`PATH` instead of building one.
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
`vinder.toml` sits next to the project's package.json:
|
|
162
|
+
|
|
163
|
+
```toml
|
|
164
|
+
# out = "vendor" # the default
|
|
165
|
+
|
|
166
|
+
[deps.toastui-editor] # -> vendor/toastui-editor/
|
|
167
|
+
package = "@toast-ui/editor" # the package being vendored
|
|
168
|
+
embeds = ["dompurify@2.3.3"] # code it inlines, for the audit
|
|
169
|
+
|
|
170
|
+
[deps.toastui-editor.bundle] # output = entry, built by esbuild
|
|
171
|
+
"toastui-editor-all.min.js" = "./vendor.src/toastui-editor.js"
|
|
172
|
+
"toastui-editor.min.css" = "@toast-ui/editor/dist/toastui-editor.css"
|
|
173
|
+
|
|
174
|
+
[deps.toastui-editor.copy] # output = source, copied as is
|
|
175
|
+
LICENSE = "./vendor.src/toastui-editor.LICENSE"
|
|
176
|
+
|
|
177
|
+
[deps.toastui-editor.ignore] # advisory ID or name@version = reason
|
|
178
|
+
"dompurify@2.3.3" = "never runs: app.js replaces the sanitizer"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- A source or entry starting with `./` is a file in the project, and
|
|
182
|
+
anything else a path in `node_modules`.
|
|
183
|
+
- `bundle` outputs are minified by esbuild for the browser: `.js` as an
|
|
184
|
+
IIFE, `.mjs` as an ES module, `.css` with its imports inlined and the
|
|
185
|
+
images and fonts its `url()`s name copied beside it under a content hash.
|
|
186
|
+
License comments are kept, at the end of the file. Bundling is how a
|
|
187
|
+
dependency gets smaller: the entry imports only what the project uses,
|
|
188
|
+
and the rest is tree-shaken away. An entry that should set a global does
|
|
189
|
+
so itself (`window.toastui = { Editor }`).
|
|
190
|
+
- To change a dependency's code, patch it with the package manager
|
|
191
|
+
(`bun patch`, `pnpm patch`, `nub patch`, `yarn patch`, or
|
|
192
|
+
[patch-package](https://github.com/ds300/patch-package) with npm, which
|
|
193
|
+
needs its postinstall script run by hand as vinder skips scripts). The
|
|
194
|
+
patch is applied on install, so the change survives upgrades or
|
|
195
|
+
fails loudly when it no longer applies. Don't edit `vendor/`: `check`
|
|
196
|
+
will flag it.
|
|
197
|
+
- `overrides` in package.json pins a transitive dependency, for instance
|
|
198
|
+
one a newer release broke.
|
|
199
|
+
|
|
200
|
+
### What the audit covers
|
|
201
|
+
|
|
202
|
+
`vendor/vinder.json` records, for each dependency, the packages whose code
|
|
203
|
+
is actually in its files: the package itself, every package esbuild's metafile
|
|
204
|
+
says a bundle pulled in, and `embeds`. A package that is in the lockfile but
|
|
205
|
+
tree-shaken out of every bundle is not audited, because none of its code
|
|
206
|
+
ships.
|
|
207
|
+
|
|
208
|
+
`embeds` exists because some packages inline other packages in their own
|
|
209
|
+
dist files, and no lockfile can see that. `@toast-ui/editor`, for example,
|
|
210
|
+
ships its own copy of DOMPurify 2.3.3 whatever version the lockfile resolves.
|
|
211
|
+
Finding these takes reading the dist files: version strings and license
|
|
212
|
+
banners are the usual clues.
|
|
213
|
+
|
|
214
|
+
An `ignore` entry needs a reason. A `name@version` key covers every
|
|
215
|
+
advisory for that version, including ones published later. An ID covers
|
|
216
|
+
just that advisory.
|
|
217
|
+
|
|
218
|
+
## Develop
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
go test -race ./... # under each package manager on PATH
|
|
222
|
+
prek run --all-files # lint, as CI runs it
|
|
223
|
+
prek run --all-files --hook-stage pre-push
|
|
224
|
+
```
|
package/bin/vinder.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// Runs vinder from the package for this platform, which the package manager
|
|
4
|
+
// installed as an optional dependency.
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const platforms = {
|
|
8
|
+
"darwin-x64": "@jmelahman/vinder-darwin-x64",
|
|
9
|
+
"linux-x64": "@jmelahman/vinder-linux-x64",
|
|
10
|
+
"linux-arm64": "@jmelahman/vinder-linux-arm64",
|
|
11
|
+
"darwin-arm64": "@jmelahman/vinder-darwin-arm64",
|
|
12
|
+
"win32-x64": "@jmelahman/vinder-win32-x64",
|
|
13
|
+
"win32-arm64": "@jmelahman/vinder-win32-arm64"
|
|
14
|
+
};
|
|
15
|
+
const target = process.platform + "-" + process.arch;
|
|
16
|
+
const pkg = platforms[target];
|
|
17
|
+
if (!pkg) {
|
|
18
|
+
console.error("vinder: no binary for " + target);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
let exe;
|
|
22
|
+
try {
|
|
23
|
+
exe = require.resolve(pkg + "/bin/vinder" + (process.platform === "win32" ? ".exe" : ""));
|
|
24
|
+
} catch {
|
|
25
|
+
console.error("vinder: " + pkg + " is not installed; it is an optional dependency of vinder, so install without --omit=optional or --no-optional.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const res = spawnSync(exe, process.argv.slice(2), { stdio: "inherit" });
|
|
29
|
+
if (res.error) {
|
|
30
|
+
console.error("vinder: " + res.error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (res.signal) {
|
|
34
|
+
process.kill(process.pid, res.signal);
|
|
35
|
+
}
|
|
36
|
+
process.exit(res.status ?? 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vinder",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Vendor npm dependencies into a no-build project: bundle them with esbuild, check the result is current, and audit it against OSV.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"vendor",
|
|
8
|
+
"esbuild",
|
|
9
|
+
"bundle",
|
|
10
|
+
"osv",
|
|
11
|
+
"audit",
|
|
12
|
+
"pre-commit"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/jmelahman/vinder#readme",
|
|
15
|
+
"bugs": "https://github.com/jmelahman/vinder/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/jmelahman/vinder.git"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"vinder": "bin/vinder.js"
|
|
22
|
+
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"@jmelahman/vinder-darwin-arm64": "0.1.3",
|
|
25
|
+
"@jmelahman/vinder-darwin-x64": "0.1.3",
|
|
26
|
+
"@jmelahman/vinder-linux-arm64": "0.1.3",
|
|
27
|
+
"@jmelahman/vinder-linux-x64": "0.1.3",
|
|
28
|
+
"@jmelahman/vinder-win32-arm64": "0.1.3",
|
|
29
|
+
"@jmelahman/vinder-win32-x64": "0.1.3"
|
|
30
|
+
}
|
|
31
|
+
}
|