@venn-lang/cli 0.1.2 → 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
@@ -1455,33 +1455,26 @@ function readBare(cur) {
1455
1455
  return raw !== "" && !Number.isNaN(num) ? num : raw;
1456
1456
  }
1457
1457
  /**
1458
- * Keys that reach the prototype rather than the object.
1458
+ * An empty table for a manifest to fill.
1459
1459
  *
1460
- * A manifest is data that arrives with somebody else's project, and assigning
1461
- * one of these from it changes how every object in the process behaves. A
1462
- * `venn.toml` containing `[package.__proto__]` was enough to put a property on
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
1463
1464
  * `Object.prototype`.
1464
- */
1465
- const RESERVED$1 = /* @__PURE__ */ new Set([
1466
- "__proto__",
1467
- "constructor",
1468
- "prototype"
1469
- ]);
1470
- /**
1471
- * Whether a key read from a manifest may be assigned.
1472
1465
  *
1473
- * @param key The key exactly as it was written in the file.
1474
- * @returns `false` for a key that would reach the prototype, which the caller
1475
- * should skip rather than assign.
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.
1476
1469
  */
1477
- function isSafeKey(key) {
1478
- return !RESERVED$1.has(key);
1470
+ function emptyTable() {
1471
+ return Object.create(null);
1479
1472
  }
1480
1473
  /** The table `[path]` names, created along the way if it is not there yet. */
1481
1474
  function enterSection(root, path) {
1482
1475
  let node = root;
1483
1476
  for (const key of keysOf$1(path)) {
1484
- node[key] ??= {};
1477
+ node[key] ??= emptyTable();
1485
1478
  node = node[key];
1486
1479
  }
1487
1480
  return node;
@@ -1499,12 +1492,12 @@ function enterTableArray(root, path) {
1499
1492
  const parent = enterSection(root, keys.join("."));
1500
1493
  const found = Array.isArray(parent[last]) ? parent[last] : [];
1501
1494
  parent[last] = found;
1502
- const table = {};
1495
+ const table = emptyTable();
1503
1496
  found.push(table);
1504
1497
  return table;
1505
1498
  }
1506
1499
  function keysOf$1(path) {
1507
- return path.split(".").map((part) => part.trim().replace(/^["']|["']$/g, "")).filter((part) => part !== "" && isSafeKey(part));
1500
+ return path.split(".").map((part) => part.trim().replace(/^["']|["']$/g, "")).filter((part) => part !== "");
1508
1501
  }
1509
1502
  /**
1510
1503
  * A TOML reader for `venn.toml`: sections, nested `[a.b]`, arrays of tables
@@ -1517,7 +1510,7 @@ function keysOf$1(path) {
1517
1510
  * @returns the root table. Malformed lines are skipped, never thrown on.
1518
1511
  */
1519
1512
  function parseToml(content) {
1520
- const root = {};
1513
+ const root = emptyTable();
1521
1514
  let section = root;
1522
1515
  for (const raw of content.split(/\r?\n/)) {
1523
1516
  const line = stripComment(raw).trim();
@@ -1545,7 +1538,6 @@ function assign(section, line) {
1545
1538
  const eq = line.indexOf("=");
1546
1539
  if (eq < 0) return;
1547
1540
  const key = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
1548
- if (!isSafeKey(key)) return;
1549
1541
  section[key] = readValue(cursor(line.slice(eq + 1)));
1550
1542
  }
1551
1543
  /**
@@ -1651,7 +1643,7 @@ function join$2(...parts) {
1651
1643
  * directory it was written against; stopping short of it would leave a walk up
1652
1644
  * from `packages/api` never reaching the workspace root beside it.
1653
1645
  */
1654
- function parentOf(path) {
1646
+ function parentOf$1(path) {
1655
1647
  const flat = normalise$1(path);
1656
1648
  if (flat === "" || flat === "/") return void 0;
1657
1649
  const slash = flat.lastIndexOf("/");
@@ -1666,7 +1658,7 @@ function parentOf(path) {
1666
1658
  */
1667
1659
  function ancestors(path) {
1668
1660
  const found = [];
1669
- 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);
1670
1662
  return found;
1671
1663
  }
1672
1664
  /** Whether `path` is `base` or sits inside it. */
@@ -55693,9 +55685,12 @@ function crashed(error) {
55693
55685
  //#region src/upgrade/install-site.ts
55694
55686
  /**
55695
55687
  * Where each manager keeps what it installed for the user rather than for a
55696
- * project. A copy sitting under one of these is global wherever it was invoked
55697
- * from, which matters because several of them live under the home directory:
55698
- * 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.
55699
55694
  */
55700
55695
  const GLOBAL_ROOTS = [
55701
55696
  "/pnpm/global/",
@@ -55713,13 +55708,11 @@ const GLOBAL_ROOTS = [
55713
55708
  * running, and is empty when the binary is invoked directly, which is every
55714
55709
  * time anyone actually uses it.
55715
55710
  *
55716
- * Each manager keeps its global root somewhere unmistakable, so the path is
55717
- * enough to tell them apart on all three operating systems.
55718
- *
55719
55711
  * @param path Where the running CLI lives. Must be a plain path: pass
55720
55712
  * `fileURLToPath(import.meta.url)`, since a `file:///` URL would be read as
55721
55713
  * living outside every project.
55722
55714
  * @param cwd The directory the user invoked it from.
55715
+ * @param nodePath The node binary running this, from `process.execPath`.
55723
55716
  * @returns The manager and whether the install is global, or `unknown` when the
55724
55717
  * path matches nothing recognisable.
55725
55718
  */
@@ -55732,7 +55725,11 @@ function installSiteOf(args) {
55732
55725
  };
55733
55726
  return {
55734
55727
  manager,
55735
- global: isGlobal(path, normalise(args.cwd))
55728
+ global: isGlobal({
55729
+ path,
55730
+ cwd: normalise(args.cwd),
55731
+ node: normalise(args.nodePath)
55732
+ })
55736
55733
  };
55737
55734
  }
55738
55735
  function normalise(path) {
@@ -55746,19 +55743,38 @@ function managerOf(path) {
55746
55743
  }
55747
55744
  /**
55748
55745
  * A copy under the working directory belongs to that project, not to the user,
55749
- * 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.
55750
55747
  *
55751
55748
  * Upgrading a copy the project owns would move a version its manifest still
55752
55749
  * pins, so the next install would put the old one back and the user would be
55753
55750
  * left wondering which of the two is running.
55754
55751
  */
55755
- function isGlobal(path, cwd) {
55756
- if (isGlobalRoot(path)) return true;
55757
- 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}/`);
55758
55757
  }
55759
- function isGlobalRoot(path) {
55760
- if (GLOBAL_ROOTS.some((root) => path.includes(root))) return true;
55761
- 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);
55762
55778
  }
55763
55779
  //#endregion
55764
55780
  //#region src/upgrade/upgrade-plan.ts
@@ -55919,7 +55935,8 @@ async function upgradeCommand(args) {
55919
55935
  function currentSite() {
55920
55936
  return installSiteOf({
55921
55937
  path: fileURLToPath(import.meta.url),
55922
- cwd: process.cwd()
55938
+ cwd: process.cwd(),
55939
+ nodePath: process.execPath
55923
55940
  });
55924
55941
  }
55925
55942
  function fail(message) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venn-lang/cli",
3
- "version": "0.1.2",
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.2",
45
- "@venn-lang/contracts": "0.1.2",
46
- "@venn-lang/core": "0.1.2",
47
- "@venn-lang/dts": "0.1.2",
48
- "@venn-lang/http": "0.1.2",
49
- "@venn-lang/io": "0.1.2",
50
- "@venn-lang/project": "0.1.2",
51
- "@venn-lang/runtime": "0.1.2",
52
- "@venn-lang/sdk": "0.1.2",
53
- "@venn-lang/stdlib": "0.1.2",
54
- "@venn-lang/types": "0.1.2"
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
  }