@exodus/stasis 1.0.0-beta.7 → 1.0.0-beta.9
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 +27 -24
- package/bin/stasis.js +106 -177
- package/package.json +7 -5
- package/src/audit-corrections.js +45 -21
- package/src/cmd/bundle.js +194 -47
- package/src/installed-commits.js +182 -0
- package/src/loaders/php.js +43 -16
- package/src/resolve-fields.js +62 -30
- package/src/resolve-node.js +9 -6
- package/src/scan.js +8 -3
- package/src/vfs-bundle/entries.js +9 -5
- package/src/vfs-bundle/github.js +4 -1
- package/src/vfs-bundle/tree.js +49 -15
- package/src/vfs-bundle.js +13 -8
package/src/vfs-bundle/tree.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { constants } from 'node:fs'
|
|
2
|
-
import { basename, dirname, join, relative, resolve, sep } from 'node:path'
|
|
2
|
+
import { basename, dirname, join, posix, relative, resolve, sep } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { isValidRepoField } from '@exodus/stasis-core/bundle'
|
|
5
|
+
import { NO_ENTRY, packageJSONStat, packageJSONText, parseGithubRepository, readJson } from '@exodus/stasis-core/bundle-util'
|
|
5
6
|
import { byName } from '@exodus/stasis-core/host'
|
|
6
7
|
import { hasNodeModulesSegment, isPlainObject } from '@exodus/stasis-core/util'
|
|
7
8
|
import { buildNpmTree, findNpmWorkspaces } from '@preventive/deptree/npm.js'
|
|
@@ -114,6 +115,14 @@ export function checkTarget(name, { os, cpu, libc }) {
|
|
|
114
115
|
if (libc !== undefined && !LIBCS.has(libc)) throw new TypeError(`${name}: libc must be one of 'glibc', 'musl', 'unknown'`)
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// `cache`, where npm packages' tarballs and version documents are kept, as @preventive/upstream's
|
|
119
|
+
// CacheOptions take it: left out, in setCacheDir's cache; a store with `read` and `write`, in place
|
|
120
|
+
// of it; or false, nowhere. Checked as deptree checks it, before anything is fetched.
|
|
121
|
+
export function checkCache(name, cache) {
|
|
122
|
+
if (cache === undefined || cache === false || (typeof cache?.read === 'function' && typeof cache?.write === 'function')) return
|
|
123
|
+
throw new TypeError(`${name}: cache must be false, or a store with read and write, or left out`)
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
// yarn installs a workspace from the root that declares it, whatever yarn.lock is nearer, and any
|
|
118
127
|
// other package from the nearest yarn.lock.
|
|
119
128
|
function yarn1Root(host, cwd) {
|
|
@@ -191,7 +200,7 @@ const PACKAGE_MANAGERS = {
|
|
|
191
200
|
alone: async (names, above) => !(await above()).some((dir) => dir.includes('pnpm-workspace.yaml')),
|
|
192
201
|
projects: (view, pnpm) => findPnpmProjects({ project: view, host: { pnpm } }),
|
|
193
202
|
installs: 'node_modules',
|
|
194
|
-
build: (view, pnpm, _file, given) => buildPnpmTree({ project: view, host: { pnpm, ...target(given) } }),
|
|
203
|
+
build: (view, pnpm, _file, given) => buildPnpmTree({ project: view, host: { pnpm, ...target(given) }, cache: given?.cache }),
|
|
195
204
|
},
|
|
196
205
|
yarn1: {
|
|
197
206
|
kind: 'js',
|
|
@@ -205,7 +214,7 @@ const PACKAGE_MANAGERS = {
|
|
|
205
214
|
installs: 'node_modules',
|
|
206
215
|
build: (view, yarn, file, given) => {
|
|
207
216
|
const { libc: _, ...host } = target({ ...given, libc: 'unknown' }) // yarn 1 matches no libc
|
|
208
|
-
return naming(file, buildYarn1Tree({ project: view, host: { yarn, ...host } }))
|
|
217
|
+
return naming(file, buildYarn1Tree({ project: view, host: { yarn, ...host }, cache: given?.cache }))
|
|
209
218
|
},
|
|
210
219
|
},
|
|
211
220
|
npm: {
|
|
@@ -218,7 +227,7 @@ const PACKAGE_MANAGERS = {
|
|
|
218
227
|
alone: async (names, above) => !(await above()).some((dir) => dir.includes('package.json')),
|
|
219
228
|
projects: (view, _npm, given) => findNpmWorkspaces({ project: view, os: target(given).os }),
|
|
220
229
|
installs: 'node_modules',
|
|
221
|
-
build: (view, npm, file, given) => naming(file, buildNpmTree({ project: view, host: npmHost(npm, given) })),
|
|
230
|
+
build: (view, npm, file, given) => naming(file, buildNpmTree({ project: view, host: npmHost(npm, given), cache: given?.cache })),
|
|
222
231
|
},
|
|
223
232
|
soldeer: {
|
|
224
233
|
kind: 'sol',
|
|
@@ -343,15 +352,17 @@ export function checkVfs(name, vfs) {
|
|
|
343
352
|
}
|
|
344
353
|
}
|
|
345
354
|
|
|
346
|
-
// -> { root, vfs, projects, stats, packageManager, packageManagerVersion }, of the project
|
|
347
|
-
// holds that `cwd` is in, as `packageManager` installs it: the directory it installs from,
|
|
348
|
-
// path; a new Vfs holding the tree, rooted there; the directories of the projects it finds,
|
|
349
|
-
// there; deptree's counts
|
|
350
|
-
//
|
|
351
|
-
// view of `project`, which nothing is
|
|
352
|
-
// client, is what a Soldeer git
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
+
// -> { root, vfs, projects, stats, installed, packageManager, packageManagerVersion }, of the project
|
|
356
|
+
// `project` holds that `cwd` is in, as `packageManager` installs it: the directory it installs from,
|
|
357
|
+
// as a real path; a new Vfs holding the tree, rooted there; the directories of the projects it finds,
|
|
358
|
+
// from there; deptree's counts, and what it installs, each by its `path` from there; and the version
|
|
359
|
+
// reproduced, `packageManagerVersion` if given, else the one the root package.json's packageManager
|
|
360
|
+
// pins, else the default. deptree reads the project through a view of `project`, which nothing is
|
|
361
|
+
// written through. `client`, a @preventive/upstream/github.js client, is what a Soldeer git
|
|
362
|
+
// dependency is fetched through (deptree's `github`); without one, a lockfile holding one is refused
|
|
363
|
+
// before anything is fetched. `cache` (checkCache) is where npm packages' tarballs and version
|
|
364
|
+
// documents are kept, which deptree fetches for pnpm, yarn1 and npm.
|
|
365
|
+
async function layOutTree({ project, packageManager, cwd, packageManagerVersion, os, cpu, libc, client, cache }) {
|
|
355
366
|
const pm = PACKAGE_MANAGERS[packageManager]
|
|
356
367
|
const found = pm.root(project, cwd, os)
|
|
357
368
|
if (found === null) throw new Error(`no ${pm.lockfile} found in ${cwd} or any parent directory`)
|
|
@@ -364,17 +375,40 @@ async function layOutTree({ project, packageManager, cwd, packageManagerVersion,
|
|
|
364
375
|
const projects = new Set(pm.projects(view, version, { os, cpu, libc }))
|
|
365
376
|
const other = pm.kind === 'js' ? outsider(project, found, cwd, projects) : null
|
|
366
377
|
if (other !== null) throw new Error(`${file} does not install ${other}: it is none of the lockfile's projects`)
|
|
367
|
-
const { vfs, stats } = await pm.build(view, version, file, { os, cpu, libc, client })
|
|
378
|
+
const { vfs, stats, installed } = await pm.build(view, version, file, { os, cpu, libc, client, cache })
|
|
368
379
|
return {
|
|
369
380
|
root: project.realpath(found),
|
|
370
381
|
vfs,
|
|
371
382
|
projects,
|
|
372
383
|
stats,
|
|
384
|
+
installed,
|
|
373
385
|
packageManager,
|
|
374
386
|
packageManagerVersion: version ?? (String(pinned).startsWith(`${pm.pin}@`) ? /^[^@]+@([^+]+)/u.exec(pinned)[1] : undefined),
|
|
375
387
|
}
|
|
376
388
|
}
|
|
377
389
|
|
|
390
|
+
// The root of the GitHub repository a git URL names, or undefined for another host's.
|
|
391
|
+
const githubRoot = (url) => {
|
|
392
|
+
const github = parseGithubRepository(url)
|
|
393
|
+
return github === null ? undefined : { github, directory: '' }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Each dependency of `bundle`, its paths from `root`, at the commit the tree (layOutTree's) installs
|
|
397
|
+
// it from, where deptree names a full one: an npm package at its gitHead, which the registry's
|
|
398
|
+
// version document names, a commit of the repository its own package.json names, so taken only
|
|
399
|
+
// where the bundle records that one; a Soldeer git dependency at its lockfile's `rev`, the root of
|
|
400
|
+
// the repository it is fetched from, where that is a GitHub one. Metadata, as `repo` is: the
|
|
401
|
+
// publisher's word, held to no repository.
|
|
402
|
+
export function pinCommits(bundle, root, { root: from, installed }) {
|
|
403
|
+
const byDir = new Map(installed.map((pkg) => [posix.relative(root, posix.join(from, pkg.path)), pkg]))
|
|
404
|
+
for (const [dir, info] of bundle.modules) {
|
|
405
|
+
const pkg = byDir.get(dir)
|
|
406
|
+
if (!isValidRepoField('commit', pkg?.commit)) continue
|
|
407
|
+
const repo = info.repo ?? githubRoot(pkg.git)
|
|
408
|
+
if (repo !== undefined) bundle.modules.set(dir, { ...info, repo: { ...repo, commit: pkg.commit } })
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
378
412
|
// fs.Stats#mode carries the file type above the permission bits a Vfs stat holds.
|
|
379
413
|
const TYPE_BITS = { file: constants.S_IFREG, directory: constants.S_IFDIR, symlink: constants.S_IFLNK }
|
|
380
414
|
|
package/src/vfs-bundle.js
CHANGED
|
@@ -3,7 +3,7 @@ import { posix } from 'node:path'
|
|
|
3
3
|
import { Vfs } from '@preventive/vfs'
|
|
4
4
|
import { KINDS, checkAhead } from './vfs-bundle/entries.js'
|
|
5
5
|
import { suggestedRepoEntries } from './vfs-bundle/github.js'
|
|
6
|
-
import { NODE_MODULES_MANAGERS, checkTarget, checkVfs, detectPackageManager, loadTree, packageManagerFor, vfsHost } from './vfs-bundle/tree.js'
|
|
6
|
+
import { NODE_MODULES_MANAGERS, checkCache, checkTarget, checkVfs, detectPackageManager, loadTree, packageManagerFor, vfsHost } from './vfs-bundle/tree.js'
|
|
7
7
|
|
|
8
8
|
// @exodus/stasis/vfs-bundle: static bundles from a project's lockfile alone, through the
|
|
9
9
|
// dependencies its package manager would install (`packageManager`: 'pnpm', pnpm 9, 10, 11 or 12;
|
|
@@ -30,10 +30,12 @@ export function createVfsHost(vfs) {
|
|
|
30
30
|
return vfsHost(vfs, { cache: false })
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// -> { root, vfs, projects, host, stats, packageManager, packageManagerVersion }, as
|
|
34
|
-
// 'yarn1' or 'npm' installs node_modules: the directory in the project's Vfs it installs cwd
|
|
35
|
-
// which holds the lockfile; the Vfs the tree is laid out into, rooted there; the projects'
|
|
36
|
-
// directories from there; the host reading the project through the tree; deptree's counts
|
|
33
|
+
// -> { root, vfs, projects, host, stats, installed, packageManager, packageManagerVersion }, as
|
|
34
|
+
// 'pnpm', 'yarn1' or 'npm' installs node_modules: the directory in the project's Vfs it installs cwd
|
|
35
|
+
// from, which holds the lockfile; the Vfs the tree is laid out into, rooted there; the projects'
|
|
36
|
+
// directories from there; the host reading the project through the tree; deptree's counts, and the
|
|
37
|
+
// packages it installs (each by its `path` from there, with the `commit` its version document
|
|
38
|
+
// names, where it names a full one); and the
|
|
37
39
|
// package manager reproduced, at `packageManagerVersion` if given, else the one the root
|
|
38
40
|
// package.json's packageManager pins, else pnpm 10.33.4, yarn 1.22.22 or npm 11.21.0 (which nothing
|
|
39
41
|
// pins: npm reads no packageManager). The host caches what it reads, of the project's Vfs too, so
|
|
@@ -41,15 +43,18 @@ export function createVfsHost(vfs) {
|
|
|
41
43
|
// `os`, `cpu` and `libc` ('glibc', 'musl' or 'unknown', pnpm's and npm's, which takes 'unknown' for
|
|
42
44
|
// none) are the machine packages are matched against: this one's but for what is given (for another
|
|
43
45
|
// os, libc defaults to 'unknown'). Without a `packageManager`, it is the one of the three whose
|
|
44
|
-
// lockfile installs cwd, where only one's does.
|
|
45
|
-
|
|
46
|
+
// lockfile installs cwd, where only one's does. `cache` is where the packages' tarballs and version
|
|
47
|
+
// documents are kept, as @preventive/upstream's CacheOptions take it: left out, in setCacheDir's
|
|
48
|
+
// cache; a store with `read` and `write`, in place of it; or false, nowhere.
|
|
49
|
+
export async function loadNodeModules({ vfs, packageManager, cwd = '/', packageManagerVersion, os, cpu, libc, cache } = {}) {
|
|
46
50
|
checkVfs('loadNodeModules', vfs)
|
|
47
51
|
checkTarget('loadNodeModules', { os, cpu, libc })
|
|
52
|
+
checkCache('loadNodeModules', cache)
|
|
48
53
|
const project = vfsHost(vfs)
|
|
49
54
|
// A real path, as detection and the layout take it.
|
|
50
55
|
cwd = project.realpath(posix.resolve('/', cwd))
|
|
51
56
|
packageManager = packageManagerFor('loadNodeModules', project, cwd, { packageManager, packageManagerVersion, os }, NODE_MODULES_MANAGERS)
|
|
52
|
-
return loadTree({ project, packageManager, cwd, packageManagerVersion, os, cpu, libc })
|
|
57
|
+
return loadTree({ project, packageManager, cwd, packageManagerVersion, os, cpu, libc, cache })
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
// -> the entries buildGitHubBundle takes where none are given, as paths from the project's
|