@venn-lang/cli 0.1.1 → 0.1.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.
package/dist/cli.mjs CHANGED
@@ -1454,11 +1454,27 @@ function readBare(cur) {
1454
1454
  const num = Number(raw);
1455
1455
  return raw !== "" && !Number.isNaN(num) ? num : raw;
1456
1456
  }
1457
+ /**
1458
+ * An empty table for a manifest to fill.
1459
+ *
1460
+ * Made without a prototype, so a key read from the file is a key and nothing
1461
+ * else. Assigning `__proto__` onto an ordinary object changes how every object
1462
+ * in the process behaves, and a manifest arrives with somebody else's project:
1463
+ * a `venn.toml` holding `[package.__proto__]` was enough to put a property on
1464
+ * `Object.prototype`.
1465
+ *
1466
+ * Refusing that one name would have closed that one door. Having no prototype
1467
+ * to reach removes the question, and the table is read with `Object.keys` and
1468
+ * `Object.entries`, which do not care that it has none.
1469
+ */
1470
+ function emptyTable() {
1471
+ return Object.create(null);
1472
+ }
1457
1473
  /** The table `[path]` names, created along the way if it is not there yet. */
1458
1474
  function enterSection(root, path) {
1459
1475
  let node = root;
1460
1476
  for (const key of keysOf$1(path)) {
1461
- node[key] ??= {};
1477
+ node[key] ??= emptyTable();
1462
1478
  node = node[key];
1463
1479
  }
1464
1480
  return node;
@@ -1476,7 +1492,7 @@ function enterTableArray(root, path) {
1476
1492
  const parent = enterSection(root, keys.join("."));
1477
1493
  const found = Array.isArray(parent[last]) ? parent[last] : [];
1478
1494
  parent[last] = found;
1479
- const table = {};
1495
+ const table = emptyTable();
1480
1496
  found.push(table);
1481
1497
  return table;
1482
1498
  }
@@ -1494,7 +1510,7 @@ function keysOf$1(path) {
1494
1510
  * @returns the root table. Malformed lines are skipped, never thrown on.
1495
1511
  */
1496
1512
  function parseToml(content) {
1497
- const root = {};
1513
+ const root = emptyTable();
1498
1514
  let section = root;
1499
1515
  for (const raw of content.split(/\r?\n/)) {
1500
1516
  const line = stripComment(raw).trim();
@@ -1627,7 +1643,7 @@ function join$2(...parts) {
1627
1643
  * directory it was written against; stopping short of it would leave a walk up
1628
1644
  * from `packages/api` never reaching the workspace root beside it.
1629
1645
  */
1630
- function parentOf(path) {
1646
+ function parentOf$1(path) {
1631
1647
  const flat = normalise$1(path);
1632
1648
  if (flat === "" || flat === "/") return void 0;
1633
1649
  const slash = flat.lastIndexOf("/");
@@ -1642,7 +1658,7 @@ function parentOf(path) {
1642
1658
  */
1643
1659
  function ancestors(path) {
1644
1660
  const found = [];
1645
- for (let at = normalise$1(path); at !== void 0; at = parentOf(at)) found.push(at);
1661
+ for (let at = normalise$1(path); at !== void 0; at = parentOf$1(at)) found.push(at);
1646
1662
  return found;
1647
1663
  }
1648
1664
  /** Whether `path` is `base` or sits inside it. */
@@ -55669,9 +55685,12 @@ function crashed(error) {
55669
55685
  //#region src/upgrade/install-site.ts
55670
55686
  /**
55671
55687
  * Where each manager keeps what it installed for the user rather than for a
55672
- * project. A copy sitting under one of these is global wherever it was invoked
55673
- * from, which matters because several of them live under the home directory:
55674
- * running from `~` would otherwise make a global install look like a local one.
55688
+ * project. A copy under one of these is global wherever it was invoked from,
55689
+ * which matters because several live under the home directory: running from `~`
55690
+ * would otherwise make a global install look like a project's.
55691
+ *
55692
+ * The list cannot be complete on its own, since npm follows wherever a version
55693
+ * manager puts node. {@link isBesideNode} covers what it cannot name.
55675
55694
  */
55676
55695
  const GLOBAL_ROOTS = [
55677
55696
  "/pnpm/global/",
@@ -55689,13 +55708,11 @@ const GLOBAL_ROOTS = [
55689
55708
  * running, and is empty when the binary is invoked directly, which is every
55690
55709
  * time anyone actually uses it.
55691
55710
  *
55692
- * Each manager keeps its global root somewhere unmistakable, so the path is
55693
- * enough to tell them apart on all three operating systems.
55694
- *
55695
55711
  * @param path Where the running CLI lives. Must be a plain path: pass
55696
55712
  * `fileURLToPath(import.meta.url)`, since a `file:///` URL would be read as
55697
55713
  * living outside every project.
55698
55714
  * @param cwd The directory the user invoked it from.
55715
+ * @param nodePath The node binary running this, from `process.execPath`.
55699
55716
  * @returns The manager and whether the install is global, or `unknown` when the
55700
55717
  * path matches nothing recognisable.
55701
55718
  */
@@ -55708,7 +55725,11 @@ function installSiteOf(args) {
55708
55725
  };
55709
55726
  return {
55710
55727
  manager,
55711
- global: isGlobal(path, normalise(args.cwd))
55728
+ global: isGlobal({
55729
+ path,
55730
+ cwd: normalise(args.cwd),
55731
+ node: normalise(args.nodePath)
55732
+ })
55712
55733
  };
55713
55734
  }
55714
55735
  function normalise(path) {
@@ -55722,19 +55743,38 @@ function managerOf(path) {
55722
55743
  }
55723
55744
  /**
55724
55745
  * A copy under the working directory belongs to that project, not to the user,
55725
- * unless it sits in a global root that happens to be an ancestor of it.
55746
+ * unless it sits somewhere only a global install can be.
55726
55747
  *
55727
55748
  * Upgrading a copy the project owns would move a version its manifest still
55728
55749
  * pins, so the next install would put the old one back and the user would be
55729
55750
  * left wondering which of the two is running.
55730
55751
  */
55731
- function isGlobal(path, cwd) {
55732
- if (isGlobalRoot(path)) return true;
55733
- return !path.startsWith(`${cwd}/`);
55752
+ function isGlobal(args) {
55753
+ if (GLOBAL_ROOTS.some((root) => args.path.includes(root))) return true;
55754
+ if (args.path.includes("/yarn/") && args.path.includes("/global/")) return true;
55755
+ if (isBesideNode(args.path, args.node)) return true;
55756
+ return !args.path.startsWith(`${args.cwd}/`);
55734
55757
  }
55735
- function isGlobalRoot(path) {
55736
- if (GLOBAL_ROOTS.some((root) => path.includes(root))) return true;
55737
- return path.includes("/yarn/") && path.includes("/global/");
55758
+ /**
55759
+ * npm keeps its global packages beside the node binary: alongside it on
55760
+ * Windows, under a sibling `lib` elsewhere.
55761
+ *
55762
+ * Asked of node rather than matched against a list of directory names, because
55763
+ * a version manager puts node wherever it likes and the global root follows it
55764
+ * there. `C:\nvm4w\nodejs` is a link to `AppData\Local\nvm\v24.17.0`, which
55765
+ * node resolves before anything here sees it, and no list would have had that
55766
+ * name in it. The upgrade then read every global install as a project's and
55767
+ * refused to touch it.
55768
+ */
55769
+ function isBesideNode(path, node) {
55770
+ const directory = parentOf(node);
55771
+ if (directory === "") return false;
55772
+ if (path.startsWith(`${directory}/node_modules/`)) return true;
55773
+ return path.startsWith(`${parentOf(directory)}/lib/node_modules/`);
55774
+ }
55775
+ function parentOf(path) {
55776
+ const at = path.lastIndexOf("/");
55777
+ return at === -1 ? "" : path.slice(0, at);
55738
55778
  }
55739
55779
  //#endregion
55740
55780
  //#region src/upgrade/upgrade-plan.ts
@@ -55895,7 +55935,8 @@ async function upgradeCommand(args) {
55895
55935
  function currentSite() {
55896
55936
  return installSiteOf({
55897
55937
  path: fileURLToPath(import.meta.url),
55898
- cwd: process.cwd()
55938
+ cwd: process.cwd(),
55939
+ nodePath: process.execPath
55899
55940
  });
55900
55941
  }
55901
55942
  function fail(message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venn-lang/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "The venn binary: one command for starting, checking, running and testing a Venn project.",
5
5
  "keywords": [
6
6
  "venn",
@@ -41,17 +41,17 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "citty": "^0.2.2",
44
- "@venn-lang/assert": "0.1.1",
45
- "@venn-lang/contracts": "0.1.1",
46
- "@venn-lang/core": "0.1.1",
47
- "@venn-lang/dts": "0.1.1",
48
- "@venn-lang/http": "0.1.1",
49
- "@venn-lang/io": "0.1.1",
50
- "@venn-lang/project": "0.1.1",
51
- "@venn-lang/runtime": "0.1.1",
52
- "@venn-lang/sdk": "0.1.1",
53
- "@venn-lang/stdlib": "0.1.1",
54
- "@venn-lang/types": "0.1.1"
44
+ "@venn-lang/assert": "0.1.3",
45
+ "@venn-lang/contracts": "0.1.3",
46
+ "@venn-lang/dts": "0.1.3",
47
+ "@venn-lang/core": "0.1.3",
48
+ "@venn-lang/http": "0.1.3",
49
+ "@venn-lang/project": "0.1.3",
50
+ "@venn-lang/runtime": "0.1.3",
51
+ "@venn-lang/sdk": "0.1.3",
52
+ "@venn-lang/stdlib": "0.1.3",
53
+ "@venn-lang/types": "0.1.3",
54
+ "@venn-lang/io": "0.1.3"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^24",
@@ -40,7 +40,11 @@ export async function upgradeCommand(args: {
40
40
  * every install inside a project look global.
41
41
  */
42
42
  function currentSite(): InstallSite {
43
- return installSiteOf({ path: fileURLToPath(import.meta.url), cwd: process.cwd() });
43
+ return installSiteOf({
44
+ path: fileURLToPath(import.meta.url),
45
+ cwd: process.cwd(),
46
+ nodePath: process.execPath,
47
+ });
44
48
  }
45
49
 
46
50
  function fail(message: string): number {
@@ -2,9 +2,12 @@ import type { InstallSite, PackageManager } from "./upgrade.types.js";
2
2
 
3
3
  /**
4
4
  * Where each manager keeps what it installed for the user rather than for a
5
- * project. A copy sitting under one of these is global wherever it was invoked
6
- * from, which matters because several of them live under the home directory:
7
- * running from `~` would otherwise make a global install look like a local one.
5
+ * project. A copy under one of these is global wherever it was invoked from,
6
+ * which matters because several live under the home directory: running from `~`
7
+ * would otherwise make a global install look like a project's.
8
+ *
9
+ * The list cannot be complete on its own, since npm follows wherever a version
10
+ * manager puts node. {@link isBesideNode} covers what it cannot name.
8
11
  */
9
12
  const GLOBAL_ROOTS: readonly string[] = [
10
13
  "/pnpm/global/",
@@ -23,21 +26,21 @@ const GLOBAL_ROOTS: readonly string[] = [
23
26
  * running, and is empty when the binary is invoked directly, which is every
24
27
  * time anyone actually uses it.
25
28
  *
26
- * Each manager keeps its global root somewhere unmistakable, so the path is
27
- * enough to tell them apart on all three operating systems.
28
- *
29
29
  * @param path Where the running CLI lives. Must be a plain path: pass
30
30
  * `fileURLToPath(import.meta.url)`, since a `file:///` URL would be read as
31
31
  * living outside every project.
32
32
  * @param cwd The directory the user invoked it from.
33
+ * @param nodePath The node binary running this, from `process.execPath`.
33
34
  * @returns The manager and whether the install is global, or `unknown` when the
34
35
  * path matches nothing recognisable.
35
36
  */
36
- export function installSiteOf(args: { path: string; cwd: string }): InstallSite {
37
+ export function installSiteOf(args: { path: string; cwd: string; nodePath: string }): InstallSite {
37
38
  const path = normalise(args.path);
38
39
  const manager = managerOf(path);
39
40
  if (!manager) return { manager: "unknown", global: false };
40
- return { manager, global: isGlobal(path, normalise(args.cwd)) };
41
+ const cwd = normalise(args.cwd);
42
+ const node = normalise(args.nodePath);
43
+ return { manager, global: isGlobal({ path, cwd, node }) };
41
44
  }
42
45
 
43
46
  function normalise(path: string): string {
@@ -53,18 +56,38 @@ function managerOf(path: string): PackageManager | undefined {
53
56
 
54
57
  /**
55
58
  * A copy under the working directory belongs to that project, not to the user,
56
- * unless it sits in a global root that happens to be an ancestor of it.
59
+ * unless it sits somewhere only a global install can be.
57
60
  *
58
61
  * Upgrading a copy the project owns would move a version its manifest still
59
62
  * pins, so the next install would put the old one back and the user would be
60
63
  * left wondering which of the two is running.
61
64
  */
62
- function isGlobal(path: string, cwd: string): boolean {
63
- if (isGlobalRoot(path)) return true;
64
- return !path.startsWith(`${cwd}/`);
65
+ function isGlobal(args: { path: string; cwd: string; node: string }): boolean {
66
+ if (GLOBAL_ROOTS.some((root) => args.path.includes(root))) return true;
67
+ if (args.path.includes("/yarn/") && args.path.includes("/global/")) return true;
68
+ if (isBesideNode(args.path, args.node)) return true;
69
+ return !args.path.startsWith(`${args.cwd}/`);
70
+ }
71
+
72
+ /**
73
+ * npm keeps its global packages beside the node binary: alongside it on
74
+ * Windows, under a sibling `lib` elsewhere.
75
+ *
76
+ * Asked of node rather than matched against a list of directory names, because
77
+ * a version manager puts node wherever it likes and the global root follows it
78
+ * there. `C:\nvm4w\nodejs` is a link to `AppData\Local\nvm\v24.17.0`, which
79
+ * node resolves before anything here sees it, and no list would have had that
80
+ * name in it. The upgrade then read every global install as a project's and
81
+ * refused to touch it.
82
+ */
83
+ function isBesideNode(path: string, node: string): boolean {
84
+ const directory = parentOf(node);
85
+ if (directory === "") return false;
86
+ if (path.startsWith(`${directory}/node_modules/`)) return true;
87
+ return path.startsWith(`${parentOf(directory)}/lib/node_modules/`);
65
88
  }
66
89
 
67
- function isGlobalRoot(path: string): boolean {
68
- if (GLOBAL_ROOTS.some((root) => path.includes(root))) return true;
69
- return path.includes("/yarn/") && path.includes("/global/");
90
+ function parentOf(path: string): string {
91
+ const at = path.lastIndexOf("/");
92
+ return at === -1 ? "" : path.slice(0, at);
70
93
  }