@vltpkg/graph 1.3.1 → 1.3.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.
@@ -349,7 +349,10 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
349
349
  // this is the entry point to fetch calls to retrieve manifests
350
350
  // from the build ideal graph point of view
351
351
  : packageInfo
352
- .manifest(spec, { from })
352
+ // an explicit tag add revalidates before use
353
+ .manifest(spec, isExplicit ?
354
+ { from }
355
+ : { from, backgroundRevalidate: true })
353
356
  .then(manifest => manifest)
354
357
  .catch((er) => {
355
358
  // optional deps ignored if inaccessible, but a dep that tried
package/dist/node.d.ts CHANGED
@@ -180,6 +180,7 @@ export declare class Node implements NodeLike {
180
180
  set location(location: string);
181
181
  /**
182
182
  * The resolved location of the node in the file system.
183
+ * Memoized per location and scurry cwd.
183
184
  */
184
185
  resolvedLocation(scurry: PathScurry): string;
185
186
  /**
package/dist/node.js CHANGED
@@ -16,6 +16,7 @@ export class Node {
16
16
  }
17
17
  #options;
18
18
  #location;
19
+ #resolvedLoc;
19
20
  #rawManifest;
20
21
  #optional = false;
21
22
  /**
@@ -243,9 +244,17 @@ export class Node {
243
244
  }
244
245
  /**
245
246
  * The resolved location of the node in the file system.
247
+ * Memoized per location and scurry cwd.
246
248
  */
247
249
  resolvedLocation(scurry) {
248
- return scurry.cwd.resolve(this.location).fullpath();
250
+ const loc = this.location;
251
+ const { cwd } = scurry;
252
+ const m = this.#resolvedLoc;
253
+ if (m?.loc === loc && m.cwd === cwd)
254
+ return m.path;
255
+ const path = cwd.resolve(loc).fullpath();
256
+ this.#resolvedLoc = { loc, cwd, path };
257
+ return path;
249
258
  }
250
259
  /**
251
260
  * The location of the node_modules folder where this node's edgesOut
@@ -257,9 +266,11 @@ export class Node {
257
266
  */
258
267
  nodeModules(scurry) {
259
268
  const loc = this.resolvedLocation(scurry);
260
- return this.inVltStore() ?
261
- loc.substring(0, loc.length - this.name.length - 1)
262
- : scurry.resolve(loc, 'node_modules');
269
+ if (this.inVltStore()) {
270
+ return loc.substring(0, loc.length - this.name.length - 1);
271
+ }
272
+ const { sep } = scurry.cwd;
273
+ return (loc.endsWith(sep) ? loc : loc + sep) + 'node_modules';
263
274
  }
264
275
  constructor(options, id, manifest, spec, name, version) {
265
276
  this.#options = options;
@@ -1,6 +1,7 @@
1
1
  import { cmdShimIfExists } from '@vltpkg/cmd-shim';
2
- import { mkdir, symlink } from 'node:fs/promises';
2
+ import { symlinkSync } from 'node:fs';
3
3
  import { dirname, relative, resolve } from 'node:path';
4
+ import { symlinkSyncMkdirp } from "./symlink-sync.js";
4
5
  const clobberSymlink = async (target, link, remover, type = 'file') => {
5
6
  // On Windows, directory symlinks require elevated privileges.
6
7
  // Use junctions instead, which work without special permissions.
@@ -9,9 +10,8 @@ const clobberSymlink = async (target, link, remover, type = 'file') => {
9
10
  const symlinkTarget = symlinkType === 'junction' ?
10
11
  resolve(dirname(link), target)
11
12
  : target;
12
- await mkdir(dirname(link), { recursive: true });
13
13
  try {
14
- await symlink(symlinkTarget, link, symlinkType);
14
+ symlinkSyncMkdirp(symlinkTarget, link, symlinkType);
15
15
  }
16
16
  catch (er) {
17
17
  /* c8 ignore start */
@@ -23,7 +23,7 @@ const clobberSymlink = async (target, link, remover, type = 'file') => {
23
23
  await remover.rm(link);
24
24
  try {
25
25
  // then try to create it again
26
- await symlink(symlinkTarget, link, symlinkType);
26
+ symlinkSync(symlinkTarget, link, symlinkType);
27
27
  /* c8 ignore start */
28
28
  }
29
29
  catch (er) {
@@ -44,8 +44,10 @@ const clobberSymlink = async (target, link, remover, type = 'file') => {
44
44
  export const addEdge = async (edge, scurry, remover, bins) => {
45
45
  if (!edge.to)
46
46
  return;
47
- const binRoot = scurry.resolve(edge.from.nodeModules(scurry), '.bin');
48
- const path = scurry.resolve(edge.from.nodeModules(scurry), edge.spec.name);
47
+ const { sep } = scurry.cwd;
48
+ const nm = edge.from.nodeModules(scurry);
49
+ const binRoot = nm + sep + '.bin';
50
+ const path = nm + sep + edge.spec.name.replace('/', sep);
49
51
  const promises = [];
50
52
  const target = relative(dirname(path), edge.to.resolvedLocation(scurry));
51
53
  // can only parallelize this on posix, because the win32 shims
@@ -57,8 +59,8 @@ export const addEdge = async (edge, scurry, remover, bins) => {
57
59
  promises.push(p);
58
60
  if (bins) {
59
61
  for (const [key, val] of Object.entries(bins)) {
60
- const link = scurry.resolve(binRoot, key);
61
- const absTarget = scurry.resolve(path, val);
62
+ const link = resolve(binRoot, key);
63
+ const absTarget = resolve(path, val);
62
64
  const target = relative(binRoot, absTarget);
63
65
  // TODO: bash/cmd/ps1 shims on Windows
64
66
  promises.push(process.platform === 'win32' ?
@@ -1,5 +1,6 @@
1
1
  import { statSync } from 'node:fs';
2
2
  import { chmod } from 'node:fs/promises';
3
+ import { join } from 'node:path';
3
4
  /**
4
5
  * Make all the packages' bins executable.
5
6
  */
@@ -18,7 +19,7 @@ export const binChmod = async (node, scurry) => {
18
19
  if (!node.bins)
19
20
  return;
20
21
  for (const bin of Object.values(node.bins)) {
21
- const path = scurry.resolve(`${node.resolvedLocation(scurry)}/${bin}`);
22
+ const path = join(node.resolvedLocation(scurry), bin);
22
23
  let mode;
23
24
  try {
24
25
  mode = statSync(path).mode;
@@ -1,3 +1,4 @@
1
+ import { resolve } from 'node:path';
1
2
  const rmBinPosix = (remover, bin) => {
2
3
  return [remover.rm(bin)];
3
4
  };
@@ -11,15 +12,16 @@ const rmBinWin32 = (remover, bin) => {
11
12
  const rmBin = process.platform === 'win32' ? rmBinWin32 : rmBinPosix;
12
13
  export const deleteEdge = async (edge, scurry, remover) => {
13
14
  const { spec: { name }, to, } = edge;
15
+ const { sep } = scurry.cwd;
14
16
  const nm = edge.from.nodeModules(scurry);
15
- const path = scurry.resolve(nm, name);
16
- const binRoot = scurry.cwd.resolve(`${nm}/.bin`);
17
+ const path = nm + sep + name.replace('/', sep);
18
+ const binRoot = nm + sep + '.bin';
17
19
  const promises = [];
18
20
  promises.push(remover.rm(path));
19
21
  const bins = to?.bins;
20
22
  if (bins) {
21
23
  for (const key of Object.keys(bins)) {
22
- const bin = binRoot.resolve(key).fullpath();
24
+ const bin = resolve(binRoot, key);
23
25
  promises.push(...rmBin(remover, bin));
24
26
  }
25
27
  }
@@ -1,12 +1,13 @@
1
1
  import { deleteEdge } from "./delete-edge.js";
2
2
  export const deleteNodes = (diff, remover, scurry) => {
3
3
  const store = scurry.resolve('node_modules/.vlt');
4
+ const { sep } = scurry.cwd;
4
5
  const rmActions = [];
5
6
  for (const node of diff.nodes.delete) {
6
7
  // do not delete workspaces or link targets
7
8
  if (!node.inVltStore())
8
9
  continue;
9
- rmActions.push(remover.rm(scurry.resolve(store, node.id)));
10
+ rmActions.push(remover.rm(store + sep + node.id));
10
11
  for (const edge of node.edgesIn) {
11
12
  rmActions.push(deleteEdge(edge, scurry, remover));
12
13
  }
@@ -1,7 +1,7 @@
1
1
  import { hydrate } from '@vltpkg/dep-id';
2
- import { platformCheck } from '@vltpkg/pick-manifest';
3
2
  import { asManifest, normalizeManifest } from '@vltpkg/types';
4
- import { optionalFail } from "./optional-fail.js";
3
+ import { lstatSync } from 'node:fs';
4
+ import { isUnsupported, optionalFail } from "./optional-fail.js";
5
5
  import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
6
6
  /**
7
7
  * Returns a function that handles removing
@@ -26,12 +26,8 @@ export const extractNode = async (node, scurry, remover, options, packageInfo, d
26
26
  const spec = hydrate(node.id, node.name, options);
27
27
  const removeOptionalFailedNode = getOptionalFailedNodeRemover(node, diff);
28
28
  const { integrity, resolved } = node;
29
- // Use platform data from node if available (from lockfile), otherwise fall back to manifest
30
- const platformData = node.platform ?? manifest;
31
- // Check if we should skip this node due to platform incompatibility or deprecation
32
- if (removeOptionalFailedNode &&
33
- (manifest.deprecated ||
34
- !platformCheck(platformData, process.version, process.platform, process.arch))) {
29
+ // skip optional nodes that are deprecated or can't run here
30
+ if (removeOptionalFailedNode && isUnsupported(node)) {
35
31
  removeOptionalFailedNode();
36
32
  return {
37
33
  success: false,
@@ -69,7 +65,10 @@ export const extractNode = async (node, scurry, remover, options, packageInfo, d
69
65
  return { success: true, node };
70
66
  };
71
67
  try {
72
- await remover.rm(target);
68
+ // nothing to move away on a fresh tree
69
+ if (lstatSync(target, { throwIfNoEntry: false })) {
70
+ await remover.rm(target);
71
+ }
73
72
  if (removeOptionalFailedNode) {
74
73
  try {
75
74
  return extracted(await packageInfo.extract(spec, target, extractOptions));
@@ -11,6 +11,7 @@ import { deleteEdges } from "./delete-edges.js";
11
11
  import { checkNeededBuild } from "./check-needed-build.js";
12
12
  import { deleteNodes } from "./delete-nodes.js";
13
13
  import { internalHoist } from "./internal-hoist.js";
14
+ import { pruneUnsupportedOptional } from "./optional-fail.js";
14
15
  import { rollback } from "./rollback.js";
15
16
  import { updatePackageJson } from "./update-importers-package-json.js";
16
17
  import { copyFileSync } from 'node:fs';
@@ -144,6 +145,9 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
144
145
  // any failed/removed optional deps are not reflected in the lockfile
145
146
  // data as it is saved.
146
147
  const lfData = lockfileData(options);
148
+ // unsupported optional deps are dropped only after that, so the
149
+ // lockfile keeps them for other platforms
150
+ pruneUnsupportedOptional(diff);
147
151
  const actions = addNodes(diff, scurry, remover, options, packageInfo).concat(deleteEdges(diff, scurry, remover));
148
152
  // need to wait, so that the nodes exist to link to
149
153
  if (actions.length)
@@ -1,7 +1,8 @@
1
1
  import { splitDepID } from '@vltpkg/dep-id';
2
2
  import { Version } from '@vltpkg/semver';
3
- import { mkdir, symlink } from 'node:fs/promises';
3
+ import { mkdir } from 'node:fs/promises';
4
4
  import { dirname, relative } from 'node:path';
5
+ import { symlinkSyncMkdirp } from "./symlink-sync.js";
5
6
  export const pickNodeToHoist = (nodes) => {
6
7
  let pick = undefined;
7
8
  let pickNode = undefined;
@@ -107,16 +108,15 @@ export const internalHoist = async (graph, options, remover) => {
107
108
  }
108
109
  }
109
110
  await Promise.all(removes);
110
- const symlinks = [];
111
+ const hoistPath = hoistDir.fullpath();
112
+ const vlt = scurry.resolve('node_modules/.vlt');
113
+ const { sep } = scurry.cwd;
114
+ const native = (n) => n.replace('/', sep);
111
115
  for (const [name, { name: nodeName, id }] of links) {
112
- const target = scurry.resolve(`node_modules/.vlt/${id}/node_modules/${nodeName}`);
113
- const path = scurry.resolve(`node_modules/.vlt/node_modules/${name}`);
114
- if (name.includes('/')) {
115
- await mkdir(dirname(path), { recursive: true });
116
- }
117
- symlinks.push(symlink(relative(dirname(path), target), path, 'dir'));
116
+ const target = vlt + sep + id + sep + 'node_modules' + sep + native(nodeName);
117
+ const path = hoistPath + sep + native(name);
118
+ symlinkSyncMkdirp(relative(dirname(path), target), path, 'dir');
118
119
  }
119
- await Promise.all(symlinks);
120
120
  };
121
121
  const checkExisting = async (name, entry, links, removes, scurry, remover) => {
122
122
  const target = await entry.readlink();
@@ -13,3 +13,14 @@ export declare function optionalFail(diff: Diff, node: Node & {
13
13
  optional: false;
14
14
  }): undefined;
15
15
  export declare function optionalFail(diff: Diff, node: Node): (() => void) | undefined;
16
+ /**
17
+ * True if the node is deprecated or can't run on this platform.
18
+ * Uses lockfile platform data when present, else the manifest.
19
+ */
20
+ export declare const isUnsupported: (node: Node) => boolean;
21
+ /**
22
+ * Drop optional adds that can't install here, and deps only they
23
+ * need, before anything is extracted. Lockfile data must be taken
24
+ * first, so it keeps them for other platforms.
25
+ */
26
+ export declare const pruneUnsupportedOptional: (diff: Diff) => void;
@@ -1,3 +1,4 @@
1
+ import { platformCheck } from '@vltpkg/pick-manifest';
1
2
  import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
2
3
  export function optionalFail(diff, node) {
3
4
  return node.isOptional() ? () => del(diff, node) : undefined;
@@ -13,3 +14,38 @@ const del = (diff, node) => {
13
14
  diff.nodes.add.delete(node);
14
15
  diff.nodes.delete.add(node);
15
16
  };
17
+ /**
18
+ * True if the node is deprecated or can't run on this platform.
19
+ * Uses lockfile platform data when present, else the manifest.
20
+ */
21
+ export const isUnsupported = (node) => !!node.manifest?.deprecated ||
22
+ !platformCheck(node.platform ?? node.manifest ?? {}, process.version, process.platform, process.arch);
23
+ /**
24
+ * Drop optional adds that can't install here, and deps only they
25
+ * need, before anything is extracted. Lockfile data must be taken
26
+ * first, so it keeps them for other platforms.
27
+ */
28
+ export const pruneUnsupportedOptional = (diff) => {
29
+ let pruned = false;
30
+ for (const node of diff.nodes.add) {
31
+ if (node.inVltStore() &&
32
+ !node.extracted &&
33
+ node.isOptional() &&
34
+ isUnsupported(node)) {
35
+ del(diff, node);
36
+ pruned = true;
37
+ }
38
+ }
39
+ if (!pruned)
40
+ return;
41
+ for (const node of diff.to.gc().values()) {
42
+ diff.nodes.add.delete(node);
43
+ diff.nodes.delete.add(node);
44
+ }
45
+ // edges out of removed nodes keep their `to`; never link them
46
+ for (const edge of diff.edges.add) {
47
+ if (diff.to.nodes.get(edge.from.id) !== edge.from) {
48
+ diff.edges.add.delete(edge);
49
+ }
50
+ }
51
+ };
@@ -0,0 +1,5 @@
1
+ import type { symlink } from 'node:fs';
2
+ /**
3
+ * `symlinkSync`, creating the parent dir on ENOENT.
4
+ */
5
+ export declare const symlinkSyncMkdirp: (target: string, link: string, type?: symlink.Type) => void;
@@ -0,0 +1,16 @@
1
+ import { mkdirSync, symlinkSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ /**
4
+ * `symlinkSync`, creating the parent dir on ENOENT.
5
+ */
6
+ export const symlinkSyncMkdirp = (target, link, type) => {
7
+ try {
8
+ symlinkSync(target, link, type);
9
+ }
10
+ catch (er) {
11
+ if (er.code !== 'ENOENT')
12
+ throw er;
13
+ mkdirSync(dirname(link), { recursive: true });
14
+ symlinkSync(target, link, type);
15
+ }
16
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/graph",
3
3
  "description": "A library that helps understanding & expressing what happens on an install",
4
- "version": "1.3.1",
4
+ "version": "1.3.3",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -13,26 +13,26 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/cmd-shim": "1.3.1",
17
- "@vltpkg/dep-id": "1.3.1",
18
- "@vltpkg/dss-breadcrumb": "1.3.1",
19
- "@vltpkg/error-cause": "1.3.1",
20
- "@vltpkg/fast-split": "1.3.1",
21
- "@vltpkg/graph-run": "1.3.1",
22
- "@vltpkg/init": "1.3.1",
23
- "@vltpkg/output": "1.3.1",
24
- "@vltpkg/package-info": "1.3.1",
25
- "@vltpkg/package-json": "1.3.1",
26
- "@vltpkg/pick-manifest": "1.3.1",
27
- "@vltpkg/query": "1.3.1",
28
- "@vltpkg/rollback-remove": "1.3.1",
29
- "@vltpkg/run": "1.3.1",
30
- "@vltpkg/satisfies": "1.3.1",
31
- "@vltpkg/security-archive": "1.3.1",
32
- "@vltpkg/spec": "1.3.1",
33
- "@vltpkg/types": "1.3.1",
34
- "@vltpkg/vlt-json": "1.3.1",
35
- "@vltpkg/workspaces": "1.3.1",
16
+ "@vltpkg/cmd-shim": "1.3.3",
17
+ "@vltpkg/dep-id": "1.3.3",
18
+ "@vltpkg/dss-breadcrumb": "1.3.3",
19
+ "@vltpkg/error-cause": "1.3.3",
20
+ "@vltpkg/fast-split": "1.3.3",
21
+ "@vltpkg/graph-run": "1.3.3",
22
+ "@vltpkg/init": "1.3.3",
23
+ "@vltpkg/output": "1.3.3",
24
+ "@vltpkg/package-info": "1.3.3",
25
+ "@vltpkg/package-json": "1.3.3",
26
+ "@vltpkg/pick-manifest": "1.3.3",
27
+ "@vltpkg/query": "1.3.3",
28
+ "@vltpkg/rollback-remove": "1.3.3",
29
+ "@vltpkg/run": "1.3.3",
30
+ "@vltpkg/satisfies": "1.3.3",
31
+ "@vltpkg/security-archive": "1.3.3",
32
+ "@vltpkg/spec": "1.3.3",
33
+ "@vltpkg/types": "1.3.3",
34
+ "@vltpkg/vlt-json": "1.3.3",
35
+ "@vltpkg/workspaces": "1.3.3",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },