@exodus/stasis 1.0.0-beta.3 → 1.0.0-beta.5
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 +7 -1
- package/bin/stasis.js +236 -17
- package/package.json +36 -9
- package/src/audit-corrections.js +68 -0
- package/src/audit.js +153 -108
- package/src/bundle-reader.js +77 -0
- package/src/cmd/build.js +3 -5
- package/src/cmd/bundle.js +571 -376
- 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 +6 -6
- package/src/json-stream.js +283 -0
- package/src/loaders/bash.js +17 -36
- package/src/loaders/cargo.js +1699 -0
- package/src/loaders/foundry.js +923 -0
- package/src/loaders/paths.js +33 -0
- package/src/loaders/php.js +208 -124
- 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 +8 -23
- package/src/mock.js +3 -5
- package/src/resolve-fields.js +331 -0
- package/src/resolve-node.js +416 -0
- package/src/resolve-typescript.js +427 -0
- package/src/rollup.js +5 -0
- package/src/sbom.js +32 -18
- package/src/scan.js +79 -43
- package/src/vfs-bundle/entries.js +140 -0
- package/src/vfs-bundle/github.js +193 -0
- package/src/vfs-bundle/soldeer.js +125 -0
- package/src/vfs-bundle/tree.js +606 -0
- package/src/vfs-bundle.js +74 -0
- package/src/why.js +434 -0
- package/src/apis/npm/index.js +0 -36
- package/src/apis/npm/semver.cjs +0 -42
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { isTomlTable, readToml } from '../loaders/toml.js'
|
|
4
|
+
|
|
5
|
+
// What `soldeer install` (0.12.0) writes beside the dependencies folder after installing it
|
|
6
|
+
// (soldeer-core's update_config_libs and edit_remappings with RemappingsAction::Update): the
|
|
7
|
+
// remappings.txt it would write is written into the tree, which then serves it in place of the
|
|
8
|
+
// project's; a foundry.toml it would edit is refused, as is what this can't tell it would leave as
|
|
9
|
+
// it is. `view` is the project's root by paths from `/`, `root` its real path, for messages.
|
|
10
|
+
|
|
11
|
+
const SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*)?(?:\+[\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*)?$/u
|
|
12
|
+
// A requirement Soldeer takes as exact: a bare version (its `^` made `=`), or one written `=`.
|
|
13
|
+
const EXACT = /^=?\s*(\S+)$/u
|
|
14
|
+
const withoutBuild = (version) => version.replace(/\+.*$/u, '')
|
|
15
|
+
|
|
16
|
+
// A file's text as Soldeer reads it (a byte order mark kept), or undefined.
|
|
17
|
+
function textOf(view, path) {
|
|
18
|
+
try {
|
|
19
|
+
return view.stat(path).type === 'file' ? view.readFile(path).toString('utf8') : undefined
|
|
20
|
+
} catch {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Rust's `Path::components` of a remapping's target, `.` kept only at the start.
|
|
26
|
+
function components(path) {
|
|
27
|
+
const out = path.startsWith('/') ? ['/'] : []
|
|
28
|
+
for (const [i, part] of path.split('/').entries()) {
|
|
29
|
+
if (part !== '' && (part !== '.' || i === 0)) out.push(part)
|
|
30
|
+
}
|
|
31
|
+
return out
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Whether the folder `name` of a remapping's target (its dependencies folder and the one in it) is
|
|
35
|
+
// `dep`'s, as soldeer's path_matches tells, or null where only Rust's semver could tell.
|
|
36
|
+
function folderMatches(dep, name) {
|
|
37
|
+
if (!name.startsWith(`${dep.name}-`)) return false
|
|
38
|
+
const version = name.slice(dep.name.length + 1)
|
|
39
|
+
if (version === dep.version) return true
|
|
40
|
+
if (!SEMVER.test(version)) return version === dep.req
|
|
41
|
+
const exact = EXACT.exec(dep.req.trim())?.[1]
|
|
42
|
+
if (exact !== undefined && SEMVER.test(exact)) return withoutBuild(exact) === withoutBuild(version)
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The remappings Soldeer's update leaves, of `existing` (`[name, target]` pairs) and the config's
|
|
47
|
+
// dependencies, sorted as it sorts them (by their UTF-8 bytes).
|
|
48
|
+
function updatedRemappings(existing, deps, { regenerate, where }) {
|
|
49
|
+
const items = deps.map((dep) => ({ dep, line: `${dep.remapped}=dependencies/${dep.folder}/` }))
|
|
50
|
+
let lines
|
|
51
|
+
if (regenerate || existing.length === 0) {
|
|
52
|
+
lines = items.map(({ line }) => line)
|
|
53
|
+
} else {
|
|
54
|
+
lines = []
|
|
55
|
+
let rest = existing
|
|
56
|
+
for (const { dep, line } of items) {
|
|
57
|
+
let found = false
|
|
58
|
+
rest = rest.filter(([name, target]) => {
|
|
59
|
+
const path = components(target).slice(0, 2)
|
|
60
|
+
const folder = path.at(-1)
|
|
61
|
+
const matches = folder === undefined || ['/', '.', '..'].includes(folder) ? false : folderMatches(dep, folder)
|
|
62
|
+
if (matches === null) throw new Error(`${where}: "${name}=${target}" names ${folder}, which \`soldeer install\` may rewrite to dependencies/${dep.folder}`)
|
|
63
|
+
if (!matches) return true
|
|
64
|
+
lines.push(`${name}=${target.replaceAll(path.join('/').replace(/^\/\//u, '/'), `dependencies/${dep.folder}`)}`)
|
|
65
|
+
found = true
|
|
66
|
+
return false
|
|
67
|
+
})
|
|
68
|
+
if (!found) lines.push(line)
|
|
69
|
+
}
|
|
70
|
+
lines.push(...rest.map(([name, target]) => `${name}=${target}`))
|
|
71
|
+
}
|
|
72
|
+
return lines.toSorted((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// `[name, target]` of each remapping in `lines`, as Soldeer reads them: split at the first `=`, any
|
|
76
|
+
// line without one passed over.
|
|
77
|
+
const pairs = (lines) => lines.flatMap((line) => {
|
|
78
|
+
const at = line.indexOf('=')
|
|
79
|
+
return at === -1 ? [] : [[line.slice(0, at), line.slice(at + 1)]]
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export function settleSoldeer(view, vfs, root) {
|
|
83
|
+
const foundryText = textOf(view, '/foundry.toml')
|
|
84
|
+
const foundry = foundryText === undefined ? undefined : readToml(foundryText)
|
|
85
|
+
const configFile = isTomlTable(foundry?.dependencies) ? 'foundry.toml' : 'soldeer.toml'
|
|
86
|
+
const config = configFile === 'foundry.toml' ? foundry : readToml(textOf(view, '/soldeer.toml') ?? '')
|
|
87
|
+
const where = (file) => join(root, file)
|
|
88
|
+
|
|
89
|
+
if (configFile === 'foundry.toml') {
|
|
90
|
+
const libs = foundry.profile?.default?.libs
|
|
91
|
+
if (!Array.isArray(libs) || !libs.includes('dependencies')) {
|
|
92
|
+
throw new Error(`${where('foundry.toml')}: [profile.default] libs holds no "dependencies", which \`soldeer install\` adds`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const settings = { remappings_generate: true, remappings_regenerate: false, remappings_version: true, remappings_prefix: '', remappings_location: 'txt', ...config.soldeer }
|
|
97
|
+
if (!settings.remappings_generate) return
|
|
98
|
+
const locked = new Map(readToml(textOf(view, '/soldeer.lock') ?? '').dependencies?.map((entry) => [entry.name, entry.version]) ?? [])
|
|
99
|
+
const deps = Object.entries(config.dependencies ?? {}).map(([name, value]) => {
|
|
100
|
+
const req = typeof value === 'string' ? value : value.version
|
|
101
|
+
const version = locked.get(name)
|
|
102
|
+
const remapped = `${settings.remappings_prefix}${name}${settings.remappings_version ? `-${req.replaceAll('=', '')}` : ''}/`
|
|
103
|
+
return { name, req, version, folder: `${name}-${version}`, remapped }
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const remappingsTxt = textOf(view, '/remappings.txt')
|
|
107
|
+
if (configFile === 'foundry.toml' && settings.remappings_location === 'config') {
|
|
108
|
+
if (remappingsTxt !== undefined) throw new Error(`${where('remappings.txt')}: \`soldeer install\` removes it, as remappings_location is "config"`)
|
|
109
|
+
for (const [name, profile] of Object.entries(isTomlTable(foundry.profile) ? foundry.profile : {})) {
|
|
110
|
+
const current = Array.isArray(profile?.remappings) ? profile.remappings : undefined
|
|
111
|
+
if (current === undefined && name !== 'default') continue
|
|
112
|
+
const updated = updatedRemappings(pairs((current ?? []).filter((r) => typeof r === 'string')), deps, { regenerate: settings.remappings_regenerate, where: where('foundry.toml') })
|
|
113
|
+
if (current === undefined || updated.length !== current.length || updated.some((r, i) => r !== current[i])) {
|
|
114
|
+
throw new Error(`${where('foundry.toml')}: [profile.${name}] remappings are not as \`soldeer install\` leaves them`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const lines = (settings.remappings_regenerate ? '' : remappingsTxt ?? '').split('\n')
|
|
121
|
+
if (lines.at(-1) === '') lines.pop()
|
|
122
|
+
const existing = pairs(lines.map((line) => line.replace(/\r$/u, '')))
|
|
123
|
+
const text = updatedRemappings(existing, deps, { regenerate: settings.remappings_regenerate, where: where('remappings.txt') }).map((line) => `${line}\n`).join('')
|
|
124
|
+
if (text !== remappingsTxt) vfs.writeFile('/remappings.txt', text, { mode: remappingsTxt === undefined ? 0o644 : view.lstat('/remappings.txt').mode })
|
|
125
|
+
}
|