vinder 0.0.0-stage → 0.1.2

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -2
  3. package/bin/vinder.js +36 -0
  4. 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
- # Temporary Holding Version
1
+ # vinder
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
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.2",
25
+ "@jmelahman/vinder-darwin-x64": "0.1.2",
26
+ "@jmelahman/vinder-linux-arm64": "0.1.2",
27
+ "@jmelahman/vinder-linux-x64": "0.1.2",
28
+ "@jmelahman/vinder-win32-arm64": "0.1.2",
29
+ "@jmelahman/vinder-win32-x64": "0.1.2"
30
+ }
31
+ }