@redue/cli 0.1.0-alpha.3 → 0.1.0-alpha.31
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/CHANGELOG.md +831 -0
- package/README.md +157 -99
- package/bin/redue.mjs +322 -66
- package/docs/README.md +3 -1
- package/docs/user/agents.md +333 -15
- package/docs/user/configuration.md +28 -2
- package/docs/user/direct-typescript.md +219 -0
- package/docs/user/getting-started.md +50 -7
- package/docs/user/json.md +4 -2
- package/docs/user/node-script.md +177 -0
- package/docs/user/npm-launcher-notice.md +42 -0
- package/docs/user/pilot.md +146 -0
- package/docs/user/sandbox.md +326 -0
- package/docs/user/shell.md +3 -3
- package/docs/user/supported-projects.md +69 -23
- package/docs/user/tests-direct.md +143 -0
- package/package.json +4 -3
- package/src/agent-integration.mjs +467 -0
- package/src/agent-policy.md +28 -0
- package/src/cached-state.mjs +24 -1
- package/src/claude-hook.mjs +616 -0
- package/src/cli.mjs +449 -54
- package/src/daemon.mjs +442 -93
- package/src/decision-profile.mjs +15 -1
- package/src/decision-validation.mjs +12 -1
- package/src/declared-plan.mjs +38 -6
- package/src/direct-run-certificate.mjs +254 -0
- package/src/direct-typescript-probe.mjs +27 -0
- package/src/direct-typescript.mjs +205 -0
- package/src/glob.mjs +8 -0
- package/src/history-worker.mjs +21 -13
- package/src/index.mjs +108 -21
- package/src/installed-inputs.mjs +7 -1
- package/src/installed-observation.mjs +23 -0
- package/src/landlock.py +527 -0
- package/src/linux-inotify.py +39 -10
- package/src/node-onboarding.mjs +203 -54
- package/src/node-script.mjs +406 -0
- package/src/npm-integrity.mjs +124 -0
- package/src/npm-typecheck-environment.mjs +36 -0
- package/src/npmrc.mjs +16 -0
- package/src/observation.mjs +34 -12
- package/src/path-identity.mjs +8 -0
- package/src/plan-worker.mjs +17 -17
- package/src/pnpm-inputs.mjs +122 -57
- package/src/pnpm-shim-templates.json +180 -0
- package/src/pnpm-shims.mjs +54 -0
- package/src/pnpm-typecheck-probe.mjs +11 -6
- package/src/presentation.mjs +13 -4
- package/src/probe-deadline.mjs +37 -0
- package/src/probe-result.mjs +7 -3
- package/src/project-location.mjs +34 -0
- package/src/python-onboarding.mjs +89 -0
- package/src/run-recorder.cjs +91 -0
- package/src/sandbox.mjs +659 -0
- package/src/setup.mjs +224 -0
- package/src/stats.mjs +72 -0
- package/src/test-direct.mjs +608 -0
- package/src/test-recorder.cjs +435 -0
- package/src/typescript-contract-probe.mjs +4 -187
- package/src/typescript-input-facts.mjs +92 -0
- package/src/typescript-invocation.mjs +92 -0
- package/src/typescript-list.mjs +118 -13
- package/src/typescript-qualification.mjs +29 -49
- package/src/workspace-manifests.mjs +24 -0
- package/src/yarn-workspace-inputs.mjs +3 -0
- package/src/yarn-workspace-typecheck-probe.mjs +2 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"templates": [
|
|
4
|
+
{
|
|
5
|
+
"versions": [
|
|
6
|
+
"8.0.0",
|
|
7
|
+
"8.1.1",
|
|
8
|
+
"8.2.0",
|
|
9
|
+
"8.3.1",
|
|
10
|
+
"8.4.0",
|
|
11
|
+
"8.5.1",
|
|
12
|
+
"8.6.12",
|
|
13
|
+
"8.7.6",
|
|
14
|
+
"8.8.0",
|
|
15
|
+
"8.9.2",
|
|
16
|
+
"8.10.5",
|
|
17
|
+
"8.11.0",
|
|
18
|
+
"8.12.1",
|
|
19
|
+
"8.13.1",
|
|
20
|
+
"8.14.3",
|
|
21
|
+
"8.15.9",
|
|
22
|
+
"9.0.6",
|
|
23
|
+
"9.1.4",
|
|
24
|
+
"9.2.0",
|
|
25
|
+
"9.3.0",
|
|
26
|
+
"9.4.0",
|
|
27
|
+
"9.5.0",
|
|
28
|
+
"9.6.0",
|
|
29
|
+
"9.7.1",
|
|
30
|
+
"9.8.0",
|
|
31
|
+
"9.9.0",
|
|
32
|
+
"9.10.0",
|
|
33
|
+
"9.11.0",
|
|
34
|
+
"9.12.3",
|
|
35
|
+
"9.13.2",
|
|
36
|
+
"9.14.4",
|
|
37
|
+
"9.15.4",
|
|
38
|
+
"9.15.9",
|
|
39
|
+
"10.0.0",
|
|
40
|
+
"10.1.0",
|
|
41
|
+
"10.2.1",
|
|
42
|
+
"10.3.0",
|
|
43
|
+
"10.4.1",
|
|
44
|
+
"10.5.2",
|
|
45
|
+
"10.6.5",
|
|
46
|
+
"10.7.1",
|
|
47
|
+
"10.8.1",
|
|
48
|
+
"10.9.0"
|
|
49
|
+
],
|
|
50
|
+
"template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*) basedir=`cygpath -w \"$basedir\"`;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"versions": [
|
|
54
|
+
"10.10.0",
|
|
55
|
+
"10.11.1",
|
|
56
|
+
"10.12.4",
|
|
57
|
+
"10.13.1",
|
|
58
|
+
"10.14.0",
|
|
59
|
+
"10.15.1",
|
|
60
|
+
"10.16.1",
|
|
61
|
+
"10.17.1",
|
|
62
|
+
"10.18.3",
|
|
63
|
+
"10.19.0",
|
|
64
|
+
"10.20.0",
|
|
65
|
+
"10.21.0",
|
|
66
|
+
"10.22.0",
|
|
67
|
+
"10.23.0",
|
|
68
|
+
"10.24.0",
|
|
69
|
+
"10.25.0",
|
|
70
|
+
"10.26.2",
|
|
71
|
+
"10.27.0",
|
|
72
|
+
"10.28.2",
|
|
73
|
+
"10.29.3",
|
|
74
|
+
"10.30.3",
|
|
75
|
+
"10.31.0",
|
|
76
|
+
"10.32.1",
|
|
77
|
+
"10.33.2",
|
|
78
|
+
"10.33.4",
|
|
79
|
+
"10.34.7"
|
|
80
|
+
],
|
|
81
|
+
"template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir=`cygpath -w \"$basedir\"`\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"versions": [
|
|
85
|
+
"11.0.9",
|
|
86
|
+
"11.1.1",
|
|
87
|
+
"11.1.3",
|
|
88
|
+
"11.2.2",
|
|
89
|
+
"11.3.0",
|
|
90
|
+
"11.4.0",
|
|
91
|
+
"11.5.3",
|
|
92
|
+
"11.6.0"
|
|
93
|
+
],
|
|
94
|
+
"template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\n\ncase `uname` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir=`cygpath -w \"$basedir\"`\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"versions": [
|
|
98
|
+
"11.7.0",
|
|
99
|
+
"11.8.0",
|
|
100
|
+
"11.9.0",
|
|
101
|
+
"11.10.0",
|
|
102
|
+
"11.11.0",
|
|
103
|
+
"11.12.0",
|
|
104
|
+
"11.13.1",
|
|
105
|
+
"11.14.0",
|
|
106
|
+
"11.15.1",
|
|
107
|
+
"11.16.0",
|
|
108
|
+
"11.17.0",
|
|
109
|
+
"11.18.0",
|
|
110
|
+
"11.19.0",
|
|
111
|
+
"11.20.0"
|
|
112
|
+
],
|
|
113
|
+
"template": "#!/bin/sh\nbasedir=$(dirname \"$(echo \"$0\" | sed -e 's,\\\\,/,g')\")\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"versions": [
|
|
117
|
+
"11.21.0",
|
|
118
|
+
"11.22.0",
|
|
119
|
+
"11.23.0",
|
|
120
|
+
"11.24.0",
|
|
121
|
+
"11.25.0",
|
|
122
|
+
"11.26.0",
|
|
123
|
+
"12.0.0",
|
|
124
|
+
"12.1.0",
|
|
125
|
+
"12.2.1",
|
|
126
|
+
"12.3.4"
|
|
127
|
+
],
|
|
128
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\nlink=\"$0\"\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"$(dirname \"$link\")/$target\" ;;\n esac\ndone\nbasedir=$(dirname \"$(echo \"$link\" | sed -e 's,\\\\,/,g')\")\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"versions": [
|
|
132
|
+
"11.27.1"
|
|
133
|
+
],
|
|
134
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"versions": [
|
|
138
|
+
"11.28.2"
|
|
139
|
+
],
|
|
140
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# Where no default path is compiled in, as on Nix, `command -p` searches PATH\n# instead, so the helpers run with node_modules and relative entries dropped from\n# PATH.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nbasedir=\"$basedir_abs\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$msys\" ] && command -v node >/dev/null 2>&1; then\n exec node \"$basedir_win/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"versions": [
|
|
144
|
+
"11.28.6",
|
|
145
|
+
"12.8.2",
|
|
146
|
+
"12.9.1",
|
|
147
|
+
"12.10.1",
|
|
148
|
+
"12.11.2",
|
|
149
|
+
"12.12.1",
|
|
150
|
+
"12.13.0",
|
|
151
|
+
"12.14.0"
|
|
152
|
+
],
|
|
153
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# A helper the default path lacks, as in a Nix build sandbox, comes from PATH\n# instead, with node_modules and relative entries dropped from it.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\n# A helper comes from PATH only when the default path lacks it and PATH has it.\n# The bash 3.2 that macOS ships as sh answers `command -p -v` from PATH.\nrun_helper() {\n if command -p -v \"$1\" >/dev/null 2>&1 || ! command -v \"$1\" >/dev/null 2>&1; then command -p \"$@\"; else command \"$@\"; fi\n}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(run_helper readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(run_helper printf '%s\\n' \"$link\" | run_helper sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nbasedir=\"$basedir_abs\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `run_helper uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$msys\" ] && command -v node >/dev/null 2>&1; then\n exec node \"$basedir_win/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"versions": [
|
|
157
|
+
"12.4.2"
|
|
158
|
+
],
|
|
159
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and\n# uname go through `command -p`, which searches the system default path instead.\n# A dependency's bin cannot stand in for one of them and take over the shim\n# before it reaches its target. Directories come from `${link%/*}`, which needs\n# no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(echo \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"versions": [
|
|
163
|
+
"12.5.1"
|
|
164
|
+
],
|
|
165
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"versions": [
|
|
169
|
+
"12.6.0"
|
|
170
|
+
],
|
|
171
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\n\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"versions": [
|
|
175
|
+
"12.7.0"
|
|
176
|
+
],
|
|
177
|
+
"template": "#!/bin/sh\n# Resolve $0 through symlinks so basedir is the shim's real directory.\n# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.\n#\n# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed,\n# uname, and printf go through `command -p`, which searches the system default\n# path instead. A dependency's bin cannot stand in for one of them and take over\n# the shim before it reaches its target. Directories come from `${link%/*}`,\n# which needs no helper at all.\n#\n# Where no default path is compiled in, as on Nix, `command -p` searches PATH\n# instead, so the helpers run with node_modules and relative entries dropped from\n# PATH.\ncaller_path_set=${PATH+set}\ncaller_path=${PATH-}\nhelper_path=\nrest=$caller_path:\nwhile [ -n \"$rest\" ]; do\n dir=${rest%%:*}\n rest=${rest#*:}\n case \"$dir\" in\n */node_modules/*|*/node_modules) ;;\n /*) helper_path=${helper_path:+$helper_path:}$dir ;;\n esac\ndone\n# An empty PATH searches the current directory.\nPATH=${helper_path:-/}\nlink=\"$0\"\n# `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup\n# and stands for a file in the current directory.\ncase \"$link\" in\n */*|*\\\\*) ;;\n *) link=\"./$link\" ;;\nesac\nhops=0\nwhile [ -L \"$link\" ] && [ \"$hops\" -lt 40 ]; do\n hops=$((hops+1))\n target=$(command -p readlink \"$link\")\n case \"$target\" in\n /*) link=\"$target\" ;;\n *) link=\"${link%/*}/$target\" ;;\n esac\ndone\nbasedir=$(command -p printf '%s\\n' \"$link\" | command -p sed -e 's,\\\\,/,g')\nbasedir=\"${basedir%/*}\"\nbasedir_win=\"$basedir\"\nexe=\"\"\nmsys=\"\"\n\ncase `command -p uname -a` in\n *CYGWIN*|*MINGW*|*MSYS*)\n if converted=$(command -p cygpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n elif command -v cygpath > /dev/null 2>&1; then\n basedir_win=`cygpath -w \"$basedir\"`\n fi\n exe=\".exe\"\n msys=\"true\"\n ;;\n *WSL2*)\n if converted=$(command -p wslpath -w \"$basedir\" 2>/dev/null) && [ -n \"$converted\" ]; then\n basedir_win=\"$converted\"\n exe=\".exe\"\n elif command -v wslpath > /dev/null 2>&1; then\n basedir_win=\"$(wslpath -w \"$basedir\" 2> /dev/null)\"\n if [ $? -ne 0 ] || [ -z \"$basedir_win\" ]; then\n basedir_win=\"$basedir\"\n else\n exe=\".exe\"\n fi\n fi\n ;;\nesac\nif [ -n \"$caller_path_set\" ]; then PATH=$caller_path; else unset PATH; fi\n\nbasedir_abs=$(CDPATH= cd -P -- \"$basedir\" && pwd -P) || exit $?\nif [ -z \"$NODE_PATH\" ]; then\n export NODE_PATH=\"@@NODE_PATH@@\"\nelse\n export NODE_PATH=\"@@NODE_PATH@@:$NODE_PATH\"\nfi\nif [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/@@TARGET@@\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/@@TARGET@@\" \"$@\"\nelse\n exec node \"$basedir_abs/@@TARGET@@\" \"$@\"\nfi\n# cmd-shim-target=@@COMMENT@@\n"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// pnpm's launchers. On macOS and Linux, pnpm writes each node_modules/.bin
|
|
2
|
+
// entry as a `#!/bin/sh` script that finds its own directory (with sed,
|
|
3
|
+
// dirname, readlink, uname or printf, depending on the pnpm release), sets
|
|
4
|
+
// NODE_PATH and execs Node on the package's bin file. A launcher is trusted only
|
|
5
|
+
// when its text is one of the templates pnpm releases write
|
|
6
|
+
// (pnpm-shim-templates.json, built by eval/pnpm-shim-templates.mjs), with only
|
|
7
|
+
// the NODE_PATH value, the bin file and the trailing target comment filled in.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const templates=JSON.parse(fs.readFileSync(new URL('pnpm-shim-templates.json',import.meta.url),'utf8')).templates;
|
|
12
|
+
const known=new Set(templates.map(row=>row.template));
|
|
13
|
+
// A value with no quote, expansion, escape or line break: it stays literal in
|
|
14
|
+
// the shell.
|
|
15
|
+
const literal=/^[^"$`\\\n]*$/;
|
|
16
|
+
|
|
17
|
+
// The launcher's text as a template, with the values it fills in, or null
|
|
18
|
+
// when it does not have pnpm's form.
|
|
19
|
+
export function shimTemplate(text){
|
|
20
|
+
if(typeof text!=='string'||!text.startsWith('#!/bin/sh\n'))return null;
|
|
21
|
+
let template=text,nodePath=null;
|
|
22
|
+
// NODE_PATH, set alone or in front of the caller's.
|
|
23
|
+
const alone=/\n export NODE_PATH="([^"\n]*)"\n/.exec(text);
|
|
24
|
+
if(alone){
|
|
25
|
+
nodePath=alone[1];
|
|
26
|
+
// Each entry is literal, or relative to the launcher's own directory
|
|
27
|
+
// ($basedir_abs, which pnpm 12.6 and later write).
|
|
28
|
+
if(!nodePath.split(':').every(entry=>literal.test(entry.replace(/^\$basedir(?:_abs)?\//,'')))||
|
|
29
|
+
!text.includes(`\n export NODE_PATH="${nodePath}:$NODE_PATH"\n`))return null;
|
|
30
|
+
template=template.split(`\n export NODE_PATH="${nodePath}:$NODE_PATH"\n`).join('\n export NODE_PATH="@@NODE_PATH@@:$NODE_PATH"\n')
|
|
31
|
+
.split(`\n export NODE_PATH="${nodePath}"\n`).join('\n export NODE_PATH="@@NODE_PATH@@"\n');
|
|
32
|
+
}
|
|
33
|
+
// The bin file, relative to the launcher's directory, after $basedir,
|
|
34
|
+
// $basedir_abs or $basedir_win; `node` and `node.exe` there are Node itself.
|
|
35
|
+
const targets=new Set([...template.matchAll(/"\$basedir(?:_abs|_win)?\/([^"\n]+)"/g)].map(match=>match[1])
|
|
36
|
+
.filter(value=>value!=='node'&&value!=='node.exe'));
|
|
37
|
+
if(targets.size!==1)return null;
|
|
38
|
+
const [target]=targets;
|
|
39
|
+
if(!literal.test(target))return null;
|
|
40
|
+
for(const prefix of ['$basedir','$basedir_abs','$basedir_win'])
|
|
41
|
+
template=template.split(`"${prefix}/${target}"`).join(`"${prefix}/@@TARGET@@"`);
|
|
42
|
+
// pnpm 11 and later end with a comment naming the target.
|
|
43
|
+
template=template.replace(/\n# cmd-shim-target=[^\n]*\n?$/,'\n# cmd-shim-target=@@COMMENT@@\n');
|
|
44
|
+
return {template,nodePath,target};
|
|
45
|
+
}
|
|
46
|
+
// The bin file a pnpm launcher runs with Node, as an absolute path, or null
|
|
47
|
+
// when `file` is not a launcher pnpm writes.
|
|
48
|
+
export function pnpmShimTarget(file,text=null){
|
|
49
|
+
try{
|
|
50
|
+
text??=fs.readFileSync(file,'utf8');
|
|
51
|
+
const shim=shimTemplate(text);
|
|
52
|
+
return shim&&known.has(shim.template)?path.resolve(path.dirname(file),shim.target):null;
|
|
53
|
+
}catch{return null;}
|
|
54
|
+
}
|
|
@@ -7,14 +7,17 @@ import fs from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import {createHash} from 'node:crypto';
|
|
9
9
|
import {pnpmTypecheckInputs} from './pnpm-inputs.mjs';
|
|
10
|
+
import {patternMatcher} from './glob.mjs';
|
|
10
11
|
import {withinPath as within,realObservedPath} from './path-identity.mjs';
|
|
12
|
+
import {runFromArgv} from './typescript-invocation.mjs';
|
|
11
13
|
|
|
12
|
-
const [rootArg,workspace
|
|
14
|
+
const [rootArg,workspace,...rest]=process.argv.slice(2).filter(value=>!value.startsWith('--redue-'));
|
|
13
15
|
const hash=value=>createHash('sha256').update(value).digest('hex');
|
|
14
16
|
const digest=file=>hash(fs.readFileSync(file));
|
|
15
17
|
const fail=code=>{throw Object.assign(Error(code),{code});};
|
|
16
18
|
try{
|
|
17
|
-
if(!rootArg||!workspace
|
|
19
|
+
if(!rootArg||!workspace)fail('probe-arguments');
|
|
20
|
+
const run=runFromArgv(rest);
|
|
18
21
|
const root=realObservedPath(rootArg);
|
|
19
22
|
if(process.env.NODE_OPTIONS||process.env.NODE_PATH||process.env.BASH_ENV||
|
|
20
23
|
process.env.ENV||Object.entries(process.env).some(([key,value])=>{
|
|
@@ -24,23 +27,25 @@ try{
|
|
|
24
27
|
return /^(PNPM_|COREPACK_|npm_config_|DYLD_|TSGO_)/i.test(key);
|
|
25
28
|
}))
|
|
26
29
|
fail('pnpm-execution-environment-unsupported');
|
|
27
|
-
const contextHash=hash(JSON.stringify([root,workspace,
|
|
30
|
+
const contextHash=hash(JSON.stringify([root,workspace,run,process.version]));
|
|
28
31
|
if(contextOnly(contextHash))process.exit(0);
|
|
29
|
-
const contract=pnpmTypecheckInputs(root,workspace,
|
|
32
|
+
const contract=pnpmTypecheckInputs(root,workspace,run,{captureQueries:process.argv.includes('--redue-checkpoint')});
|
|
30
33
|
if(contract.limitations.length)fail('pnpm-input-coverage-unavailable');
|
|
31
34
|
const selected=path.join(root,contract.workspace.dir),tsc=contract.tsc;
|
|
32
35
|
const inputs=contract.listedFiles;
|
|
33
36
|
if(!inputs.length)fail('typescript-input-list-unavailable');
|
|
34
|
-
const inputRoots=[
|
|
37
|
+
const inputRoots=[...contract.generated.map(pattern=>
|
|
35
38
|
path.join(root,pattern.slice(0,-3))),...contract.installed.filter(p=>!p.startsWith('!'))
|
|
36
39
|
.map(pattern=>path.join(root,pattern.slice(0,-3)))];
|
|
40
|
+
const isSource=patternMatcher(contract.sourcePatterns);
|
|
37
41
|
for(const file of inputs)if(!within(file,root)||
|
|
42
|
+
!isSource(path.relative(root,file).split(path.sep).join('/'))&&
|
|
38
43
|
!inputRoots.some(boundary=>within(file,boundary)))
|
|
39
44
|
fail('typescript-input-outside-pnpm-contract');
|
|
40
45
|
const files=[...new Set(inputs)].sort().map(file=>
|
|
41
46
|
[hash(path.relative(root,file)),digest(file)]);
|
|
42
47
|
const configs=contract.configs.map(file=>[hash(file),digest(path.join(root,file))]);
|
|
43
|
-
probeResult(hash(JSON.stringify({schema:1,workspace,
|
|
48
|
+
probeResult(hash(JSON.stringify({schema:1,workspace,run,
|
|
44
49
|
files,configs,node:process.version,tsc:digest(tsc),
|
|
45
50
|
packageManager:contract.install.version,linker:contract.install.linker})),contextHash,contract.discoveryQueries);
|
|
46
51
|
}catch(e){console.error(`VSTATE_REASON:${e.code&&/^[a-z-]+$/.test(e.code)?e.code:
|
package/src/presentation.mjs
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// Presentation consumes evidence; it never grants applicability.
|
|
2
|
+
// The recipe that made a receipt, by the qualification it names.
|
|
3
|
+
const recipes={'npm-typescript-direct-v1':'direct TypeScript recipe','npm-tests-direct-v1':'direct test recipe',
|
|
4
|
+
'node-script-v1':'general recipe','pnpm-tsc-v1':'pnpm TypeScript recipe','yarn-workspace-tsc-v1':'Yarn workspace TypeScript recipe',
|
|
5
|
+
'typescript-noemit-v1':'npm TypeScript recipe','sandbox-v1':'sandboxed recipe',
|
|
6
|
+
'sandbox-v2':'sandboxed recipe, git-ignored files hidden','sandbox-v3':'sandboxed recipe, git-ignored files and docs hidden'};
|
|
7
|
+
export const recipeLabel=recipe=>recipes[recipe]??null;
|
|
2
8
|
export function checkLabel(row) {
|
|
3
9
|
return row.result==='FAIL'?'FAILED':row.freshness;
|
|
4
10
|
}
|
|
@@ -6,8 +12,8 @@ export function explanation(row) {
|
|
|
6
12
|
const reason=row.reason||'No verification evidence is available.';
|
|
7
13
|
if(reason==='no receipt')return 'No execution has been recorded. Run this check when verification is needed.';
|
|
8
14
|
if(reason==='declared inputs match')return 'Recorded evidence still applies to the declared inputs.';
|
|
9
|
-
if(reason==='execution environment context not supplied')return 'Cached status cannot confirm your execution context.
|
|
10
|
-
if(/requires? a synchronized content read/.test(reason))return 'Cached status cannot establish applicability here.
|
|
15
|
+
if(reason==='execution environment context not supplied')return 'Cached status cannot confirm your execution context. Run redue status without --cached, or rerun the check.';
|
|
16
|
+
if(/requires? a synchronized content read/.test(reason))return 'Cached status cannot establish applicability here. Run redue status without --cached, or rerun the check.';
|
|
11
17
|
if(reason.startsWith('input coverage unresolved: '))return 'Recording-only: '+reason.slice(27);
|
|
12
18
|
if(/input plan (rebuilding|indexing)|observer initialization|input observation reconciliation pending/.test(reason))
|
|
13
19
|
return 'Checking inputs after startup or changes. Wait for observation to finish, then query again.';
|
|
@@ -21,7 +27,8 @@ export function renderStatus(value,{short=false}={}) {
|
|
|
21
27
|
const label=checkLabel(row),symbol=label==='CURRENT'&&row.result==='PASS'?'✓':label==='FAILED'?'✗':'?';
|
|
22
28
|
const mark=label==='STALE'?'⚠':symbol;
|
|
23
29
|
const historical=row.result?` / ${row.result}`:'';
|
|
24
|
-
|
|
30
|
+
const recipe=label==='CURRENT'?recipeLabel(row.recipe):null;
|
|
31
|
+
lines.push(`${mark} ${row.name} ${label}${historical}${recipe?` (${recipe})`:''}`);
|
|
25
32
|
if(label!=='CURRENT')lines.push(` ${row.result==='FAIL'?`Historical failure; applicability ${row.freshness}. `:''}${explanation(row)}`);
|
|
26
33
|
}
|
|
27
34
|
if(!lines.length)lines.push('UNVERIFIED — observation is initializing; no check state is available yet.');
|
|
@@ -33,6 +40,8 @@ export function renderExplain(value,name) {
|
|
|
33
40
|
return rows.map(row=>{
|
|
34
41
|
const lines=[`${row.name}: ${checkLabel(row)}${row.result?` / ${row.result}`:''}`];
|
|
35
42
|
if(row.result==='FAIL')lines.push(`Historical failure; applicability: ${row.freshness}.`);
|
|
43
|
+
if(row.verification_recipe==='npm-direct-typescript@1')lines.push('Recorded recipe: direct local TypeScript compiler; npm and lifecycle scripts were not executed.');
|
|
44
|
+
else if(recipeLabel(row.recipe))lines.push(`Recorded recipe: ${recipeLabel(row.recipe)}.`);
|
|
36
45
|
lines.push(explanation(row));
|
|
37
46
|
if(row.changed_inputs?.length)lines.push('Relevant inputs changed:',...row.changed_inputs.map(file=>` ${file}`));
|
|
38
47
|
if(row.freshness==='STALE')lines.push(`Due again when verification is needed: redue run ${JSON.stringify(row.name)}`);
|
|
@@ -47,6 +56,6 @@ export function renderRun(value) {
|
|
|
47
56
|
if(value.invocation!=='exited')lines.push(`Invocation ${value.invocation}; no reusable passing evidence.`);
|
|
48
57
|
else if(!value.coverage_qualified)lines.push('Applicability remains UNVERIFIED: input coverage is incomplete.');
|
|
49
58
|
else if(!value.stable)lines.push('Applicability remains UNVERIFIED: inputs changed or observation was unavailable during execution.');
|
|
50
|
-
else lines.push('
|
|
59
|
+
else lines.push('Check redue status before reusing this evidence.');
|
|
51
60
|
return lines.join('\n');
|
|
52
61
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// How long a full probe may run before it is killed and the check stays
|
|
2
|
+
// UNVERIFIED. It grows with the project: the larger of the hint from earlier
|
|
3
|
+
// probes (twice the last successful one, or twice the deadline that last timed
|
|
4
|
+
// out) and the last completed run, between the platform floor and a ceiling.
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
export const probeCeilingMs=process.env.VSTATE_TEST_FAULTS==='1'&&
|
|
7
|
+
Number(process.env.VSTATE_TEST_PROBE_CEILING_MS)||60000;
|
|
8
|
+
// Supported TypeScript probes perform bounded input/toolchain hashing.
|
|
9
|
+
// Native NTFS runs have exceeded the generic 5 s cap under load, despite
|
|
10
|
+
// completing normally and with the duplicated work already removed.
|
|
11
|
+
export const probeFloorMs=qualification=>qualification==='pnpm-tsc-v1'||
|
|
12
|
+
(process.platform==='win32'&&['typescript-noemit-v1','npm-typescript-direct-v1','yarn-workspace-tsc-v1']
|
|
13
|
+
.includes(qualification))?10000:5000;
|
|
14
|
+
// REDUE's own probes, which accept --redue-deadline; a user's probe never gets it.
|
|
15
|
+
const builtIn=new Set(['typescript-contract-probe.mjs','direct-typescript-probe.mjs',
|
|
16
|
+
'yarn-workspace-typecheck-probe.mjs','pnpm-typecheck-probe.mjs'].map(name=>fileURLToPath(new URL(name,import.meta.url))));
|
|
17
|
+
export const builtInProbe=argv=>builtIn.has(argv?.[1]);
|
|
18
|
+
// A deadline read from status.json, as a whole number of milliseconds no
|
|
19
|
+
// longer than a decision could need (0 when absent or unusable).
|
|
20
|
+
export function publishedDeadline(value) {
|
|
21
|
+
return Number.isFinite(value)&&value>0?Math.min(Math.round(value),10*60*1000):0;
|
|
22
|
+
}
|
|
23
|
+
export function probeDeadline({floor,hint=0,runMs=0,ceiling=probeCeilingMs}) {
|
|
24
|
+
const finite=value=>Number.isFinite(value)&&value>0?value:0;
|
|
25
|
+
return Math.round(Math.min(ceiling,Math.max(floor,finite(hint),finite(runMs))));
|
|
26
|
+
}
|
|
27
|
+
// The hint the next probe starts from.
|
|
28
|
+
export function nextProbeHint({durationMs,deadlineMs,timedOut}) {
|
|
29
|
+
return 2*(timedOut?deadlineMs:durationMs);
|
|
30
|
+
}
|
|
31
|
+
// Inside a probe process: what is left of the deadline its caller passed with
|
|
32
|
+
// --redue-deadline=MS, or the fallback when none was passed. An inner call
|
|
33
|
+
// always ends before its parent is killed, so it never outlives the probe.
|
|
34
|
+
export function probeTimeLeft(fallback) {
|
|
35
|
+
const deadline=Number(/^--redue-deadline=(\d+)$/.exec(process.argv.find(arg=>arg.startsWith('--redue-deadline='))||'')?.[1]);
|
|
36
|
+
return deadline?Math.max(1,Math.round(deadline-performance.now()-250)):fallback;
|
|
37
|
+
}
|
package/src/probe-result.mjs
CHANGED
|
@@ -9,11 +9,15 @@ export function contextOnly(context) {
|
|
|
9
9
|
}
|
|
10
10
|
return false;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
// A layout lets a recorded run rebuild the output after a content-only edit.
|
|
13
|
+
export function probeResult(output,context,queries=null,layout=null) {
|
|
13
14
|
if(!process.argv.includes('--redue-checkpoint')){process.stdout.write(output);return;}
|
|
14
15
|
const raw=queries?JSON.stringify(queries):null;
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const queryData=raw&&Buffer.byteLength(raw)<=8*1024*1024?deflateSync(raw).toString('base64'):null;
|
|
17
|
+
let value=JSON.stringify({output,context,queryData,
|
|
18
|
+
...(layout?{layoutData:deflateSync(JSON.stringify(layout)).toString('base64')}:{})});
|
|
19
|
+
// The layout is dropped first: it only shortens the run after an edit.
|
|
20
|
+
if(Buffer.byteLength(value)>512*1024)value=JSON.stringify({output,context,queryData});
|
|
17
21
|
// Optimization metadata must not overflow the ordinary probe channel.
|
|
18
22
|
// Larger contracts keep the normal complete probe, without result reuse.
|
|
19
23
|
if(Buffer.byteLength(value)>512*1024)value=JSON.stringify({output,context,queries:null});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Shared project selection only; evidence and caller-context semantics stay in the kernel.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import {createHash} from 'node:crypto';
|
|
6
|
+
import {realObservedPath} from './path-identity.mjs';
|
|
7
|
+
import {stateIdentity,userStateBase} from './platform-state.mjs';
|
|
8
|
+
|
|
9
|
+
export function discoverConfig(cwd=process.cwd()) {
|
|
10
|
+
let dir=path.resolve(cwd);const found=[];
|
|
11
|
+
for(;;){
|
|
12
|
+
for(const name of ['redue.config.json','vstate.config.json']) {
|
|
13
|
+
const file=path.join(dir,name);
|
|
14
|
+
if(fs.existsSync(file))found.push(file);
|
|
15
|
+
}
|
|
16
|
+
if(fs.existsSync(path.join(dir,'.git'))||path.dirname(dir)===dir)break;
|
|
17
|
+
dir=path.dirname(dir);
|
|
18
|
+
}
|
|
19
|
+
if(found.length>1)throw Error('multiple REDUE configurations apply; select the intended one with --config FILE');
|
|
20
|
+
return found[0]||path.resolve(cwd,'redue.config.json');
|
|
21
|
+
}
|
|
22
|
+
export function canonicalConfig(configFile) {
|
|
23
|
+
return path.join(realObservedPath(path.dirname(configFile)),path.basename(configFile));
|
|
24
|
+
}
|
|
25
|
+
export function projectState(root,configFile) {
|
|
26
|
+
return path.join(userStateBase(process.platform,process.env,os.homedir()),'vstate',
|
|
27
|
+
`vstate-${createHash('sha256').update(stateIdentity(root)+'\0'+stateIdentity(canonicalConfig(configFile))).digest('hex').slice(0,16)}`);
|
|
28
|
+
}
|
|
29
|
+
export function readProject(configFile) {
|
|
30
|
+
const config=JSON.parse(fs.readFileSync(configFile,'utf8'));
|
|
31
|
+
if(config.schema!==1||!Array.isArray(config.checks)||!config.checks.length)
|
|
32
|
+
throw Error('config needs schema 1 and at least one check');
|
|
33
|
+
return {config,root:realObservedPath(path.resolve(path.dirname(configFile),config.root||'.'))};
|
|
34
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Python projects for `redue init`: typecheck, lint and test checks that run
|
|
2
|
+
// mypy, pyright, ty, ruff or pytest from the project's .venv under the sandboxed recipe
|
|
3
|
+
// (sandbox.mjs). A tool is proposed when it is installed in .venv/bin. Its
|
|
4
|
+
// arguments are the ones the project runs it with, read from CI workflows,
|
|
5
|
+
// the Makefile, tox, nox, a justfile and shell scripts; without one, the
|
|
6
|
+
// tool's own default (`ruff check`, `ty check`, `pyright`, `pytest`, and
|
|
7
|
+
// `mypy` with its configured files or `.`).
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import {realObservedPath} from './path-identity.mjs';
|
|
11
|
+
import {sandboxCheckInputs,sandboxQualification} from './sandbox.mjs';
|
|
12
|
+
|
|
13
|
+
const read=file=>{try{return fs.readFileSync(file,'utf8');}catch{return '';}};
|
|
14
|
+
const exists=file=>{try{fs.lstatSync(file);return true;}catch{return false;}};
|
|
15
|
+
const executable=file=>{try{fs.accessSync(file,fs.constants.X_OK);return fs.statSync(file).isFile();}catch{return false;}};
|
|
16
|
+
// Each tool, its kind, and the subcommand it runs as a check.
|
|
17
|
+
export const pythonTools=[{tool:'mypy',kind:'typecheck',sub:[]},{tool:'pyright',kind:'typecheck',sub:[]},
|
|
18
|
+
{tool:'ty',kind:'typecheck',sub:['check']},{tool:'ruff',kind:'lint',sub:['check']},{tool:'pytest',kind:'test',sub:[]}];
|
|
19
|
+
// The text of a TOML or INI section, up to the next section header.
|
|
20
|
+
const section=(text,header)=>{const at=text.split(/\r?\n/).findIndex(line=>line.trim()===header);
|
|
21
|
+
if(at<0)return null;const rest=text.split(/\r?\n/).slice(at+1);const end=rest.findIndex(line=>/^\s*\[/.test(line));
|
|
22
|
+
return (end<0?rest:rest.slice(0,end)).join('\n');};
|
|
23
|
+
// Ways a project starts a tool: through uv, poetry or pdm, `python -m`, or from .venv/bin.
|
|
24
|
+
const wrappers=[/^uv run(?: --(?:frozen|locked|no-sync|offline|quiet|all-extras|all-groups))*(?: --(?:group|extra|with|python|package) [^ ]+)* /,
|
|
25
|
+
/^poetry run /,/^pdm run /,/^python3?(?:\.\d+)? -m /,/^(?:\.\/)?\.venv\/bin\//,
|
|
26
|
+
// A variable that names the environment's bin directory: `${PREFIX}mypy`.
|
|
27
|
+
/^\$\{?[A-Za-z_]+\}?(?=[A-Za-z])/];
|
|
28
|
+
// A tool's arguments in one line of a script or workflow, or null. The line
|
|
29
|
+
// must run the tool and its subcommand plainly; arguments with shell syntax,
|
|
30
|
+
// ones that change files or install packages, and pytest's that run a set
|
|
31
|
+
// chosen from its cache (--lf, --ff, --sw, --nf), show the cache, loop
|
|
32
|
+
// (-f), rewrite snapshots or add to earlier coverage data, are not taken.
|
|
33
|
+
export function toolArgs(line,{tool,sub}){
|
|
34
|
+
let text=line.trim().replace(/^-\s+/,'').replace(/^run:\s*/,'').replace(/^[@-]+/,'');
|
|
35
|
+
text=text.split(/\s(?:&&|\|\||;|\|)\s/)[0].replace(/\s+/g,' ').trim();
|
|
36
|
+
for(let changed=true;changed;){changed=false;for(const wrapper of wrappers)if(wrapper.test(text)){text=text.replace(wrapper,'');changed=true;}}
|
|
37
|
+
const words=text.split(' ');
|
|
38
|
+
if(words[0]!==tool||JSON.stringify(words.slice(1,1+sub.length))!==JSON.stringify(sub))return null;
|
|
39
|
+
const args=words.slice(1+sub.length);
|
|
40
|
+
if(args.some(arg=>!/^[A-Za-z0-9_./:=,@+%-]+$/.test(arg)||/^--(?:fix|unsafe-fixes|write|install-types|watch|lf|last-failed|ff|failed-first|sw|sw-skip|sw-reset|stepwise|stepwise-skip|stepwise-reset|nf|new-first|cache-show|looponfail|snapshot-update|update-snapshots|inline-snapshot|cov-append)(?:=|$)/.test(arg)||arg==='-f'||arg.startsWith('..')||arg.startsWith('/')))return null;
|
|
41
|
+
return args;
|
|
42
|
+
}
|
|
43
|
+
// The places a project writes how it runs its tools, in order of preference.
|
|
44
|
+
function scriptTexts(root){
|
|
45
|
+
const texts=[];
|
|
46
|
+
const workflows=path.join(root,'.github','workflows');
|
|
47
|
+
let files=[];try{files=fs.readdirSync(workflows).filter(file=>/\.ya?ml$/.test(file)).sort();}catch{}
|
|
48
|
+
for(const file of files)texts.push(read(path.join(workflows,file)));
|
|
49
|
+
for(const file of ['Makefile','makefile','justfile','tox.ini','noxfile.py'])texts.push(read(path.join(root,file)));
|
|
50
|
+
// Shell scripts: *.sh, or a file whose #! line runs a shell (scripts/check).
|
|
51
|
+
for(const dir of ['scripts','script','bin','ci']){let names=[];try{names=fs.readdirSync(path.join(root,dir)).sort();}catch{}
|
|
52
|
+
for(const name of names){const text=read(path.join(root,dir,name)).slice(0,65536);
|
|
53
|
+
if(name.endsWith('.sh')||/^#!\S*(?:\/|env\s+)(?:ba|z|da)?sh\b/.test(text))texts.push(text);}}
|
|
54
|
+
return texts;
|
|
55
|
+
}
|
|
56
|
+
// The arguments the project runs `tool` with, or its default.
|
|
57
|
+
export function projectArgs(root,spec,texts=scriptTexts(root)){
|
|
58
|
+
for(const text of texts)for(const line of text.split(/\r?\n/)){
|
|
59
|
+
if(/^\s*#/.test(line))continue;
|
|
60
|
+
const args=toolArgs(line,spec);
|
|
61
|
+
if(args)return {args,source:'project'};
|
|
62
|
+
}
|
|
63
|
+
if(spec.tool!=='mypy')return {args:[],source:'default'};
|
|
64
|
+
// mypy without targets needs `files` in its configuration.
|
|
65
|
+
const configured=[section(read(path.join(root,'pyproject.toml')),'[tool.mypy]'),section(read(path.join(root,'mypy.ini')),'[mypy]'),
|
|
66
|
+
section(read(path.join(root,'.mypy.ini')),'[mypy]'),section(read(path.join(root,'setup.cfg')),'[mypy]')].some(text=>text&&/^\s*files\s*=/m.test(text));
|
|
67
|
+
return {args:configured?[]:['.'],source:'default'};
|
|
68
|
+
}
|
|
69
|
+
// The Python checks `init` proposes, or null when the root is not a Python
|
|
70
|
+
// project. Each row is ready only once init has run it both ways (bin/redue.mjs).
|
|
71
|
+
export function discoverPythonProject(root){
|
|
72
|
+
root=realObservedPath(root);
|
|
73
|
+
if(!['pyproject.toml','setup.cfg','setup.py','requirements.txt'].some(file=>exists(path.join(root,file))))return null;
|
|
74
|
+
const checks=[],skipped=[];
|
|
75
|
+
if(!exists(path.join(root,'.venv','pyvenv.cfg'))){
|
|
76
|
+
skipped.push({kind:'python',reason:'no .venv in the project root; REDUE runs Python tools from the project\'s .venv (uv sync, or Poetry with virtualenvs.in-project)'});
|
|
77
|
+
return {root,checks,skipped};
|
|
78
|
+
}
|
|
79
|
+
const texts=scriptTexts(root);
|
|
80
|
+
const found=pythonTools.filter(spec=>executable(path.join(root,'.venv','bin',spec.tool)));
|
|
81
|
+
for(const spec of found){
|
|
82
|
+
const {args,source}=projectArgs(root,spec,texts);
|
|
83
|
+
const several=found.filter(other=>other.kind===spec.kind).length>1;
|
|
84
|
+
checks.push({config:{name:several?`${spec.kind}:${spec.tool}`:spec.kind,kind:spec.kind,qualification:sandboxQualification,
|
|
85
|
+
command:[`.venv/bin/${spec.tool}`,...spec.sub,...args],...sandboxCheckInputs()},level:'ready',qualification:sandboxQualification,
|
|
86
|
+
reason:`sandboxed recipe: ${[spec.tool,...spec.sub,...args].join(' ')} from .venv${source==='project'?' as the project runs it':''}; the whole checkout is its input`});
|
|
87
|
+
}
|
|
88
|
+
return {root,checks,skipped};
|
|
89
|
+
}
|