@0xrama/bnpm 0.0.1
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 +83 -0
- package/dist/package.json +45 -0
- package/dist/src/cache/archive.d.ts +15 -0
- package/dist/src/cache/archive.js +163 -0
- package/dist/src/cache/archive.js.map +1 -0
- package/dist/src/cache/commands.d.ts +15 -0
- package/dist/src/cache/commands.js +168 -0
- package/dist/src/cache/commands.js.map +1 -0
- package/dist/src/cache/quarantine.d.ts +18 -0
- package/dist/src/cache/quarantine.js +104 -0
- package/dist/src/cache/quarantine.js.map +1 -0
- package/dist/src/cache/store.d.ts +10 -0
- package/dist/src/cache/store.js +155 -0
- package/dist/src/cache/store.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +4 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/diff.d.ts +34 -0
- package/dist/src/commands/diff.js +223 -0
- package/dist/src/commands/diff.js.map +1 -0
- package/dist/src/commands/doctor.d.ts +14 -0
- package/dist/src/commands/doctor.js +42 -0
- package/dist/src/commands/doctor.js.map +1 -0
- package/dist/src/commands/edit.d.ts +12 -0
- package/dist/src/commands/edit.js +100 -0
- package/dist/src/commands/edit.js.map +1 -0
- package/dist/src/commands/fund.d.ts +10 -0
- package/dist/src/commands/fund.js +32 -0
- package/dist/src/commands/fund.js.map +1 -0
- package/dist/src/commands/graph.d.ts +29 -0
- package/dist/src/commands/graph.js +90 -0
- package/dist/src/commands/graph.js.map +1 -0
- package/dist/src/commands/index.d.ts +20 -0
- package/dist/src/commands/index.js +1753 -0
- package/dist/src/commands/index.js.map +1 -0
- package/dist/src/commands/navigation.d.ts +2 -0
- package/dist/src/commands/navigation.js +47 -0
- package/dist/src/commands/navigation.js.map +1 -0
- package/dist/src/commands/outdated.d.ts +17 -0
- package/dist/src/commands/outdated.js +69 -0
- package/dist/src/commands/outdated.js.map +1 -0
- package/dist/src/commands/process.d.ts +11 -0
- package/dist/src/commands/process.js +189 -0
- package/dist/src/commands/process.js.map +1 -0
- package/dist/src/commands/prompts.d.ts +6 -0
- package/dist/src/commands/prompts.js +86 -0
- package/dist/src/commands/prompts.js.map +1 -0
- package/dist/src/commands/query.d.ts +48 -0
- package/dist/src/commands/query.js +639 -0
- package/dist/src/commands/query.js.map +1 -0
- package/dist/src/commands/rebuild.d.ts +16 -0
- package/dist/src/commands/rebuild.js +44 -0
- package/dist/src/commands/rebuild.js.map +1 -0
- package/dist/src/commands/sbom.d.ts +6 -0
- package/dist/src/commands/sbom.js +31 -0
- package/dist/src/commands/sbom.js.map +1 -0
- package/dist/src/commands/script-approvals.d.ts +22 -0
- package/dist/src/commands/script-approvals.js +128 -0
- package/dist/src/commands/script-approvals.js.map +1 -0
- package/dist/src/commands/shrinkwrap.d.ts +6 -0
- package/dist/src/commands/shrinkwrap.js +97 -0
- package/dist/src/commands/shrinkwrap.js.map +1 -0
- package/dist/src/commands/stage.d.ts +20 -0
- package/dist/src/commands/stage.js +40 -0
- package/dist/src/commands/stage.js.map +1 -0
- package/dist/src/commands/token.d.ts +2 -0
- package/dist/src/commands/token.js +47 -0
- package/dist/src/commands/token.js.map +1 -0
- package/dist/src/commands/trust.d.ts +5 -0
- package/dist/src/commands/trust.js +41 -0
- package/dist/src/commands/trust.js.map +1 -0
- package/dist/src/config/commands.d.ts +5 -0
- package/dist/src/config/commands.js +91 -0
- package/dist/src/config/commands.js.map +1 -0
- package/dist/src/config/configuration.d.ts +40 -0
- package/dist/src/config/configuration.js +157 -0
- package/dist/src/config/configuration.js.map +1 -0
- package/dist/src/config/interactive.d.ts +16 -0
- package/dist/src/config/interactive.js +19 -0
- package/dist/src/config/interactive.js.map +1 -0
- package/dist/src/config/paths.d.ts +22 -0
- package/dist/src/config/paths.js +46 -0
- package/dist/src/config/paths.js.map +1 -0
- package/dist/src/config/types.d.ts +15 -0
- package/dist/src/config/types.js +6 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/core/cli-parser.d.ts +102 -0
- package/dist/src/core/cli-parser.js +1148 -0
- package/dist/src/core/cli-parser.js.map +1 -0
- package/dist/src/core/cli-runner.d.ts +15 -0
- package/dist/src/core/cli-runner.js +261 -0
- package/dist/src/core/cli-runner.js.map +1 -0
- package/dist/src/core/exit-codes.d.ts +13 -0
- package/dist/src/core/exit-codes.js +13 -0
- package/dist/src/core/exit-codes.js.map +1 -0
- package/dist/src/core/output.d.ts +23 -0
- package/dist/src/core/output.js +123 -0
- package/dist/src/core/output.js.map +1 -0
- package/dist/src/installer/install.d.ts +73 -0
- package/dist/src/installer/install.js +596 -0
- package/dist/src/installer/install.js.map +1 -0
- package/dist/src/installer/mutations.d.ts +5 -0
- package/dist/src/installer/mutations.js +160 -0
- package/dist/src/installer/mutations.js.map +1 -0
- package/dist/src/linker/project-linker.d.ts +9 -0
- package/dist/src/linker/project-linker.js +223 -0
- package/dist/src/linker/project-linker.js.map +1 -0
- package/dist/src/lockfile/index.d.ts +27 -0
- package/dist/src/lockfile/index.js +221 -0
- package/dist/src/lockfile/index.js.map +1 -0
- package/dist/src/package/authoring.d.ts +31 -0
- package/dist/src/package/authoring.js +125 -0
- package/dist/src/package/authoring.js.map +1 -0
- package/dist/src/package/pack.d.ts +22 -0
- package/dist/src/package/pack.js +238 -0
- package/dist/src/package/pack.js.map +1 -0
- package/dist/src/package/provenance.d.ts +28 -0
- package/dist/src/package/provenance.js +162 -0
- package/dist/src/package/provenance.js.map +1 -0
- package/dist/src/package/publish.d.ts +28 -0
- package/dist/src/package/publish.js +189 -0
- package/dist/src/package/publish.js.map +1 -0
- package/dist/src/package/trusted-publishing.d.ts +7 -0
- package/dist/src/package/trusted-publishing.js +84 -0
- package/dist/src/package/trusted-publishing.js.map +1 -0
- package/dist/src/project/dependency-error.d.ts +3 -0
- package/dist/src/project/dependency-error.js +7 -0
- package/dist/src/project/dependency-error.js.map +1 -0
- package/dist/src/project/dev-engines.d.ts +10 -0
- package/dist/src/project/dev-engines.js +70 -0
- package/dist/src/project/dev-engines.js.map +1 -0
- package/dist/src/project/discovery.d.ts +6 -0
- package/dist/src/project/discovery.js +51 -0
- package/dist/src/project/discovery.js.map +1 -0
- package/dist/src/project/global.d.ts +3 -0
- package/dist/src/project/global.js +43 -0
- package/dist/src/project/global.js.map +1 -0
- package/dist/src/project/invalidation.d.ts +3 -0
- package/dist/src/project/invalidation.js +21 -0
- package/dist/src/project/invalidation.js.map +1 -0
- package/dist/src/project/link.d.ts +21 -0
- package/dist/src/project/link.js +152 -0
- package/dist/src/project/link.js.map +1 -0
- package/dist/src/project/manifest.d.ts +27 -0
- package/dist/src/project/manifest.js +378 -0
- package/dist/src/project/manifest.js.map +1 -0
- package/dist/src/project/pkg.d.ts +5 -0
- package/dist/src/project/pkg.js +94 -0
- package/dist/src/project/pkg.js.map +1 -0
- package/dist/src/project/recovery.d.ts +2 -0
- package/dist/src/project/recovery.js +82 -0
- package/dist/src/project/recovery.js.map +1 -0
- package/dist/src/project/workspaces.d.ts +4 -0
- package/dist/src/project/workspaces.js +59 -0
- package/dist/src/project/workspaces.js.map +1 -0
- package/dist/src/registry/account.d.ts +79 -0
- package/dist/src/registry/account.js +305 -0
- package/dist/src/registry/account.js.map +1 -0
- package/dist/src/registry/audit.d.ts +18 -0
- package/dist/src/registry/audit.js +74 -0
- package/dist/src/registry/audit.js.map +1 -0
- package/dist/src/registry/client.d.ts +19 -0
- package/dist/src/registry/client.js +102 -0
- package/dist/src/registry/client.js.map +1 -0
- package/dist/src/registry/configuration.d.ts +36 -0
- package/dist/src/registry/configuration.js +133 -0
- package/dist/src/registry/configuration.js.map +1 -0
- package/dist/src/registry/operations.d.ts +163 -0
- package/dist/src/registry/operations.js +502 -0
- package/dist/src/registry/operations.js.map +1 -0
- package/dist/src/registry/types.d.ts +30 -0
- package/dist/src/registry/types.js +2 -0
- package/dist/src/registry/types.js.map +1 -0
- package/dist/src/resolver/registry-resolver.d.ts +40 -0
- package/dist/src/resolver/registry-resolver.js +354 -0
- package/dist/src/resolver/registry-resolver.js.map +1 -0
- package/dist/src/resolver/source-provider.d.ts +17 -0
- package/dist/src/resolver/source-provider.js +336 -0
- package/dist/src/resolver/source-provider.js.map +1 -0
- package/dist/src/resolver/types.d.ts +28 -0
- package/dist/src/resolver/types.js +2 -0
- package/dist/src/resolver/types.js.map +1 -0
- package/dist/src/security/analyzer.d.ts +38 -0
- package/dist/src/security/analyzer.js +241 -0
- package/dist/src/security/analyzer.js.map +1 -0
- package/dist/src/security/audit.d.ts +19 -0
- package/dist/src/security/audit.js +61 -0
- package/dist/src/security/audit.js.map +1 -0
- package/dist/src/security/findings.d.ts +23 -0
- package/dist/src/security/findings.js +2 -0
- package/dist/src/security/findings.js.map +1 -0
- package/dist/src/security/integrity.d.ts +14 -0
- package/dist/src/security/integrity.js +40 -0
- package/dist/src/security/integrity.js.map +1 -0
- package/dist/src/security/policy.d.ts +16 -0
- package/dist/src/security/policy.js +24 -0
- package/dist/src/security/policy.js.map +1 -0
- package/dist/src/security/recent-release.d.ts +19 -0
- package/dist/src/security/recent-release.js +27 -0
- package/dist/src/security/recent-release.js.map +1 -0
- package/dist/src/security/script-runner.d.ts +13 -0
- package/dist/src/security/script-runner.js +73 -0
- package/dist/src/security/script-runner.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import npa from "npm-package-arg";
|
|
2
|
+
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
4
|
+
import { downloadToQuarantine } from "../cache/quarantine.js";
|
|
5
|
+
import { extractPackageArchive } from "../cache/archive.js";
|
|
6
|
+
import { hashLocalPackage, promoteToStore, storePath, verifyStoreEntry } from "../cache/store.js";
|
|
7
|
+
import { createBnpmPaths } from "../config/paths.js";
|
|
8
|
+
import { activateWorkspaceImporterViews, buildIsolatedLayout } from "../linker/project-linker.js";
|
|
9
|
+
import { activateWithRecovery, recoverProjectLayout } from "../project/recovery.js";
|
|
10
|
+
import { createLockfile, readLockfileGraph, writeLockfileAtomic, LockfileError } from "../lockfile/index.js";
|
|
11
|
+
import { parseManifest } from "../project/manifest.js";
|
|
12
|
+
import { discoverProject } from "../project/discovery.js";
|
|
13
|
+
import { discoverWorkspacePackages } from "../project/workspaces.js";
|
|
14
|
+
import { loadRegistryConfiguration, RegistryConfiguration, RoutedRegistryClient } from "../registry/configuration.js";
|
|
15
|
+
import { RegistryResolver } from "../resolver/registry-resolver.js";
|
|
16
|
+
import { RemoteSourceProvider } from "../resolver/source-provider.js";
|
|
17
|
+
import { ResolutionError } from "../resolver/registry-resolver.js";
|
|
18
|
+
import { evaluateRecentRelease } from "../security/recent-release.js";
|
|
19
|
+
import { loadConfigFile, composeConfig } from "../config/configuration.js";
|
|
20
|
+
import { analyzePackage, gitBuildStages, lifecycleStages } from "../security/analyzer.js";
|
|
21
|
+
import { decidePackagePolicy } from "../security/policy.js";
|
|
22
|
+
import { linkedPackagePath } from "../linker/project-linker.js";
|
|
23
|
+
import { runLifecycle } from "../security/script-runner.js";
|
|
24
|
+
import { selectRecencyConfiguration } from "../config/configuration.js";
|
|
25
|
+
import { ensureCacheOwnership } from "../cache/commands.js";
|
|
26
|
+
import { clearInstalledLayoutInvalidation, installedLayoutIsInvalidated } from "../project/invalidation.js";
|
|
27
|
+
async function forEachConcurrent(values, concurrency, worker) {
|
|
28
|
+
let index = 0;
|
|
29
|
+
const next = async () => {
|
|
30
|
+
while (true) {
|
|
31
|
+
const current = index;
|
|
32
|
+
index += 1;
|
|
33
|
+
const value = values[current];
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return;
|
|
36
|
+
await worker(value);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => next()));
|
|
40
|
+
}
|
|
41
|
+
function requirementKeys(requirements) {
|
|
42
|
+
return requirements.map(({ name, specifier, importer }) => `${importer ?? "."}:${name}:${specifier}`).sort();
|
|
43
|
+
}
|
|
44
|
+
async function storedGraphIsUsable(graph, storeRoot) {
|
|
45
|
+
let valid = true;
|
|
46
|
+
await forEachConcurrent([...graph.packages.values()], 16, async (pkg) => {
|
|
47
|
+
if (!await verifyStoreEntry(storePath(storeRoot, pkg.integrity), pkg.integrity, { full: false }))
|
|
48
|
+
valid = false;
|
|
49
|
+
});
|
|
50
|
+
return valid;
|
|
51
|
+
}
|
|
52
|
+
async function installedGraphMatches(projectRoot, graph) {
|
|
53
|
+
const importers = graph.importers ?? new Map([[".", graph.roots]]);
|
|
54
|
+
try {
|
|
55
|
+
if (await installedLayoutIsInvalidated(projectRoot))
|
|
56
|
+
return false;
|
|
57
|
+
for (const [importer, roots] of importers) {
|
|
58
|
+
const nodeModules = importer === "." ? join(projectRoot, "node_modules") : join(projectRoot, ...importer.split("/"), "node_modules");
|
|
59
|
+
for (const [alias, id] of roots) {
|
|
60
|
+
const pkg = graph.packages.get(id);
|
|
61
|
+
if (!pkg)
|
|
62
|
+
return false;
|
|
63
|
+
const manifest = JSON.parse(await readFile(join(nodeModules, ...alias.split("/"), "package.json"), "utf8"));
|
|
64
|
+
if (manifest.name !== pkg.name || manifest.version !== pkg.version)
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function kindForSection(section) {
|
|
75
|
+
return section === "devDependencies" ? "dev" : section === "optionalDependencies" ? "optional" : section === "peerDependencies" ? "peer" : "dependency";
|
|
76
|
+
}
|
|
77
|
+
export function requirementsForManifest(manifest, omitDev) {
|
|
78
|
+
const requirements = new Map();
|
|
79
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
|
|
80
|
+
if (omitDev && section === "devDependencies")
|
|
81
|
+
continue;
|
|
82
|
+
for (const [name, specifier] of Object.entries(manifest.dependencies[section] ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
83
|
+
requirements.set(name, { name, specifier, kind: kindForSection(section) });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [...requirements.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
87
|
+
}
|
|
88
|
+
async function manifestRequirements(cwd, omitDev) {
|
|
89
|
+
const path = join(cwd, "package.json");
|
|
90
|
+
return requirementsForManifest(parseManifest(await readFile(path, "utf8"), path), omitDev);
|
|
91
|
+
}
|
|
92
|
+
function omittedDependencyTypes(options) {
|
|
93
|
+
const omitted = new Set(options?.omit ?? []);
|
|
94
|
+
if (options?.omitDev)
|
|
95
|
+
omitted.add("dev");
|
|
96
|
+
return omitted;
|
|
97
|
+
}
|
|
98
|
+
function installationGraph(graph, requirements, omitted) {
|
|
99
|
+
if (omitted.size === 0)
|
|
100
|
+
return graph;
|
|
101
|
+
const requirementKinds = new Map(requirements.map((requirement) => [`${requirement.importer ?? "."}\0${requirement.name}`, requirement.kind]));
|
|
102
|
+
const excludedRoot = (importer, name) => {
|
|
103
|
+
const kind = requirementKinds.get(`${importer}\0${name}`);
|
|
104
|
+
return kind === "dev" ? omitted.has("dev") : kind === "optional" ? omitted.has("optional") : kind === "peer" ? omitted.has("peer") : false;
|
|
105
|
+
};
|
|
106
|
+
const importerSource = graph.importers ?? new Map([[".", graph.roots]]);
|
|
107
|
+
const importers = new Map([...importerSource].map(([importer, roots]) => [importer, new Map([...roots].filter(([name]) => !excludedRoot(importer, name)))]));
|
|
108
|
+
const roots = new Map(importers.get(".") ?? []);
|
|
109
|
+
const reachable = new Set();
|
|
110
|
+
const pending = [...importers.values()].flatMap((values) => [...values.values()]);
|
|
111
|
+
const includedEdges = (pkg) => new Map([...pkg.dependencies].filter(([alias]) => {
|
|
112
|
+
if (omitted.has("optional") && pkg.manifest.optionalDependencies?.[alias] !== undefined)
|
|
113
|
+
return false;
|
|
114
|
+
if (omitted.has("peer") && pkg.manifest.peerDependencies?.[alias] !== undefined && pkg.manifest.dependencies?.[alias] === undefined && pkg.manifest.optionalDependencies?.[alias] === undefined)
|
|
115
|
+
return false;
|
|
116
|
+
return true;
|
|
117
|
+
}));
|
|
118
|
+
while (pending.length > 0) {
|
|
119
|
+
const id = pending.pop();
|
|
120
|
+
if (!id || reachable.has(id))
|
|
121
|
+
continue;
|
|
122
|
+
reachable.add(id);
|
|
123
|
+
const pkg = graph.packages.get(id);
|
|
124
|
+
if (pkg)
|
|
125
|
+
pending.push(...includedEdges(pkg).values());
|
|
126
|
+
}
|
|
127
|
+
const packages = new Map([...graph.packages].filter(([id]) => reachable.has(id)).map(([id, pkg]) => [id, { ...pkg, dependencies: includedEdges(pkg) }]));
|
|
128
|
+
return { roots, packages, importers };
|
|
129
|
+
}
|
|
130
|
+
async function specificationRequirements(specifications, sourceProvider, cwd, signal) {
|
|
131
|
+
const requirements = [];
|
|
132
|
+
for (const specification of specifications) {
|
|
133
|
+
const parsed = npa(specification);
|
|
134
|
+
let name = parsed.name ?? undefined;
|
|
135
|
+
if (!name && (parsed.type === "remote" || parsed.type === "git" || parsed.type === "file")) {
|
|
136
|
+
const sourced = await sourceProvider.resolve("bnpm-source", parsed.rawSpec, cwd, signal);
|
|
137
|
+
name = sourced?.actualName;
|
|
138
|
+
}
|
|
139
|
+
if (!name && parsed.type === "directory") {
|
|
140
|
+
const resolved = npa.resolve("bnpm-source", parsed.rawSpec, cwd);
|
|
141
|
+
const directory = typeof resolved.fetchSpec === "string" ? resolved.fetchSpec : "";
|
|
142
|
+
const manifestPath = join(directory, "package.json");
|
|
143
|
+
name = parseManifest(await readFile(manifestPath, "utf8"), manifestPath).name;
|
|
144
|
+
}
|
|
145
|
+
if (!name)
|
|
146
|
+
throw new Error(`Package specification does not have an inferable package name: ${specification}`);
|
|
147
|
+
requirements.push({ name, specifier: parsed.rawSpec || "latest", kind: "dependency" });
|
|
148
|
+
}
|
|
149
|
+
return requirements;
|
|
150
|
+
}
|
|
151
|
+
function policyError(message) {
|
|
152
|
+
const error = new Error(message);
|
|
153
|
+
error.name = "PolicyError";
|
|
154
|
+
return error;
|
|
155
|
+
}
|
|
156
|
+
function sourceProjectPaths(base, cwd) {
|
|
157
|
+
const disabledPolicy = join(base.quarantine, ".source-project-policy-disabled");
|
|
158
|
+
return {
|
|
159
|
+
...base,
|
|
160
|
+
projectConfig: disabledPolicy,
|
|
161
|
+
projectNpmrc: disabledPolicy,
|
|
162
|
+
lockfile: join(cwd, "bnpm-lock.yaml"),
|
|
163
|
+
virtualStore: join(cwd, "node_modules", ".bnpm"),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export async function createGitPreparer(options) {
|
|
167
|
+
const globalConfig = await loadConfigFile(options.paths.globalConfig);
|
|
168
|
+
const projectConfig = await loadConfigFile(options.paths.projectConfig);
|
|
169
|
+
const effectiveConfig = composeConfig({
|
|
170
|
+
...(globalConfig === undefined ? {} : { global: globalConfig }),
|
|
171
|
+
...(projectConfig === undefined ? {} : { project: projectConfig }),
|
|
172
|
+
overrides: { allowRecent: options.commandOptions?.allowRecent ?? [] },
|
|
173
|
+
});
|
|
174
|
+
return async (packageRoot, rawManifest, signal) => {
|
|
175
|
+
const depth = options.sourceBuildDepth ?? 0;
|
|
176
|
+
if (depth >= 4)
|
|
177
|
+
throw new ResolutionError("git prepare dependency nesting exceeds four levels");
|
|
178
|
+
const name = rawManifest.name;
|
|
179
|
+
const version = rawManifest.version;
|
|
180
|
+
if (typeof name !== "string" || typeof version !== "string")
|
|
181
|
+
throw new ResolutionError("git build package requires a name and version");
|
|
182
|
+
const scriptsValue = rawManifest.scripts;
|
|
183
|
+
if (scriptsValue !== undefined && (typeof scriptsValue !== "object" || scriptsValue === null || Array.isArray(scriptsValue)))
|
|
184
|
+
throw new ResolutionError("git package scripts must be an object");
|
|
185
|
+
const scripts = (scriptsValue ?? {});
|
|
186
|
+
const stages = gitBuildStages.filter((stage) => typeof scripts[stage] === "string");
|
|
187
|
+
const bundles = rawManifest.bundleDependencies ?? rawManifest.bundledDependencies;
|
|
188
|
+
const needsInstall = stages.length > 0 || rawManifest.workspaces !== undefined || bundles === true || (Array.isArray(bundles) && bundles.length > 0);
|
|
189
|
+
if (needsInstall) {
|
|
190
|
+
const nested = await installProject({
|
|
191
|
+
cwd: packageRoot,
|
|
192
|
+
paths: sourceProjectPaths(options.paths, packageRoot),
|
|
193
|
+
registryConfiguration: options.registryConfiguration,
|
|
194
|
+
fetch: options.fetch,
|
|
195
|
+
commandOptions: {
|
|
196
|
+
json: options.commandOptions?.json ?? false,
|
|
197
|
+
allowRecent: options.commandOptions?.allowRecent ?? [],
|
|
198
|
+
allowDangerous: options.commandOptions?.allowDangerous ?? [],
|
|
199
|
+
frozenLockfile: false,
|
|
200
|
+
offline: false,
|
|
201
|
+
omitDev: false,
|
|
202
|
+
saveExact: false,
|
|
203
|
+
noSave: true,
|
|
204
|
+
},
|
|
205
|
+
sourceBuildDepth: depth + 1,
|
|
206
|
+
...(signal === undefined ? {} : { signal }),
|
|
207
|
+
...(options.prompts === undefined ? {} : { prompts: options.prompts }),
|
|
208
|
+
...(options.onChildOutput === undefined ? {} : { onChildOutput: options.onChildOutput }),
|
|
209
|
+
...(options.onSecurityEvidence === undefined ? {} : { onSecurityEvidence: options.onSecurityEvidence }),
|
|
210
|
+
});
|
|
211
|
+
if (nested.skippedLifecyclePackages.length > 0)
|
|
212
|
+
throw policyError(`Git build dependencies have unapproved lifecycle scripts: ${nested.skippedLifecyclePackages.join(", ")}`);
|
|
213
|
+
}
|
|
214
|
+
if (stages.length === 0)
|
|
215
|
+
return;
|
|
216
|
+
const integrity = await hashLocalPackage(packageRoot);
|
|
217
|
+
const declaredScripts = Object.fromEntries(stages.map((stage) => [stage, scripts[stage]]));
|
|
218
|
+
const analyzed = await analyzePackage({ root: packageRoot, packageName: name, packageVersion: version, integrity, scripts: declaredScripts, stages });
|
|
219
|
+
options.onSecurityEvidence?.(`Security inspection for Git build ${name}@${version}`, { findings: analyzed.analysis.findings, lifecycles: analyzed.lifecycles });
|
|
220
|
+
const allowedDangerous = new Set(options.commandOptions?.allowDangerous ?? []);
|
|
221
|
+
const identity = `${name}@${version}`;
|
|
222
|
+
if (analyzed.analysis.findings.some((finding) => finding.severity === "dangerous") && !allowedDangerous.has(identity) && options.prompts?.allowDangerous && await options.prompts.allowDangerous(analyzed)) {
|
|
223
|
+
allowedDangerous.add(identity);
|
|
224
|
+
}
|
|
225
|
+
const trusted = effectiveConfig.trustedPackages.value[name];
|
|
226
|
+
const decision = decidePackagePolicy({ analyzed, allowedDangerous, ...(trusted === undefined ? {} : { trustedApproval: trusted }) });
|
|
227
|
+
if (decision.blocked)
|
|
228
|
+
throw policyError(`Dangerous Git build behavior blocked: ${identity}`);
|
|
229
|
+
const approved = [...decision.approvedLifecycles];
|
|
230
|
+
for (const fact of decision.skippedLifecycles) {
|
|
231
|
+
if (options.prompts?.approveLifecycle && await options.prompts.approveLifecycle(fact, analyzed))
|
|
232
|
+
approved.push(fact);
|
|
233
|
+
else
|
|
234
|
+
throw policyError(`Git build lifecycle requires approval: ${identity} ${fact.stage}`);
|
|
235
|
+
}
|
|
236
|
+
for (const fact of approved.sort((left, right) => gitBuildStages.indexOf(left.stage) - gitBuildStages.indexOf(right.stage))) {
|
|
237
|
+
await runLifecycle({
|
|
238
|
+
fact,
|
|
239
|
+
cwd: packageRoot,
|
|
240
|
+
...(signal === undefined ? {} : { signal }),
|
|
241
|
+
onOutput: (stream, text) => options.onChildOutput?.(stream, text, { package: identity, stage: fact.stage }),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export async function installProject(options) {
|
|
247
|
+
const discovered = await discoverProject(options.cwd);
|
|
248
|
+
const projectRoot = discovered?.projectRoot ?? options.cwd;
|
|
249
|
+
const importerRoot = discovered?.importerRoot ?? options.cwd;
|
|
250
|
+
await recoverProjectLayout(projectRoot);
|
|
251
|
+
const paths = options.paths ?? createBnpmPaths({ cwd: projectRoot });
|
|
252
|
+
await ensureCacheOwnership(paths);
|
|
253
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
254
|
+
const registryConfiguration = options.registryConfiguration ?? await loadRegistryConfiguration({
|
|
255
|
+
userNpmrc: paths.userNpmrc,
|
|
256
|
+
projectNpmrc: paths.projectNpmrc,
|
|
257
|
+
...(options.registry === undefined ? {} : { defaultRegistry: options.registry }),
|
|
258
|
+
});
|
|
259
|
+
const registry = registryConfiguration.defaultRegistry;
|
|
260
|
+
const registryProvider = new RoutedRegistryClient(registryConfiguration, fetchImpl);
|
|
261
|
+
const prepareGit = await createGitPreparer({
|
|
262
|
+
paths,
|
|
263
|
+
registryConfiguration,
|
|
264
|
+
fetch: fetchImpl,
|
|
265
|
+
...(options.commandOptions === undefined ? {} : { commandOptions: options.commandOptions }),
|
|
266
|
+
...(options.prompts === undefined ? {} : { prompts: options.prompts }),
|
|
267
|
+
...(options.sourceBuildDepth === undefined ? {} : { sourceBuildDepth: options.sourceBuildDepth }),
|
|
268
|
+
...(options.onChildOutput === undefined ? {} : { onChildOutput: options.onChildOutput }),
|
|
269
|
+
...(options.onSecurityEvidence === undefined ? {} : { onSecurityEvidence: options.onSecurityEvidence }),
|
|
270
|
+
});
|
|
271
|
+
const sourceProvider = options.sourceProvider ?? new RemoteSourceProvider({ quarantineRoot: paths.quarantine, registryConfiguration, fetch: fetchImpl, prepareGit });
|
|
272
|
+
try {
|
|
273
|
+
const globalConfig = await loadConfigFile(paths.globalConfig);
|
|
274
|
+
const projectConfig = await loadConfigFile(paths.projectConfig);
|
|
275
|
+
const effectiveConfig = composeConfig({
|
|
276
|
+
...(globalConfig === undefined ? {} : { global: globalConfig }),
|
|
277
|
+
...(projectConfig === undefined ? {} : { project: projectConfig }),
|
|
278
|
+
overrides: { allowRecent: options.commandOptions?.allowRecent ?? [] },
|
|
279
|
+
});
|
|
280
|
+
const firstUse = await selectRecencyConfiguration({
|
|
281
|
+
commandIsSecuritySensitive: true,
|
|
282
|
+
existingConfiguration: globalConfig !== undefined || projectConfig !== undefined,
|
|
283
|
+
mode: options.prompts?.mode ?? { interactive: false, reason: "prompt-unavailable" },
|
|
284
|
+
configPath: paths.globalConfig,
|
|
285
|
+
...(options.prompts?.selectRecencyHours === undefined ? {} : { prompt: options.prompts.selectRecencyHours }),
|
|
286
|
+
});
|
|
287
|
+
const configuredRecentReleaseHours = options.recentReleaseHours ?? (globalConfig === undefined && projectConfig === undefined ? firstUse.hours : effectiveConfig.recentReleaseHours.value);
|
|
288
|
+
const configuredAllowedRecent = new Set(effectiveConfig.allowRecent.value);
|
|
289
|
+
const resolverRecency = { minimumReleaseAgeMilliseconds: configuredRecentReleaseHours * 60 * 60 * 1000, ...(options.now === undefined ? {} : { now: options.now }), allowedRecentVersions: configuredAllowedRecent };
|
|
290
|
+
const activeRequirements = options.requirements ?? (options.specifications && options.specifications.length > 0
|
|
291
|
+
? await specificationRequirements(options.specifications, sourceProvider, importerRoot, options.signal)
|
|
292
|
+
: await manifestRequirements(importerRoot, false));
|
|
293
|
+
options.onProgress?.({ phase: "resolving", total: activeRequirements.length });
|
|
294
|
+
let lockExists = false;
|
|
295
|
+
try {
|
|
296
|
+
await stat(paths.lockfile);
|
|
297
|
+
lockExists = true;
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
if (error.code !== "ENOENT")
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
if (!lockExists && (options.commandOptions?.offline || options.commandOptions?.frozenLockfile))
|
|
304
|
+
throw new LockfileError("offline or frozen install requires bnpm-lock.yaml");
|
|
305
|
+
let existingLock;
|
|
306
|
+
if (lockExists) {
|
|
307
|
+
try {
|
|
308
|
+
existingLock = await readLockfileGraph(paths.lockfile, paths.store);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (options.commandOptions?.offline || options.commandOptions?.frozenLockfile)
|
|
312
|
+
throw error;
|
|
313
|
+
existingLock = undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
let locked = options.commandOptions?.offline ? existingLock : undefined;
|
|
317
|
+
let workspaces;
|
|
318
|
+
let overrides;
|
|
319
|
+
try {
|
|
320
|
+
const importerManifestPath = join(importerRoot, "package.json");
|
|
321
|
+
overrides = parseManifest(await readFile(importerManifestPath, "utf8"), importerManifestPath).overrides;
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (error.code !== "ENOENT")
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
if (discovered?.kind === "workspace") {
|
|
328
|
+
const rootManifestPath = join(projectRoot, "package.json");
|
|
329
|
+
const rootManifest = parseManifest(await readFile(rootManifestPath, "utf8"), rootManifestPath);
|
|
330
|
+
workspaces = await discoverWorkspacePackages(projectRoot, rootManifest.workspaces);
|
|
331
|
+
}
|
|
332
|
+
else if (discovered && discovered.projectRoot === discovered.importerRoot) {
|
|
333
|
+
const rootManifestPath = join(projectRoot, "package.json");
|
|
334
|
+
const rootManifest = parseManifest(await readFile(rootManifestPath, "utf8"), rootManifestPath);
|
|
335
|
+
if (rootManifest.workspaces.length > 0)
|
|
336
|
+
workspaces = await discoverWorkspacePackages(projectRoot, rootManifest.workspaces);
|
|
337
|
+
}
|
|
338
|
+
let requirements = activeRequirements;
|
|
339
|
+
let importerPaths;
|
|
340
|
+
if (workspaces && discovered) {
|
|
341
|
+
const activeImporter = relative(projectRoot, importerRoot).split(sep).join("/") || ".";
|
|
342
|
+
const pathsByImporter = new Map([[".", projectRoot]]);
|
|
343
|
+
for (const path of workspaces.values())
|
|
344
|
+
pathsByImporter.set(relative(projectRoot, path).split(sep).join("/"), path);
|
|
345
|
+
importerPaths = pathsByImporter;
|
|
346
|
+
const allRequirements = [];
|
|
347
|
+
for (const [importer, path] of [...pathsByImporter].sort(([left], [right]) => left.localeCompare(right))) {
|
|
348
|
+
const importerRequirements = importer === activeImporter
|
|
349
|
+
? activeRequirements
|
|
350
|
+
: await manifestRequirements(path, false);
|
|
351
|
+
allRequirements.push(...importerRequirements.map((requirement) => ({ ...requirement, ...(importer === "." ? {} : { importer }) })));
|
|
352
|
+
}
|
|
353
|
+
requirements = allRequirements;
|
|
354
|
+
}
|
|
355
|
+
const lockMatches = existingLock !== undefined && JSON.stringify(requirementKeys(requirements)) === JSON.stringify(requirementKeys(existingLock.requirements));
|
|
356
|
+
if (options.commandOptions?.offline && !lockMatches)
|
|
357
|
+
throw new LockfileError("manifest requirements do not match the offline lockfile");
|
|
358
|
+
const omitted = omittedDependencyTypes(options.commandOptions);
|
|
359
|
+
if (!options.forceResolution && lockMatches && existingLock && await storedGraphIsUsable(installationGraph(existingLock.graph, existingLock.requirements, omitted), paths.store))
|
|
360
|
+
locked = existingLock;
|
|
361
|
+
else if (options.commandOptions?.offline)
|
|
362
|
+
throw new LockfileError("offline install requires complete, valid store entries");
|
|
363
|
+
let resolvedGraph;
|
|
364
|
+
if (!locked && importerPaths && workspaces) {
|
|
365
|
+
const importers = new Map();
|
|
366
|
+
const packages = new Map();
|
|
367
|
+
const provider = registryProvider;
|
|
368
|
+
for (const [importer, path] of [...importerPaths].sort(([left], [right]) => left.localeCompare(right))) {
|
|
369
|
+
const importerRequirements = requirements.filter((requirement) => (requirement.importer ?? ".") === importer).map(({ importer: _importer, ...requirement }) => requirement);
|
|
370
|
+
const importerManifestPath = join(path, "package.json");
|
|
371
|
+
const importerOverrides = parseManifest(await readFile(importerManifestPath, "utf8"), importerManifestPath).overrides;
|
|
372
|
+
const part = await new RegistryResolver(provider, { baseDirectory: path, workspaces, overrides: { ...importerOverrides, ...options.resolutionOverrides }, sourceProvider, ...resolverRecency }).resolve(importerRequirements, options.signal);
|
|
373
|
+
importers.set(importer, part.roots);
|
|
374
|
+
for (const [id, pkg] of part.packages) {
|
|
375
|
+
const existing = packages.get(id);
|
|
376
|
+
if (existing && JSON.stringify([...existing.dependencies]) !== JSON.stringify([...pkg.dependencies]))
|
|
377
|
+
throw new Error(`Conflicting workspace resolution for ${id}`);
|
|
378
|
+
packages.set(id, pkg);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
resolvedGraph = { roots: new Map(importers.get(".") ?? []), packages: new Map([...packages].sort(([left], [right]) => left.localeCompare(right))), importers };
|
|
382
|
+
}
|
|
383
|
+
const graph = locked?.graph ?? resolvedGraph ?? await new RegistryResolver(registryProvider, { baseDirectory: importerRoot, ...(workspaces === undefined ? {} : { workspaces }), overrides: { ...overrides, ...options.resolutionOverrides }, sourceProvider, ...resolverRecency }).resolve(requirements, options.signal);
|
|
384
|
+
const installGraph = installationGraph(graph, requirements, omitted);
|
|
385
|
+
options.onProgress?.({ phase: "resolved", total: installGraph.packages.size });
|
|
386
|
+
const recentReleaseHours = options.recentReleaseHours ?? locked?.recentReleaseHours ?? configuredRecentReleaseHours;
|
|
387
|
+
const allowedRecent = new Set(effectiveConfig.allowRecent.value);
|
|
388
|
+
const recentReleaseDecisions = [...graph.packages.values()].filter((pkg) => pkg.source === "registry" || (pkg.source === undefined && pkg.localPath === undefined)).map((pkg) => evaluateRecentRelease({ name: pkg.name, version: pkg.version, ...(pkg.publishedAt === undefined ? {} : { publishedAt: pkg.publishedAt.toISOString() }) }, { thresholdHours: recentReleaseHours, ...(options.now === undefined ? {} : { now: options.now }), allowedExactVersions: allowedRecent }));
|
|
389
|
+
for (const decision of recentReleaseDecisions) {
|
|
390
|
+
if (decision.status !== "allowed" && options.prompts?.allowRecent && await options.prompts.allowRecent(decision))
|
|
391
|
+
allowedRecent.add(decision.identity);
|
|
392
|
+
}
|
|
393
|
+
const finalRecentDecisions = [...graph.packages.values()].filter((pkg) => pkg.source === "registry" || (pkg.source === undefined && pkg.localPath === undefined)).map((pkg) => evaluateRecentRelease({ name: pkg.name, version: pkg.version, ...(pkg.publishedAt === undefined ? {} : { publishedAt: pkg.publishedAt.toISOString() }) }, { thresholdHours: recentReleaseHours, ...(options.now === undefined ? {} : { now: options.now }), allowedExactVersions: allowedRecent }));
|
|
394
|
+
const blocked = finalRecentDecisions.filter((decision) => decision.status !== "allowed");
|
|
395
|
+
if (blocked.length > 0) {
|
|
396
|
+
const error = new Error(`Recent-release policy blocked ${blocked.map((decision) => decision.identity).join(", ")}`);
|
|
397
|
+
error.name = "PolicyError";
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
if (options.commandOptions?.packageLockOnly || options.commandOptions?.dryRun) {
|
|
401
|
+
const lockfile = createLockfile(graph, requirements, { registry: registry.href, recentReleaseHours });
|
|
402
|
+
if (options.commandOptions.packageLockOnly && !options.commandOptions.dryRun && !options.commandOptions.offline && !options.commandOptions.frozenLockfile)
|
|
403
|
+
await writeLockfileAtomic(paths.lockfile, lockfile);
|
|
404
|
+
return { graph, lockfile, recentReleaseDecisions: finalRecentDecisions, skippedLifecyclePackages: [], analyses: new Map(), policyDecisions: [] };
|
|
405
|
+
}
|
|
406
|
+
if (!options.forceRelink && locked && lockMatches && locked.dangerousPackageIds.size === 0 && await installedGraphMatches(projectRoot, installGraph)) {
|
|
407
|
+
const skippedLifecyclePackages = options.commandOptions?.ignoreScripts ? [] : [...installGraph.packages.values()].filter((pkg) => {
|
|
408
|
+
const approval = locked?.approvals[pkg.name];
|
|
409
|
+
return lifecycleStages.some((stage) => pkg.manifest.scripts?.[stage] && approval?.scripts[stage] === undefined);
|
|
410
|
+
}).map((pkg) => pkg.id);
|
|
411
|
+
if (skippedLifecyclePackages.length === 0) {
|
|
412
|
+
return {
|
|
413
|
+
graph,
|
|
414
|
+
lockfile: await readFile(paths.lockfile, "utf8"),
|
|
415
|
+
recentReleaseDecisions: finalRecentDecisions,
|
|
416
|
+
skippedLifecyclePackages,
|
|
417
|
+
analyses: new Map(),
|
|
418
|
+
policyDecisions: [],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const storeEntries = new Map();
|
|
423
|
+
const analyses = new Map();
|
|
424
|
+
let completedPackages = 0;
|
|
425
|
+
let cachedPackages = 0;
|
|
426
|
+
let downloadedPackages = 0;
|
|
427
|
+
const progressTotal = installGraph.packages.size;
|
|
428
|
+
const packageComplete = (cached) => {
|
|
429
|
+
completedPackages += 1;
|
|
430
|
+
if (cached)
|
|
431
|
+
cachedPackages += 1;
|
|
432
|
+
else
|
|
433
|
+
downloadedPackages += 1;
|
|
434
|
+
options.onProgress?.({ phase: "fetching", completed: completedPackages, total: progressTotal, cached: cachedPackages, downloaded: downloadedPackages });
|
|
435
|
+
};
|
|
436
|
+
options.onProgress?.({ phase: "fetching", completed: 0, total: progressTotal, cached: 0, downloaded: 0 });
|
|
437
|
+
const inspect = async (id, pkg, root) => {
|
|
438
|
+
const analyzed = await analyzePackage({
|
|
439
|
+
root,
|
|
440
|
+
packageName: pkg.name,
|
|
441
|
+
packageVersion: pkg.version,
|
|
442
|
+
integrity: pkg.integrity,
|
|
443
|
+
...(pkg.manifest.scripts === undefined ? {} : { scripts: pkg.manifest.scripts }),
|
|
444
|
+
});
|
|
445
|
+
analyses.set(id, analyzed);
|
|
446
|
+
if (analyzed.analysis.findings.length > 0 || analyzed.lifecycles.length > 0) {
|
|
447
|
+
options.onSecurityEvidence?.(`Security inspection for ${id}`, { findings: analyzed.analysis.findings, lifecycles: analyzed.lifecycles });
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
await forEachConcurrent([...installGraph.packages], 16, async ([id, pkg]) => {
|
|
452
|
+
if (locked) {
|
|
453
|
+
const stored = storePath(paths.store, pkg.integrity);
|
|
454
|
+
storeEntries.set(id, stored);
|
|
455
|
+
packageComplete(true);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (pkg.localPath) {
|
|
459
|
+
storeEntries.set(id, await promoteToStore(pkg.localPath, paths.store, pkg.integrity, { localPackage: true }));
|
|
460
|
+
packageComplete(true);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (pkg.preparedPath) {
|
|
464
|
+
storeEntries.set(id, await promoteToStore(pkg.preparedPath, paths.store, pkg.integrity));
|
|
465
|
+
packageComplete(true);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const warm = storePath(paths.store, pkg.integrity);
|
|
469
|
+
try {
|
|
470
|
+
if (!await verifyStoreEntry(warm, pkg.integrity))
|
|
471
|
+
throw Object.assign(new Error("missing or corrupt warm store entry"), { code: "ENOENT" });
|
|
472
|
+
storeEntries.set(id, warm);
|
|
473
|
+
packageComplete(true);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
if (error.code !== "ENOENT")
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
const quarantined = await downloadToQuarantine(pkg.tarball, pkg.integrity, {
|
|
481
|
+
root: paths.quarantine,
|
|
482
|
+
fetch: fetchImpl,
|
|
483
|
+
headers: (url) => registryConfiguration.headersFor(url),
|
|
484
|
+
onProgress: (downloadedBytes, totalBytes) => options.onProgress?.({ phase: "downloading", package: id, bytes: downloadedBytes, ...(totalBytes === undefined ? {} : { totalBytes }), completed: completedPackages, total: progressTotal, cached: cachedPackages, downloaded: downloadedPackages }),
|
|
485
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
486
|
+
});
|
|
487
|
+
const quarantineDirectory = dirname(quarantined.path);
|
|
488
|
+
try {
|
|
489
|
+
const extracted = await extractPackageArchive(quarantined.path, join(quarantineDirectory, "extracted"));
|
|
490
|
+
storeEntries.set(id, await promoteToStore(extracted.path, paths.store, pkg.integrity));
|
|
491
|
+
packageComplete(false);
|
|
492
|
+
}
|
|
493
|
+
finally {
|
|
494
|
+
await rm(quarantineDirectory, { recursive: true, force: true });
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
let inspectedPackages = 0;
|
|
498
|
+
options.onProgress?.({ phase: "inspecting", completed: 0, total: progressTotal, cached: cachedPackages, downloaded: downloadedPackages });
|
|
499
|
+
await forEachConcurrent([...installGraph.packages], 8, async ([id, pkg]) => {
|
|
500
|
+
const stored = storeEntries.get(id);
|
|
501
|
+
if (!stored)
|
|
502
|
+
throw new Error(`Verified store entry is unavailable for ${id}`);
|
|
503
|
+
await inspect(id, pkg, stored);
|
|
504
|
+
inspectedPackages += 1;
|
|
505
|
+
options.onProgress?.({ phase: "inspecting", completed: inspectedPackages, total: progressTotal, cached: cachedPackages, downloaded: downloadedPackages });
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
finally {
|
|
509
|
+
await sourceProvider.cleanup();
|
|
510
|
+
}
|
|
511
|
+
const allowedDangerous = new Set(options.commandOptions?.allowDangerous ?? []);
|
|
512
|
+
for (const analyzed of analyses.values()) {
|
|
513
|
+
const identity = `${analyzed.analysis.packageName}@${analyzed.analysis.packageVersion}`;
|
|
514
|
+
if (analyzed.analysis.findings.some((finding) => finding.severity === "dangerous") && !allowedDangerous.has(identity) && options.prompts?.allowDangerous && await options.prompts.allowDangerous(analyzed)) {
|
|
515
|
+
allowedDangerous.add(identity);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
let policyDecisions = [...analyses].sort(([left], [right]) => left.localeCompare(right)).map(([packageId, analyzed]) => {
|
|
519
|
+
const trustedApproval = effectiveConfig.trustedPackages.value[analyzed.analysis.packageName] ?? existingLock?.approvals[analyzed.analysis.packageName];
|
|
520
|
+
return decidePackagePolicy({ analyzed, allowedDangerous, packageId, ...(trustedApproval === undefined ? {} : { trustedApproval }) });
|
|
521
|
+
});
|
|
522
|
+
const policyBlocked = policyDecisions.filter((decision) => decision.blocked);
|
|
523
|
+
if (policyBlocked.length > 0) {
|
|
524
|
+
const error = new Error(`Dangerous package behavior blocked: ${policyBlocked.map((decision) => decision.identity).join(", ")}`);
|
|
525
|
+
error.name = "PolicyError";
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
if (options.prompts?.approveLifecycle) {
|
|
529
|
+
const updated = [];
|
|
530
|
+
for (const decision of policyDecisions) {
|
|
531
|
+
const analyzed = analyses.get(decision.packageId);
|
|
532
|
+
if (!analyzed) {
|
|
533
|
+
updated.push(decision);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const newlyApproved = [];
|
|
537
|
+
const stillSkipped = [];
|
|
538
|
+
for (const fact of decision.skippedLifecycles) {
|
|
539
|
+
if (await options.prompts.approveLifecycle(fact, analyzed))
|
|
540
|
+
newlyApproved.push(fact);
|
|
541
|
+
else
|
|
542
|
+
stillSkipped.push(fact);
|
|
543
|
+
}
|
|
544
|
+
updated.push({ ...decision, approvedLifecycles: [...decision.approvedLifecycles, ...newlyApproved], skippedLifecycles: stillSkipped });
|
|
545
|
+
}
|
|
546
|
+
policyDecisions = updated;
|
|
547
|
+
}
|
|
548
|
+
const lockfile = createLockfile(graph, requirements, { registry: registry.href, recentReleaseHours }, { analyses, decisions: policyDecisions });
|
|
549
|
+
if (options.commandOptions?.frozenLockfile) {
|
|
550
|
+
let current;
|
|
551
|
+
try {
|
|
552
|
+
current = await readFile(paths.lockfile, "utf8");
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if (error.code === "ENOENT")
|
|
556
|
+
throw new LockfileError("frozen install requires an existing bnpm-lock.yaml");
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
if (current !== lockfile)
|
|
560
|
+
throw new LockfileError("frozen lockfile is not current with the manifest, graph, and security decisions");
|
|
561
|
+
}
|
|
562
|
+
const preparedRoot = await mkdtemp(join(projectRoot, ".bnpm-install-"));
|
|
563
|
+
const preparedNodeModules = join(preparedRoot, "node_modules");
|
|
564
|
+
try {
|
|
565
|
+
options.onProgress?.({ phase: "linking", completed: 0, total: installGraph.packages.size, cached: cachedPackages, downloaded: downloadedPackages });
|
|
566
|
+
await buildIsolatedLayout(preparedNodeModules, installGraph, storeEntries);
|
|
567
|
+
await activateWithRecovery(projectRoot, preparedNodeModules);
|
|
568
|
+
await activateWorkspaceImporterViews(projectRoot, installGraph);
|
|
569
|
+
await clearInstalledLayoutInvalidation(projectRoot);
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
await rm(preparedRoot, { recursive: true, force: true });
|
|
573
|
+
}
|
|
574
|
+
if (!options.commandOptions?.offline && !options.commandOptions?.frozenLockfile)
|
|
575
|
+
await writeLockfileAtomic(paths.lockfile, lockfile);
|
|
576
|
+
const stageIndex = new Map(lifecycleStages.map((stage, index) => [stage, index]));
|
|
577
|
+
const approved = policyDecisions.flatMap((decision) => decision.approvedLifecycles.map((fact) => ({ fact, packageId: decision.packageId }))).sort((left, right) => left.packageId.localeCompare(right.packageId) ||
|
|
578
|
+
(stageIndex.get(left.fact.stage) ?? 0) - (stageIndex.get(right.fact.stage) ?? 0));
|
|
579
|
+
for (const { fact, packageId: id } of options.commandOptions?.ignoreScripts ? [] : approved) {
|
|
580
|
+
await runLifecycle({
|
|
581
|
+
fact,
|
|
582
|
+
cwd: linkedPackagePath(join(projectRoot, "node_modules"), id, fact.packageName),
|
|
583
|
+
initialCwd: importerRoot,
|
|
584
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
585
|
+
onOutput: (stream, text) => options.onChildOutput?.(stream, text, { package: id, stage: fact.stage }),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
const skippedLifecyclePackages = options.commandOptions?.ignoreScripts ? [] : [...new Set(policyDecisions.flatMap((decision) => decision.skippedLifecycles.map((fact) => `${fact.packageName}@${fact.packageVersion}`)))];
|
|
589
|
+
options.onProgress?.({ phase: "complete", completed: installGraph.packages.size, total: installGraph.packages.size, cached: cachedPackages, downloaded: downloadedPackages });
|
|
590
|
+
return { graph: installGraph, lockfile, recentReleaseDecisions: finalRecentDecisions, skippedLifecyclePackages, analyses, policyDecisions };
|
|
591
|
+
}
|
|
592
|
+
finally {
|
|
593
|
+
await sourceProvider.cleanup();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
//# sourceMappingURL=install.js.map
|