@vltpkg/graph 1.1.1 → 1.2.0

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/dist/node.d.ts CHANGED
@@ -128,6 +128,11 @@ export declare class Node implements NodeLike {
128
128
  * Record of binary names to their paths in the package, if any.
129
129
  */
130
130
  bins?: Record<string, string>;
131
+ /**
132
+ * True if the package has a root binding.gyp, when known without a
133
+ * disk check (placed from the global store). Not persisted.
134
+ */
135
+ bindingGyp?: boolean;
131
136
  /**
132
137
  * True if this node has been built as part of the reify step.
133
138
  */
package/dist/node.js CHANGED
@@ -160,6 +160,11 @@ export class Node {
160
160
  * Record of binary names to their paths in the package, if any.
161
161
  */
162
162
  bins;
163
+ /**
164
+ * True if the package has a root binding.gyp, when known without a
165
+ * disk check (placed from the global store). Not persisted.
166
+ */
167
+ bindingGyp;
163
168
  /**
164
169
  * True if this node has been built as part of the reify step.
165
170
  */
@@ -1,4 +1,4 @@
1
- import { statSync, existsSync } from 'node:fs';
1
+ import { statSync } from 'node:fs';
2
2
  import { chmod } from 'node:fs/promises';
3
3
  /**
4
4
  * Make all the packages' bins executable.
@@ -19,20 +19,20 @@ export const binChmod = async (node, scurry) => {
19
19
  return;
20
20
  for (const bin of Object.values(node.bins)) {
21
21
  const path = scurry.resolve(`${node.resolvedLocation(scurry)}/${bin}`);
22
- // only try to make executable if the file exists
23
- if (existsSync(path)) {
24
- chmods.push(makeExecutable(path));
22
+ let mode;
23
+ try {
24
+ mode = statSync(path).mode;
25
25
  }
26
+ catch {
27
+ // missing or unreadable (ENOENT, ELOOP, EACCES): skip
28
+ continue;
29
+ }
30
+ // a file linked from the global store is executable already, and
31
+ // chmod would change the store
32
+ if ((mode & 0o111) === 0o111)
33
+ continue;
34
+ // `| 0o111` keeps group-writable files at 0o775
35
+ chmods.push(chmod(path, (mode & 0o777) | 0o111));
26
36
  }
27
37
  await Promise.all(chmods);
28
38
  };
29
- // 0 is "not yet set"
30
- // This is defined by doing `0o111 | <mode>` so that systems
31
- // that create files group-writable result in 0o775 instead of 0o755
32
- let execMode = 0;
33
- const makeExecutable = async (path) => {
34
- if (!execMode) {
35
- execMode = (statSync(path).mode & 0o777) | 0o111;
36
- }
37
- await chmod(path, execMode);
38
- };
@@ -0,0 +1,8 @@
1
+ import type { PathScurry } from 'path-scurry';
2
+ import type { Node } from '../node.ts';
3
+ /**
4
+ * True if the package has a root binding.gyp (implicit install). Known
5
+ * from the global store index, else checked on disk past the path
6
+ * cache, which may still hold ENOENT from before the extraction.
7
+ */
8
+ export declare const hasBindingGyp: (node: Node, scurry: PathScurry) => boolean;
@@ -0,0 +1,19 @@
1
+ import { lstatSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const onDisk = (dir) => {
4
+ try {
5
+ return !!lstatSync(join(dir, 'binding.gyp'), {
6
+ throwIfNoEntry: false,
7
+ })?.isFile();
8
+ }
9
+ catch {
10
+ // e.g. ENOTDIR, location is a file
11
+ return false;
12
+ }
13
+ };
14
+ /**
15
+ * True if the package has a root binding.gyp (implicit install). Known
16
+ * from the global store index, else checked on disk past the path
17
+ * cache, which may still hold ENOENT from before the extraction.
18
+ */
19
+ export const hasBindingGyp = (node, scurry) => node.bindingGyp ?? onDisk(node.resolvedLocation(scurry));
@@ -1,12 +1,12 @@
1
1
  // walk the graph of added nodes, building and chmoding their bins
2
2
  // at the end, we get back to the importers, and run their prepare
3
3
  // script as well as install script.
4
- import { join } from 'node:path';
5
4
  import { run } from '@vltpkg/run';
6
5
  import { graphRun } from '@vltpkg/graph-run';
7
6
  import { nonEmptyList } from "../non-empty-list.js";
8
7
  import { optionalFail } from "./optional-fail.js";
9
8
  import { binChmod } from "./bin-chmod.js";
9
+ import { hasBindingGyp } from "./binding-gyp.js";
10
10
  import { scriptsManifest } from "./scripts-manifest.js";
11
11
  export const build = async (diff, packageJson, scurry, allowScriptsNodes) => {
12
12
  const graph = diff.to;
@@ -78,10 +78,7 @@ const visit = async (packageJson, scurry, node, signal, _path) => {
78
78
  // "If there is a binding.gyp file in the root of your package and you
79
79
  // haven't defined your own install or preinstall scripts, npm will default
80
80
  // the install command to compile using node-gyp via node-gyp rebuild"
81
- const hasBindingGyp = scurry
82
- .lstatSync(join(node.resolvedLocation(scurry), 'binding.gyp'))
83
- ?.isFile() ?? false;
84
- const hasImplicitInstall = hasBindingGyp && !install && !preinstall;
81
+ const hasImplicitInstall = hasBindingGyp(node, scurry) && !install && !preinstall;
85
82
  // if it has install script or binding.gyp (implicit install), run it
86
83
  const runInstall = !!(install || preinstall || postinstall) || hasImplicitInstall;
87
84
  if (runInstall) {
@@ -1,4 +1,4 @@
1
- import { join } from 'node:path';
1
+ import { hasBindingGyp } from "./binding-gyp.js";
2
2
  import { scriptsManifest } from "./scripts-manifest.js";
3
3
  /**
4
4
  * Checks if a node needs to be built based on the conditions from build.ts:
@@ -38,10 +38,9 @@ const nodeNeedsBuild = (node, scurry, packageJson) => {
38
38
  // "If there is a binding.gyp file in the root of your package and you
39
39
  // haven't defined your own install or preinstall scripts, npm will default
40
40
  // the install command to compile using node-gyp via node-gyp rebuild"
41
- const hasBindingGyp = scurry
42
- .lstatSync(join(node.resolvedLocation(scurry), 'binding.gyp'))
43
- ?.isFile() ?? false;
44
- if (hasBindingGyp && !scripts.install && !scripts.preinstall)
41
+ if (hasBindingGyp(node, scurry) &&
42
+ !scripts.install &&
43
+ !scripts.preinstall)
45
44
  return true;
46
45
  // Check for prepare scripts on importers or git dependencies
47
46
  const prepable = node.id.startsWith('git') || node.importer || !node.inVltStore();
@@ -1,5 +1,6 @@
1
1
  import { hydrate } from '@vltpkg/dep-id';
2
2
  import { platformCheck } from '@vltpkg/pick-manifest';
3
+ import { asManifest, normalizeManifest } from '@vltpkg/types';
3
4
  import { optionalFail } from "./optional-fail.js";
4
5
  import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
5
6
  /**
@@ -38,22 +39,40 @@ export const extractNode = async (node, scurry, remover, options, packageInfo, d
38
39
  error: new Error('Platform check failed or package deprecated'),
39
40
  };
40
41
  }
42
+ // the registry manifest can declare install scripts that the
43
+ // tarball's package.json, read by the global store, lacks
44
+ const { scripts, hasInstallScript } = manifest;
41
45
  const extractOptions = {
42
46
  from,
43
47
  integrity,
44
48
  resolved,
45
49
  fromLockfile: node.resolvedFromLockfile,
50
+ installScripts: !!(hasInstallScript ||
51
+ scripts?.install ||
52
+ scripts?.preinstall ||
53
+ scripts?.postinstall),
54
+ };
55
+ const extracted = (r) => {
56
+ // Store computed integrity for git/remote deps
57
+ if (r.integrity && !node.integrity)
58
+ node.integrity = r.integrity;
59
+ // a global store link hands over what reify would read from disk;
60
+ // same result as reading it back, or left for that on failure
61
+ if (r.manifest && !node.manifest) {
62
+ try {
63
+ node.manifest = normalizeManifest(asManifest(JSON.parse(r.manifest)));
64
+ }
65
+ catch { }
66
+ }
67
+ if (r.bindingGyp !== undefined)
68
+ node.bindingGyp = r.bindingGyp;
69
+ return { success: true, node };
46
70
  };
47
71
  try {
48
72
  await remover.rm(target);
49
73
  if (removeOptionalFailedNode) {
50
74
  try {
51
- const result = await packageInfo.extract(spec, target, extractOptions);
52
- // Store computed integrity for git/remote deps
53
- if (result.integrity && !node.integrity) {
54
- node.integrity = result.integrity;
55
- }
56
- return { success: true, node };
75
+ return extracted(await packageInfo.extract(spec, target, extractOptions));
57
76
  }
58
77
  catch (error) {
59
78
  removeOptionalFailedNode();
@@ -61,12 +80,7 @@ export const extractNode = async (node, scurry, remover, options, packageInfo, d
61
80
  }
62
81
  }
63
82
  else {
64
- const result = await packageInfo.extract(spec, target, extractOptions);
65
- // Store computed integrity for git/remote deps
66
- if (result.integrity && !node.integrity) {
67
- node.integrity = result.integrity;
68
- }
69
- return { success: true, node };
83
+ return extracted(await packageInfo.extract(spec, target, extractOptions));
70
84
  }
71
85
  }
72
86
  catch (error) {
@@ -185,6 +185,16 @@ const reify_ = async (options, diff, remover, saveImportersPackageJson) => {
185
185
  await Promise.all(rmActions);
186
186
  // updates package.json files if anything was added / removed
187
187
  saveImportersPackageJson?.();
188
+ // an integrity first learned while extracting -- a git or remote dep,
189
+ // or a registry tarball verified against the digest the registry sent
190
+ // with it -- was not known when lfData was captured above. pin it, so
191
+ // the next install checks the same bytes.
192
+ for (const node of diff.nodes.add) {
193
+ const lfNode = lfData.nodes[node.id];
194
+ if (lfNode && node.integrity && !lfNode[2]) {
195
+ lfNode[2] = node.integrity;
196
+ }
197
+ }
188
198
  // write the ideal graph data to the lockfile
189
199
  saveData(lfData, scurry.resolve('vlt-lock.json'), false);
190
200
  // update the store config reference if a config file was used
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.1.1",
4
+ "version": "1.2.0",
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.1.1",
17
- "@vltpkg/dep-id": "1.1.1",
18
- "@vltpkg/dss-breadcrumb": "1.1.1",
19
- "@vltpkg/error-cause": "1.1.1",
20
- "@vltpkg/fast-split": "1.1.1",
21
- "@vltpkg/graph-run": "1.1.1",
22
- "@vltpkg/init": "1.1.1",
23
- "@vltpkg/output": "1.1.1",
24
- "@vltpkg/package-info": "1.1.1",
25
- "@vltpkg/package-json": "1.1.1",
26
- "@vltpkg/pick-manifest": "1.1.1",
27
- "@vltpkg/query": "1.1.1",
28
- "@vltpkg/rollback-remove": "1.1.1",
29
- "@vltpkg/run": "1.1.1",
30
- "@vltpkg/satisfies": "1.1.1",
31
- "@vltpkg/security-archive": "1.1.1",
32
- "@vltpkg/spec": "1.1.1",
33
- "@vltpkg/types": "1.1.1",
34
- "@vltpkg/vlt-json": "1.1.1",
35
- "@vltpkg/workspaces": "1.1.1",
16
+ "@vltpkg/cmd-shim": "1.2.0",
17
+ "@vltpkg/dep-id": "1.2.0",
18
+ "@vltpkg/dss-breadcrumb": "1.2.0",
19
+ "@vltpkg/error-cause": "1.2.0",
20
+ "@vltpkg/fast-split": "1.2.0",
21
+ "@vltpkg/graph-run": "1.2.0",
22
+ "@vltpkg/init": "1.2.0",
23
+ "@vltpkg/output": "1.2.0",
24
+ "@vltpkg/package-info": "1.2.0",
25
+ "@vltpkg/package-json": "1.2.0",
26
+ "@vltpkg/pick-manifest": "1.2.0",
27
+ "@vltpkg/query": "1.2.0",
28
+ "@vltpkg/rollback-remove": "1.2.0",
29
+ "@vltpkg/run": "1.2.0",
30
+ "@vltpkg/satisfies": "1.2.0",
31
+ "@vltpkg/security-archive": "1.2.0",
32
+ "@vltpkg/spec": "1.2.0",
33
+ "@vltpkg/types": "1.2.0",
34
+ "@vltpkg/vlt-json": "1.2.0",
35
+ "@vltpkg/workspaces": "1.2.0",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^22.19.2",
42
42
  "eslint": "^9.39.1",
43
43
  "prettier": "^3.7.4",
44
- "tap": "^21.5.0",
44
+ "tap": "^21.8.0",
45
45
  "tar": "^7.5.2",
46
46
  "typedoc": "~0.27.9",
47
47
  "typescript": "5.7.3",