@stll/oxlint-config 0.12.2 → 0.14.1
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/README.md +234 -1
- package/dist/cloud-setup-cli.cjs +6 -4
- package/dist/cloud-setup-cli.cjs.map +1 -1
- package/dist/cloud-setup-cli.mjs +3 -1
- package/dist/cloud-setup-cli.mjs.map +1 -1
- package/dist/compiler-packages.cjs +28 -0
- package/dist/compiler-packages.cjs.map +1 -0
- package/dist/compiler-packages.mjs +17 -0
- package/dist/compiler-packages.mjs.map +1 -0
- package/dist/consumer-compat-cli.cjs +1091 -0
- package/dist/consumer-compat-cli.cjs.map +1 -0
- package/dist/consumer-compat-cli.d.cts +1 -0
- package/dist/consumer-compat-cli.d.mts +1 -0
- package/dist/consumer-compat-cli.mjs +1089 -0
- package/dist/consumer-compat-cli.mjs.map +1 -0
- package/dist/publish-build-target-nuxt-helper.cjs +71 -0
- package/dist/publish-build-target-nuxt-helper.cjs.map +1 -0
- package/dist/publish-build-target-nuxt-helper.d.cts +10 -0
- package/dist/publish-build-target-nuxt-helper.d.mts +10 -0
- package/dist/publish-build-target-nuxt-helper.mjs +68 -0
- package/dist/publish-build-target-nuxt-helper.mjs.map +1 -0
- package/dist/publish-build-target-vite-dts-helper.cjs +3 -0
- package/dist/publish-build-target-vite-dts-helper.d.cts +24 -0
- package/dist/publish-build-target-vite-dts-helper.d.mts +24 -0
- package/dist/publish-build-target-vite-dts-helper.mjs +2 -0
- package/dist/publish-build-target-vite-dts-helper2.cjs +372 -0
- package/dist/publish-build-target-vite-dts-helper2.cjs.map +1 -0
- package/dist/publish-build-target-vite-dts-helper2.mjs +359 -0
- package/dist/publish-build-target-vite-dts-helper2.mjs.map +1 -0
- package/dist/publish-contract-cli.cjs +983 -0
- package/dist/publish-contract-cli.cjs.map +1 -0
- package/dist/publish-contract-cli.d.cts +1 -0
- package/dist/publish-contract-cli.d.mts +1 -0
- package/dist/publish-contract-cli.mjs +981 -0
- package/dist/publish-contract-cli.mjs.map +1 -0
- package/dist/publish-contract.cjs +431 -0
- package/dist/publish-contract.cjs.map +1 -0
- package/dist/publish-contract.mjs +396 -0
- package/dist/publish-contract.mjs.map +1 -0
- package/dist/rolldown-runtime.cjs +28 -0
- package/dist/toolchain-changed-cli.cjs +34 -0
- package/dist/toolchain-changed-cli.cjs.map +1 -0
- package/dist/toolchain-changed-cli.d.cts +1 -0
- package/dist/toolchain-changed-cli.d.mts +1 -0
- package/dist/toolchain-changed-cli.mjs +34 -0
- package/dist/toolchain-changed-cli.mjs.map +1 -0
- package/dist/toolchain-changed.cjs +1470 -0
- package/dist/toolchain-changed.cjs.map +1 -0
- package/dist/toolchain-changed.mjs +1463 -0
- package/dist/toolchain-changed.mjs.map +1 -0
- package/dist/toolchain-check-cli.cjs +2 -1
- package/dist/toolchain-check-cli.cjs.map +1 -1
- package/dist/toolchain-check-cli.mjs +2 -1
- package/dist/toolchain-check-cli.mjs.map +1 -1
- package/dist/toolchain-guard.cjs +360 -1161
- package/dist/toolchain-guard.cjs.map +1 -1
- package/dist/toolchain-guard.mjs +338 -1100
- package/dist/toolchain-guard.mjs.map +1 -1
- package/dist/toolchain-inputs.cjs +926 -0
- package/dist/toolchain-inputs.cjs.map +1 -0
- package/dist/toolchain-inputs.mjs +816 -0
- package/dist/toolchain-inputs.mjs.map +1 -0
- package/dist/toolchain-schema.cjs +702 -0
- package/dist/toolchain-schema.cjs.map +1 -0
- package/dist/toolchain-schema.mjs +580 -0
- package/dist/toolchain-schema.mjs.map +1 -0
- package/dist/typecheck-parity-cli.cjs +38 -898
- package/dist/typecheck-parity-cli.cjs.map +1 -1
- package/dist/typecheck-parity-cli.mjs +39 -899
- package/dist/typecheck-parity-cli.mjs.map +1 -1
- package/dist/typecheck-parity.cjs +905 -0
- package/dist/typecheck-parity.cjs.map +1 -0
- package/dist/typecheck-parity.mjs +894 -0
- package/dist/typecheck-parity.mjs.map +1 -0
- package/dist/typecheck-probe-cli.cjs +176 -0
- package/dist/typecheck-probe-cli.cjs.map +1 -0
- package/dist/typecheck-probe-cli.d.cts +1 -0
- package/dist/typecheck-probe-cli.d.mts +1 -0
- package/dist/typecheck-probe-cli.mjs +174 -0
- package/dist/typecheck-probe-cli.mjs.map +1 -0
- package/package.json +43 -5
- package/toolchain.json +9 -1
package/dist/toolchain-guard.cjs
CHANGED
|
@@ -1,782 +1,241 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
-
key = keys[i];
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
-
get: ((k) => from[k]).bind(null, key),
|
|
13
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
-
value: mod,
|
|
20
|
-
enumerable: true
|
|
21
|
-
}) : target, mod));
|
|
22
|
-
//#endregion
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime.cjs");
|
|
2
|
+
const require_toolchain_inputs = require("./toolchain-inputs.cjs");
|
|
3
|
+
const require_toolchain_schema = require("./toolchain-schema.cjs");
|
|
23
4
|
let node_fs = require("node:fs");
|
|
24
5
|
let node_child_process = require("node:child_process");
|
|
25
6
|
let node_path = require("node:path");
|
|
26
|
-
node_path = __toESM(node_path, 1);
|
|
7
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
27
8
|
let semver = require("semver");
|
|
9
|
+
let yaml = require("yaml");
|
|
28
10
|
let picomatch = require("picomatch");
|
|
29
|
-
picomatch = __toESM(picomatch, 1);
|
|
11
|
+
picomatch = require_rolldown_runtime.__toESM(picomatch, 1);
|
|
30
12
|
let smol_toml = require("smol-toml");
|
|
31
|
-
let yaml = require("yaml");
|
|
32
|
-
//#region src/cloud-setup-install.ts
|
|
33
|
-
const packageInstall = String.raw`
|
|
34
|
-
# Prevent package maintainer scripts from starting unmanaged system services.
|
|
35
|
-
apt_install() (
|
|
36
|
-
# EXIT runs after function locals expire; this subshell owns the trap state.
|
|
37
|
-
backup=''
|
|
38
|
-
policy_state=untouched
|
|
39
|
-
[[ ! -L /var/lock/stll-cloud-apt.lock ]] || fail 'Invalid package install lock'
|
|
40
|
-
exec 8>/var/lock/stll-cloud-apt.lock
|
|
41
|
-
flock -x -w 120 8 || fail 'Another package installation is active'
|
|
42
|
-
[[ ! -L /usr/sbin/policy-rc.d ]] || fail 'Unsupported service-start policy symlink'
|
|
43
|
-
[[ ! -e /usr/sbin/policy-rc.d || -f /usr/sbin/policy-rc.d ]] || fail 'Unsupported service-start policy file'
|
|
44
|
-
backup="$(mktemp -d)"
|
|
45
|
-
cleanup_package_policy() {
|
|
46
|
-
local status=$?
|
|
47
|
-
trap - EXIT HUP INT TERM
|
|
48
|
-
if [[ "$policy_state" == installed ]]; then
|
|
49
|
-
if [[ -f "$backup/original" ]]; then
|
|
50
|
-
if ! cp -p "$backup/original" /usr/sbin/policy-rc.d; then status=1; fi
|
|
51
|
-
else
|
|
52
|
-
if ! rm -f /usr/sbin/policy-rc.d; then status=1; fi
|
|
53
|
-
fi
|
|
54
|
-
fi
|
|
55
|
-
rm -rf -- "$backup"
|
|
56
|
-
exit "$status"
|
|
57
|
-
}
|
|
58
|
-
trap cleanup_package_policy EXIT
|
|
59
|
-
trap 'exit 129' HUP
|
|
60
|
-
trap 'exit 130' INT
|
|
61
|
-
trap 'exit 143' TERM
|
|
62
|
-
if [[ -e /usr/sbin/policy-rc.d ]]; then cp -p /usr/sbin/policy-rc.d "$backup/original"; fi
|
|
63
|
-
policy_state=installed
|
|
64
|
-
printf '%s\n' '#!/bin/sh' 'exit 101' > /usr/sbin/policy-rc.d
|
|
65
|
-
chmod 755 /usr/sbin/policy-rc.d
|
|
66
|
-
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
`;
|
|
70
|
-
const postgresSourceLine = "deb [signed-by=/usr/share/keyrings/stll-pgdg.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main";
|
|
71
|
-
const postgresSigningFingerprint = "B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8";
|
|
72
|
-
const postgresSources = String.raw`
|
|
73
|
-
pgdg_key_fingerprint() {
|
|
74
|
-
gpg --batch --show-keys --with-colons "$1" | awk -F: '$1 == "pub" { primary = 1 } $1 == "sub" { primary = 0 } $1 == "fpr" && primary { print toupper($10); primary = 0 }'
|
|
75
|
-
}
|
|
76
|
-
pgdg_sources_ready() {
|
|
77
|
-
local managed="$1" file mode records suite keyring override fingerprint selected_keyring=''
|
|
78
|
-
shift
|
|
79
|
-
PGDG_SOURCE_STATE=missing
|
|
80
|
-
[[ ! -L "$managed" ]] || fail "Managed PGDG source must not be a symlink: $managed"
|
|
81
|
-
for file in "$@"; do
|
|
82
|
-
[[ -e "$file" ]] || continue
|
|
83
|
-
[[ -f "$file" ]] || fail "APT source must be a regular file: $file"
|
|
84
|
-
case "$file" in *.sources) mode=sources ;; *) mode=list ;; esac
|
|
85
|
-
records="$(awk -v mode="$mode" '
|
|
86
|
-
function trim(value) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); return value }
|
|
87
|
-
function pgdg_uri(uri) {
|
|
88
|
-
uri = tolower(uri)
|
|
89
|
-
if (uri !~ /^[a-z][a-z0-9+.-]*:\/\//) return 0
|
|
90
|
-
sub(/^[a-z][a-z0-9+.-]*:\/\//, "", uri)
|
|
91
|
-
sub(/[\/?#].*$/, "", uri)
|
|
92
|
-
sub(/^.*@/, "", uri)
|
|
93
|
-
sub(/:.*$/, "", uri)
|
|
94
|
-
return uri == "apt.postgresql.org" || uri == "apt.postgresql.org."
|
|
95
|
-
}
|
|
96
|
-
function contains_pgdg(value, tokens, count, token_index) {
|
|
97
|
-
count = split(value, tokens, /[[:space:]]+/)
|
|
98
|
-
for (token_index = 1; token_index <= count; token_index++) if (pgdg_uri(tokens[token_index])) return 1
|
|
99
|
-
return 0
|
|
100
|
-
}
|
|
101
|
-
function unsafe_field(name) {
|
|
102
|
-
return name ~ /^(trusted|allow-insecure|allow-weak|allow-downgrade-to-insecure)$/
|
|
103
|
-
}
|
|
104
|
-
function disabled_override(value) {
|
|
105
|
-
value = tolower(trim(value))
|
|
106
|
-
gsub(/^["\047]|["\047]$/, "", value)
|
|
107
|
-
return value ~ /^(no|false|without|off|disable|0)$/
|
|
108
|
-
}
|
|
109
|
-
function stanza(override_index) {
|
|
110
|
-
override = 0
|
|
111
|
-
for (override_index = 1; override_index <= override_count; override_index++) {
|
|
112
|
-
if (!disabled_override(override_values[override_index])) override = 1
|
|
113
|
-
delete override_values[override_index]
|
|
114
|
-
}
|
|
115
|
-
if (tolower(trim(enabled)) != "no" && pgdg) print (trim(suites) == "" ? "-" : trim(suites)) "\t" (trim(keyring) == "" ? "-" : trim(keyring)) "\t" (override ? "unsafe" : "safe")
|
|
116
|
-
enabled = ""; pgdg = 0; field = ""; suites = ""; keyring = ""; override = 0; override_count = 0
|
|
117
|
-
}
|
|
118
|
-
/^[[:space:]]*#/ { if (mode == "list" || $0 ~ /^#/) next }
|
|
119
|
-
{
|
|
120
|
-
line = $0
|
|
121
|
-
if (mode == "list") {
|
|
122
|
-
sub(/[[:space:]]*#.*$/, "", line)
|
|
123
|
-
if (line !~ /^[[:space:]]*deb(-src)?[[:space:]]/ || !contains_pgdg(line)) next
|
|
124
|
-
keyring = ""; override = 0
|
|
125
|
-
if (match(line, /\[[^]]*\]/)) {
|
|
126
|
-
options = substr(line, RSTART + 1, RLENGTH - 2)
|
|
127
|
-
count = split(options, tokens, /[[:space:]]+/)
|
|
128
|
-
for (token_index = 1; token_index <= count; token_index++) {
|
|
129
|
-
if (tolower(tokens[token_index]) ~ /^signed-by=/) { keyring = tokens[token_index]; sub(/^[^=]*=/, "", keyring) }
|
|
130
|
-
option_name = tolower(tokens[token_index]); sub(/=.*/, "", option_name)
|
|
131
|
-
option_value = tokens[token_index]; sub(/^[^=]*=/, "", option_value)
|
|
132
|
-
if (unsafe_field(option_name) && !disabled_override(option_value)) override = 1
|
|
133
|
-
}
|
|
134
|
-
sub(/\[[^]]*\]/, "", line)
|
|
135
|
-
}
|
|
136
|
-
count = split(line, tokens, /[[:space:]]+/)
|
|
137
|
-
for (token_index = 1; token_index <= count; token_index++) if (pgdg_uri(tokens[token_index])) print (tokens[token_index + 1] == "" ? "-" : tokens[token_index + 1]) "\t" (keyring == "" ? "-" : keyring) "\t" (override ? "unsafe" : "safe")
|
|
138
|
-
next
|
|
139
|
-
}
|
|
140
|
-
if (line ~ /^[[:space:]]*$/) { stanza(); next }
|
|
141
|
-
if (line ~ /^[[:space:]]/) {
|
|
142
|
-
if (field == "uris" && contains_pgdg(line)) pgdg = 1
|
|
143
|
-
if (field == "enabled") enabled = enabled " " line
|
|
144
|
-
if (field == "suites") suites = suites " " line
|
|
145
|
-
if (field == "signed-by") keyring = keyring " " line
|
|
146
|
-
if (unsafe_field(field)) override_values[override_count] = override_values[override_count] " " line
|
|
147
|
-
next
|
|
148
|
-
}
|
|
149
|
-
field = tolower(line)
|
|
150
|
-
sub(/:.*/, "", field)
|
|
151
|
-
value = line
|
|
152
|
-
sub(/^[^:]*:[[:space:]]*/, "", value)
|
|
153
|
-
if (field == "uris" && contains_pgdg(value)) pgdg = 1
|
|
154
|
-
if (field == "enabled") enabled = value
|
|
155
|
-
if (field == "suites") suites = value
|
|
156
|
-
if (field == "signed-by") keyring = value
|
|
157
|
-
if (unsafe_field(field)) override_values[++override_count] = value
|
|
158
|
-
}
|
|
159
|
-
END { if (mode == "sources") stanza() }
|
|
160
|
-
' "$file")" || fail "Cannot inspect APT source: $file"
|
|
161
|
-
if [[ "$file" == "$managed" && -z "$records" ]]; then
|
|
162
|
-
fail "Managed PGDG source has no active PGDG entry: $file"
|
|
163
|
-
fi
|
|
164
|
-
while IFS=$'\t' read -r suite keyring override; do
|
|
165
|
-
[[ -n "$suite" ]] || continue
|
|
166
|
-
[[ "$override" == safe ]] || fail "PGDG source enables an unsafe trust override: $file"
|
|
167
|
-
[[ "$suite" == noble-pgdg && "$keyring" == /* && ! "$keyring" =~ [[:space:],] ]] || fail "PGDG source requires noble-pgdg and one Signed-By keyring: $file"
|
|
168
|
-
[[ -z "$selected_keyring" || "$selected_keyring" == "$keyring" ]] || fail "Conflicting PGDG Signed-By keyrings: $file"
|
|
169
|
-
fingerprint="$(pgdg_key_fingerprint "$keyring")" || fail "Cannot verify PGDG Signed-By keyring: $file"
|
|
170
|
-
[[ "$fingerprint" == @PGDG_FINGERPRINT@ ]] || fail "PGDG Signed-By fingerprint mismatch: $file"
|
|
171
|
-
selected_keyring="$keyring"
|
|
172
|
-
PGDG_SOURCE_STATE=reuse
|
|
173
|
-
done <<< "$records"
|
|
174
|
-
done
|
|
175
|
-
}
|
|
176
|
-
`;
|
|
177
|
-
const postgresInstall = String.raw`
|
|
178
|
-
postgres_ready() {
|
|
179
|
-
local binary
|
|
180
|
-
for binary in postgres initdb pg_ctl psql createdb; do
|
|
181
|
-
[[ -x "/usr/lib/postgresql/@POSTGRES@/bin/$binary" ]] || return 1
|
|
182
|
-
done
|
|
183
|
-
/usr/lib/postgresql/@POSTGRES@/bin/postgres --version | grep -Eq '^postgres \(PostgreSQL\) @POSTGRES@\.'
|
|
184
|
-
}
|
|
185
|
-
if ! postgres_ready; then
|
|
186
|
-
pgdg_sources_ready /etc/apt/sources.list.d/stll-pgdg.list /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources
|
|
187
|
-
if [[ "$PGDG_SOURCE_STATE" == missing ]]; then
|
|
188
|
-
local key key_fingerprint
|
|
189
|
-
key="$(mktemp)"
|
|
190
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 https://www.postgresql.org/media/keys/ACCC4CF8.asc -o "$key"
|
|
191
|
-
key_fingerprint="$(pgdg_key_fingerprint "$key")"
|
|
192
|
-
[[ "$key_fingerprint" == @PGDG_FINGERPRINT@ ]] || fail 'PostgreSQL signing key mismatch'
|
|
193
|
-
install -d -m 755 /usr/share/keyrings
|
|
194
|
-
install -m 644 "$key" /usr/share/keyrings/stll-pgdg.asc
|
|
195
|
-
rm -- "$key"
|
|
196
|
-
printf '%s\n' '@PGDG_SOURCE@' > /etc/apt/sources.list.d/stll-pgdg.list
|
|
197
|
-
fi
|
|
198
|
-
apt-get update
|
|
199
|
-
apt_install postgresql-@POSTGRES@ postgresql-client-@POSTGRES@
|
|
200
|
-
fi
|
|
201
|
-
postgres_ready || fail 'PostgreSQL toolchain is incomplete or differs from the policy major'
|
|
202
|
-
`;
|
|
203
|
-
const valkeyInstall = String.raw`
|
|
204
|
-
if ! command -v valkey-server >/dev/null || [[ "$(valkey-server --version | sed -nE 's/.*v=([0-9.]+).*/\1/p')" != '@VALKEY@' ]] || ! command -v valkey-cli >/dev/null || [[ "$(valkey-cli --version)" != 'valkey-cli @VALKEY@' ]]; then
|
|
205
|
-
local scratch server_url server_sha tools_url tools_sha package
|
|
206
|
-
scratch="$(mktemp -d)"
|
|
207
|
-
chmod 755 "$scratch"
|
|
208
|
-
case "$ARCH" in
|
|
209
|
-
amd64) server_url='@AMD64_SERVER_URL@'; server_sha='@AMD64_SERVER_SHA@'; tools_url='@AMD64_TOOLS_URL@'; tools_sha='@AMD64_TOOLS_SHA@' ;;
|
|
210
|
-
arm64) server_url='@ARM64_SERVER_URL@'; server_sha='@ARM64_SERVER_SHA@'; tools_url='@ARM64_TOOLS_URL@'; tools_sha='@ARM64_TOOLS_SHA@' ;;
|
|
211
|
-
*) fail 'Unsupported Valkey architecture' ;;
|
|
212
|
-
esac
|
|
213
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "$server_url" -o "$scratch/valkey-server.deb"
|
|
214
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "$tools_url" -o "$scratch/valkey-tools.deb"
|
|
215
|
-
(cd "$scratch"; printf '%s %s\n' "$server_sha" valkey-server.deb "$tools_sha" valkey-tools.deb | sha256sum --strict -c -)
|
|
216
|
-
for package in valkey-server valkey-tools; do
|
|
217
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Package)" == "$package" ]] || fail 'Valkey artifact package mismatch'
|
|
218
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Version)" == '@VALKEY@-1.noble' ]] || fail 'Valkey artifact version mismatch'
|
|
219
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Architecture)" == "$ARCH" ]] || fail 'Valkey artifact architecture mismatch'
|
|
220
|
-
done
|
|
221
|
-
apt-get update
|
|
222
|
-
apt_install "$scratch/valkey-server.deb" "$scratch/valkey-tools.deb"
|
|
223
|
-
rm -rf -- "$scratch"
|
|
224
|
-
fi
|
|
225
|
-
[[ "$(valkey-server --version | sed -nE 's/.*v=([0-9.]+).*/\1/p')" == '@VALKEY@' ]] || fail 'Valkey version differs from policy'
|
|
226
|
-
[[ "$(valkey-cli --version)" == 'valkey-cli @VALKEY@' ]] || fail 'Valkey client version differs from policy'
|
|
227
|
-
`;
|
|
228
|
-
const state = String.raw`
|
|
229
|
-
service_account_ready() {
|
|
230
|
-
local uid gid shell
|
|
231
|
-
uid="$(id -u stll-cloud)" || fail 'Service user is unavailable; run install'
|
|
232
|
-
gid="$(id -g stll-cloud)"
|
|
233
|
-
shell="$(getent passwd stll-cloud | cut -d: -f7)"
|
|
234
|
-
[[ "$uid" =~ ^[1-9][0-9]*$ && "$gid" =~ ^[1-9][0-9]*$ && "$shell" == /usr/sbin/nologin ]] || fail 'Service user must be unprivileged with no login shell'
|
|
235
|
-
}
|
|
236
|
-
prepare_state() {
|
|
237
|
-
service_account_ready
|
|
238
|
-
[[ ! -L /var/lib/stll-cloud ]] || fail 'Service state root must not be a symlink'
|
|
239
|
-
if [[ -e /var/lib/stll-cloud ]]; then
|
|
240
|
-
[[ -d /var/lib/stll-cloud && "$(stat -c '%u:%a' /var/lib/stll-cloud)" == 0:755 ]] || fail 'Invalid service state root'
|
|
241
|
-
else
|
|
242
|
-
install -d -m 755 -o root -g root /var/lib/stll-cloud
|
|
243
|
-
fi
|
|
244
|
-
STATE="/var/lib/stll-cloud/$(printf '%s' "$REPO_ROOT" | sha256sum | cut -d' ' -f1)"
|
|
245
|
-
[[ ! -L "$STATE" ]] || fail 'Service state must not be a symlink'
|
|
246
|
-
if [[ -e "$STATE" ]]; then
|
|
247
|
-
[[ -d "$STATE" && "$(stat -c '%u:%a' "$STATE")" == 0:710 && "$(stat -c '%g' "$STATE")" == "$(id -g stll-cloud)" ]] || fail 'Invalid service state ownership'
|
|
248
|
-
else
|
|
249
|
-
install -d -m 710 -o root -g stll-cloud "$STATE"
|
|
250
|
-
fi
|
|
251
|
-
[[ ! -L "$STATE/lock" ]] || fail 'Service lock must not be a symlink'
|
|
252
|
-
exec 9>"$STATE/lock"
|
|
253
|
-
flock -x -w 10 9 || fail 'Another setup process owns the service state'
|
|
254
|
-
}
|
|
255
|
-
`;
|
|
256
|
-
const renderCloudServiceInstall = ({ services, postgres, valkey, valkeyArtifacts }) => {
|
|
257
|
-
const parts = services.map((service) => {
|
|
258
|
-
switch (service) {
|
|
259
|
-
case "postgres": return postgresInstall.replaceAll("@POSTGRES@", postgres).replaceAll("@PGDG_SOURCE@", postgresSourceLine).replaceAll("@PGDG_FINGERPRINT@", postgresSigningFingerprint);
|
|
260
|
-
case "valkey": {
|
|
261
|
-
const replacements = /* @__PURE__ */ new Map([
|
|
262
|
-
["@VALKEY@", valkey],
|
|
263
|
-
["@AMD64_SERVER_URL@", valkeyArtifacts.amd64.server.url],
|
|
264
|
-
["@AMD64_SERVER_SHA@", valkeyArtifacts.amd64.server.sha256],
|
|
265
|
-
["@AMD64_TOOLS_URL@", valkeyArtifacts.amd64.tools.url],
|
|
266
|
-
["@AMD64_TOOLS_SHA@", valkeyArtifacts.amd64.tools.sha256],
|
|
267
|
-
["@ARM64_SERVER_URL@", valkeyArtifacts.arm64.server.url],
|
|
268
|
-
["@ARM64_SERVER_SHA@", valkeyArtifacts.arm64.server.sha256],
|
|
269
|
-
["@ARM64_TOOLS_URL@", valkeyArtifacts.arm64.tools.url],
|
|
270
|
-
["@ARM64_TOOLS_SHA@", valkeyArtifacts.arm64.tools.sha256]
|
|
271
|
-
]);
|
|
272
|
-
return valkeyInstall.replace(/@[A-Z0-9_]+@/g, (token) => {
|
|
273
|
-
const value = replacements.get(token);
|
|
274
|
-
if (value === void 0) throw new Error(`Missing Valkey artifact field: ${token}`);
|
|
275
|
-
return value;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
default: throw new Error(`Unsupported service: ${String(service)}`);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
const user = services.length === 0 ? "" : " if ! id stll-cloud >/dev/null 2>&1; then useradd --system --user-group --no-create-home --shell /usr/sbin/nologin stll-cloud; fi\n service_account_ready\n";
|
|
282
|
-
return `${packageInstall}\n${services.includes("postgres") ? postgresSources.replaceAll("@PGDG_FINGERPRINT@", postgresSigningFingerprint) : ""}\n${services.length === 0 ? "prepare_state() { :; }\n" : state}\ninstall_services() {\n${user}${parts.join("")}\n :\n}\n`;
|
|
283
|
-
};
|
|
284
13
|
//#endregion
|
|
285
|
-
//#region src/
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
14
|
+
//#region src/toolchain-consumer-checks.ts
|
|
15
|
+
const consumerRunnerVersion = "0.14.1";
|
|
16
|
+
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
17
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
19
|
+
const code = character.charCodeAt(0);
|
|
20
|
+
return code < 32 || code === 127;
|
|
21
|
+
}) && !/^[A-Za-z]:/.test(value) && !node_path.default.posix.isAbsolute(value) && (value === "." || value.split("/").every((part) => part !== "" && part !== "." && part !== ".."));
|
|
22
|
+
const packageDirectories = (value) => Array.isArray(value) && value.length > 0 && value.every(repositoryDirectory) && new Set(value).size === value.length;
|
|
23
|
+
const cronFields = [
|
|
24
|
+
{
|
|
25
|
+
minimum: 0,
|
|
26
|
+
maximum: 59
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
minimum: 0,
|
|
30
|
+
maximum: 23
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
minimum: 1,
|
|
34
|
+
maximum: 31
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
minimum: 1,
|
|
38
|
+
maximum: 12,
|
|
39
|
+
names: Object.fromEntries([
|
|
40
|
+
"JAN",
|
|
41
|
+
"FEB",
|
|
42
|
+
"MAR",
|
|
43
|
+
"APR",
|
|
44
|
+
"MAY",
|
|
45
|
+
"JUN",
|
|
46
|
+
"JUL",
|
|
47
|
+
"AUG",
|
|
48
|
+
"SEP",
|
|
49
|
+
"OCT",
|
|
50
|
+
"NOV",
|
|
51
|
+
"DEC"
|
|
52
|
+
].map((name, index) => [name, index + 1]))
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
minimum: 0,
|
|
56
|
+
maximum: 6,
|
|
57
|
+
names: Object.fromEntries([
|
|
58
|
+
"SUN",
|
|
59
|
+
"MON",
|
|
60
|
+
"TUE",
|
|
61
|
+
"WED",
|
|
62
|
+
"THU",
|
|
63
|
+
"FRI",
|
|
64
|
+
"SAT"
|
|
65
|
+
].map((name, index) => [name, index]))
|
|
310
66
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
install: cloudInstallCommand,
|
|
317
|
-
envFile
|
|
67
|
+
];
|
|
68
|
+
const cronFieldValues = (text, field) => {
|
|
69
|
+
const value = (token) => {
|
|
70
|
+
const number = /^\d+$/.test(token) ? Number(token) : field.names?.[token.toUpperCase()];
|
|
71
|
+
return number !== void 0 && Number.isSafeInteger(number) && number >= field.minimum && number <= field.maximum ? number : void 0;
|
|
318
72
|
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
service_password() {
|
|
340
|
-
local file="$1"
|
|
341
|
-
service_file "$file" 0
|
|
342
|
-
if [[ ! -e "$file" ]]; then
|
|
343
|
-
(umask 077; openssl rand -hex 32 > "$file")
|
|
344
|
-
root chown root:root "$file"
|
|
345
|
-
fi
|
|
346
|
-
[[ "$(cat "$file")" =~ ^[0-9a-f]{64}$ ]] || fail "Invalid service credential state"
|
|
347
|
-
}
|
|
348
|
-
service_process_matches() {
|
|
349
|
-
local pid="$1" executable="$2" owner actual expected
|
|
350
|
-
[[ "$pid" =~ ^[1-9][0-9]*$ && -d "/proc/$pid" ]] || return 1
|
|
351
|
-
owner="$(stat -c '%u' "/proc/$pid" 2>/dev/null)" || return 2
|
|
352
|
-
[[ "$owner" =~ ^[0-9]+$ ]] || return 2
|
|
353
|
-
[[ "$owner" == "$SERVICE_UID" ]] || return 1
|
|
354
|
-
actual="$(readlink -f "/proc/$pid/exe" 2>/dev/null)" || return 2
|
|
355
|
-
expected="$(readlink -f "$executable")" || return 2
|
|
356
|
-
[[ -n "$actual" && -n "$expected" && "$actual" != *' (deleted)' ]] || return 2
|
|
357
|
-
[[ "$actual" == "$expected" ]]
|
|
358
|
-
}
|
|
359
|
-
service_process() {
|
|
360
|
-
service_process_matches "$1" "$2" || fail "Managed service process identity differs from the recorded state"
|
|
361
|
-
}
|
|
362
|
-
service_prune_pid() {
|
|
363
|
-
local file="$1" executable="$2" service="$3" pid identity
|
|
364
|
-
[[ -e "$file" ]] || return 0
|
|
365
|
-
pid="$(head -n 1 "$file")"
|
|
366
|
-
# PostgreSQL encodes a standalone backend with a negative PID.
|
|
367
|
-
if [[ "$service" == postgres ]]; then pid="$(printf '%s' "$pid" | sed 's/^-//')"; fi
|
|
368
|
-
if service_process_matches "$pid" "$executable"; then return 0; else identity=$?; fi
|
|
369
|
-
[[ "$identity" != 2 ]] || fail "Cannot verify recorded live service process; retaining its PID file"
|
|
370
|
-
# Let PostgreSQL check orphan shared memory before removing a dead-PID lock.
|
|
371
|
-
if [[ "$service" == postgres && "$pid" =~ ^[1-9][0-9]*$ && ! -d "/proc/$pid" ]]; then return 0; fi
|
|
372
|
-
if [[ "$service" == postgres ]]; then
|
|
373
|
-
[[ "$pid" =~ ^[1-9][0-9]*$ && "$SERVICE_UID" != 0 ]] || fail "Cannot safely recover PostgreSQL PID state"
|
|
374
|
-
# The root controller is not our nonroot postgres. Preserve the remaining
|
|
375
|
-
# lock rows so PostgreSQL's EPERM/ancestor path still checks orphan memory.
|
|
376
|
-
root sed -i "1s/.*/$$/" "$file"
|
|
377
|
-
return 0
|
|
378
|
-
fi
|
|
379
|
-
# Restored state can name an unrelated live process; never signal that PID.
|
|
380
|
-
root rm -- "$file"
|
|
381
|
-
}
|
|
382
|
-
`;
|
|
383
|
-
const postgresStart = String.raw`
|
|
384
|
-
local pg_bin='/usr/lib/postgresql/@POSTGRES@/bin' pg_data="$STATE/postgres" pg_password pg_details pg_pid pg_version
|
|
385
|
-
service_password "$STATE/postgres.password"
|
|
386
|
-
pg_password="$(cat "$STATE/postgres.password")"
|
|
387
|
-
service_directory "$pg_data"
|
|
388
|
-
for file in PG_VERSION postmaster.pid postgresql.conf pg_hba.conf pg_ident.conf postgresql.auto.conf; do
|
|
389
|
-
service_file "$pg_data/$file" "$SERVICE_UID"
|
|
390
|
-
done
|
|
391
|
-
if [[ ! -e "$pg_data/PG_VERSION" ]]; then
|
|
392
|
-
[[ -z "$(ls -A "$pg_data")" ]] || fail "PostgreSQL data directory is not initialized"
|
|
393
|
-
service_file "$STATE/postgres.init-password" "$SERVICE_UID"
|
|
394
|
-
(umask 077; printf '%s\n' "$pg_password" > "$STATE/postgres.init-password")
|
|
395
|
-
root chown stll-cloud:stll-cloud "$STATE/postgres.init-password"
|
|
396
|
-
service_run env PGHOST=127.0.0.1 PGHOSTADDR=127.0.0.1 "$pg_bin/initdb" -D "$pg_data" --username=stll_cloud --auth-host=scram-sha-256 --auth-local=scram-sha-256 --pwfile="$STATE/postgres.init-password" --encoding=UTF8 --locale=C --set=unix_socket_directories= >/dev/null || fail "PostgreSQL initialization failed"
|
|
397
|
-
root rm "$STATE/postgres.init-password"
|
|
398
|
-
fi
|
|
399
|
-
[[ -f "$pg_data/PG_VERSION" && "$(cat "$pg_data/PG_VERSION")" == '@POSTGRES@' ]] || fail "PostgreSQL data has a different major version"
|
|
400
|
-
service_prune_pid "$pg_data/postmaster.pid" "$pg_bin/postgres" postgres
|
|
401
|
-
if ! service_run env PGHOST=127.0.0.1 PGHOSTADDR=127.0.0.1 "$pg_bin/pg_ctl" -D "$pg_data" status >/dev/null 2>&1; then
|
|
402
|
-
service_run env PGHOST=127.0.0.1 PGHOSTADDR=127.0.0.1 "$pg_bin/pg_ctl" -D "$pg_data" -l "$pg_data/cloud.log" -o "-h 127.0.0.1 -p 55432 -c unix_socket_directories=" -t 10 -w start >/dev/null || fail "PostgreSQL start failed; port 55432 must be available"
|
|
403
|
-
fi
|
|
404
|
-
pg_details="$(env -u PGSERVICE -u PGSERVICEFILE -u PGOPTIONS PGHOSTADDR=127.0.0.1 PGPASSWORD="$pg_password" PGCONNECT_TIMEOUT=2 "$pg_bin/psql" -X -h 127.0.0.1 -p 55432 -U stll_cloud -d postgres -At -v ON_ERROR_STOP=1 -c "SELECT current_setting('data_directory'), current_setting('listen_addresses'), current_setting('port'), current_setting('server_version_num'), current_setting('unix_socket_directories')")" || fail "PostgreSQL authenticated readiness failed"
|
|
405
|
-
pg_version="$(printf '%s\n' "$pg_details" | cut -d '|' -f4)"
|
|
406
|
-
[[ "$pg_details" == "$pg_data|127.0.0.1|55432|$pg_version|" && "$pg_version" =~ ^[0-9]{6}$ ]] || fail "PostgreSQL endpoint does not match managed data and listener"
|
|
407
|
-
(( pg_version / 10000 == @POSTGRES@ )) || fail "PostgreSQL endpoint has a different major version"
|
|
408
|
-
pg_pid="$(head -n 1 "$pg_data/postmaster.pid")"
|
|
409
|
-
service_process "$pg_pid" "$pg_bin/postgres"
|
|
410
|
-
[[ "$(env -u PGSERVICE -u PGSERVICEFILE -u PGOPTIONS PGHOSTADDR=127.0.0.1 PGPASSWORD="$pg_password" PGCONNECT_TIMEOUT=2 "$pg_bin/psql" -X -h 127.0.0.1 -p 55432 -U stll_cloud -d postgres -At -v ON_ERROR_STOP=1 -c "SELECT count(*) FROM pg_database WHERE datname = 'cloud_test'")" == 1 ]] || env -u PGSERVICE -u PGSERVICEFILE -u PGOPTIONS PGHOSTADDR=127.0.0.1 PGPASSWORD="$pg_password" PGCONNECT_TIMEOUT=2 "$pg_bin/createdb" -h 127.0.0.1 -p 55432 -U stll_cloud cloud_test || fail "PostgreSQL test database creation failed"
|
|
411
|
-
DATABASE_URL="postgresql://stll_cloud:$pg_password@127.0.0.1:55432/cloud_test"
|
|
412
|
-
`;
|
|
413
|
-
const valkeyStart = String.raw`
|
|
414
|
-
local vk_data="$STATE/valkey" vk_password vk_config vk_info vk_pid vk_expected ready=0
|
|
415
|
-
service_password "$STATE/valkey.password"
|
|
416
|
-
vk_password="$(cat "$STATE/valkey.password")"
|
|
417
|
-
service_directory "$vk_data"
|
|
418
|
-
vk_config="$vk_data/valkey.conf"
|
|
419
|
-
service_file "$vk_config" "$SERVICE_UID"
|
|
420
|
-
for file in valkey.pid valkey.log dump.rdb; do
|
|
421
|
-
[[ ! -L "$vk_data/$file" ]] || fail "Valkey state must not contain symbolic links"
|
|
422
|
-
if [[ -e "$vk_data/$file" ]]; then
|
|
423
|
-
[[ -f "$vk_data/$file" && "$(stat -c '%u' "$vk_data/$file")" == "$SERVICE_UID" ]] || fail "Invalid Valkey state file"
|
|
424
|
-
fi
|
|
425
|
-
done
|
|
426
|
-
vk_expected="$(printf '%s\n' 'bind 127.0.0.1' 'port 56379' 'protected-mode yes' 'daemonize yes' "requirepass $vk_password" "dir $vk_data" "pidfile $vk_data/valkey.pid" "logfile $vk_data/valkey.log" 'save ""' 'appendonly no')"
|
|
427
|
-
if [[ ! -e "$vk_config" ]]; then
|
|
428
|
-
(umask 077; printf '%s\n' "$vk_expected" > "$vk_config")
|
|
429
|
-
root chown stll-cloud:stll-cloud "$vk_config"
|
|
430
|
-
fi
|
|
431
|
-
[[ "$(cat "$vk_config")" == "$vk_expected" ]] || fail "Valkey configuration differs from managed settings"
|
|
432
|
-
service_prune_pid "$vk_data/valkey.pid" "$(command -v valkey-server)" valkey
|
|
433
|
-
if [[ ! -e "$vk_data/valkey.pid" ]]; then
|
|
434
|
-
service_run valkey-server "$vk_config" || fail "Valkey start failed; port 56379 must be available"
|
|
435
|
-
fi
|
|
436
|
-
for ((attempt=0; attempt<20; attempt++)); do
|
|
437
|
-
if vk_info="$(VALKEYCLI_AUTH="$vk_password" timeout 0.2 valkey-cli -h 127.0.0.1 -p 56379 --raw INFO server 2>/dev/null)"; then
|
|
438
|
-
if [[ "$vk_info" == *$'valkey_version:@VALKEY@\r\n'* && "$vk_info" == *"config_file:$vk_config"$'\r\n'* ]]; then ready=1; break; fi
|
|
439
|
-
fi
|
|
440
|
-
sleep 0.25
|
|
441
|
-
done
|
|
442
|
-
[[ "$ready" == 1 ]] || fail "Valkey authenticated readiness or managed version/configuration check failed"
|
|
443
|
-
[[ -f "$vk_data/valkey.pid" ]] || fail "Valkey managed process identifier is unavailable"
|
|
444
|
-
vk_pid="$(cat "$vk_data/valkey.pid")"
|
|
445
|
-
service_process "$vk_pid" "$(command -v valkey-server)"
|
|
446
|
-
[[ "$vk_info" == *"process_id:$vk_pid"$'\r\n'* ]] || fail "Valkey endpoint has a different process identifier"
|
|
447
|
-
[[ "$(VALKEYCLI_AUTH="$vk_password" timeout 2 valkey-cli -h 127.0.0.1 -p 56379 --raw CONFIG GET bind)" == $'bind\n127.0.0.1' ]] || fail "Valkey endpoint must bind only loopback"
|
|
448
|
-
[[ "$(VALKEYCLI_AUTH="$vk_password" timeout 2 valkey-cli -h 127.0.0.1 -p 56379 --raw CONFIG GET dir)" == "$(printf 'dir\n%s' "$vk_data")" ]] || fail "Valkey endpoint has a different data directory"
|
|
449
|
-
REDIS_URL="redis://:$vk_password@127.0.0.1:56379/0"
|
|
450
|
-
`;
|
|
451
|
-
/** Render only declared services; installation and environment-file ownership stay with the caller. */
|
|
452
|
-
const renderCloudServiceStart = ({ services, postgres, valkey }) => {
|
|
453
|
-
if (!/^[1-9][0-9]*$/.test(postgres) || !/^\d+\.\d+\.\d+$/.test(valkey)) throw new Error("Invalid cloud service runtime policy");
|
|
454
|
-
if (services.length === 0) return "start_services() { :; }\n";
|
|
455
|
-
const bodies = services.map((service) => {
|
|
456
|
-
switch (service) {
|
|
457
|
-
case "postgres": return postgresStart.replaceAll("@POSTGRES@", postgres);
|
|
458
|
-
case "valkey": return valkeyStart.replaceAll("@VALKEY@", valkey);
|
|
459
|
-
default: throw new Error(`Unsupported cloud service: ${String(service)}`);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
return `${common}\nstart_services() {\n local SERVICE_UID file attempt\n SERVICE_UID="$(id -u stll-cloud)"\n [[ -d "$STATE" && ! -L "$STATE" && "$(stat -c '%u' "$STATE")" == 0 ]] || fail "Invalid cloud service state directory"\n${bodies.join("")}\n}\n`;
|
|
463
|
-
};
|
|
464
|
-
//#endregion
|
|
465
|
-
//#region src/toolchain-node.ts
|
|
466
|
-
const numericComponent = "(?:0|[1-9]\\d*)";
|
|
467
|
-
const policyPattern = new RegExp(`^${numericComponent}\\.x$`);
|
|
468
|
-
const selectorPattern = new RegExp(`^(${numericComponent})(?:\\.(?:x|${numericComponent}(?:\\.${numericComponent})?))?$`);
|
|
469
|
-
/** A Node policy selects the complete stable release series of one major. */
|
|
470
|
-
const nodePolicyValid = (value) => policyPattern.test(value) && (0, semver.validRange)(value) !== null;
|
|
471
|
-
/** Static selectors may narrow that series, but cannot float across majors. */
|
|
472
|
-
const nodeSelectorMatches = (value, policy) => {
|
|
473
|
-
if (!nodePolicyValid(policy) || typeof value !== "string") return false;
|
|
474
|
-
const major = selectorPattern.exec(value)?.[1];
|
|
475
|
-
return major !== void 0 && major === policy.slice(0, -2) && (0, semver.validRange)(value) !== null;
|
|
476
|
-
};
|
|
477
|
-
/** Engines must support every stable release in the approved major series. */
|
|
478
|
-
const nodeSupportRangeMatches = (policy, range) => {
|
|
479
|
-
if (!nodePolicyValid(policy)) return false;
|
|
480
|
-
try {
|
|
481
|
-
return (0, semver.subset)(policy, range);
|
|
482
|
-
} catch {
|
|
483
|
-
return false;
|
|
73
|
+
const values = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const part of text.split(",")) {
|
|
75
|
+
const split = part.split("/");
|
|
76
|
+
if (split.length > 2) return void 0;
|
|
77
|
+
const [base, stepText] = split;
|
|
78
|
+
const step = stepText === void 0 ? 1 : Number(stepText);
|
|
79
|
+
if (stepText !== void 0 && !/^\d+$/.test(stepText)) return void 0;
|
|
80
|
+
if (!Number.isSafeInteger(step) || step <= 0 || base === void 0) return void 0;
|
|
81
|
+
const bounds = base.split("-");
|
|
82
|
+
if (bounds.length > 2) return void 0;
|
|
83
|
+
const lower = bounds.at(0);
|
|
84
|
+
if (lower === void 0) return void 0;
|
|
85
|
+
const start = base === "*" ? field.minimum : value(lower);
|
|
86
|
+
const upper = bounds.at(1);
|
|
87
|
+
let end = start;
|
|
88
|
+
if (base === "*" || bounds.length === 1 && stepText !== void 0) end = field.maximum;
|
|
89
|
+
else if (upper !== void 0) end = value(upper);
|
|
90
|
+
if (start === void 0 || end === void 0 || start > end) return void 0;
|
|
91
|
+
for (let selected = start; selected <= end; selected += step) values.add(selected);
|
|
484
92
|
}
|
|
93
|
+
return values;
|
|
485
94
|
};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
# Generated by stll-cloud-setup. Regenerate with stll-cloud-setup.
|
|
492
|
-
# Networked installation and offline service startup are separate lifecycle steps.
|
|
493
|
-
# Cached filesystems may not preserve processes; run start at every session start.
|
|
494
|
-
# Supported platform: Ubuntu 24.04 (noble), amd64/arm64, root or passwordless sudo.
|
|
495
|
-
set -euo pipefail
|
|
496
|
-
|
|
497
|
-
usage() { echo 'Usage: cloud-setup.sh install|start' >&2; exit 2; }
|
|
498
|
-
[[ "$#" == 1 ]] || usage
|
|
499
|
-
case "$1" in install|start) MODE="$1" ;; *) usage ;; esac
|
|
500
|
-
fail() { echo "cloud-setup: $*" >&2; exit 1; }
|
|
501
|
-
trap 'echo "cloud-setup: command failed at line $LINENO" >&2' ERR
|
|
502
|
-
[[ -f /etc/os-release ]] || fail 'Ubuntu 24.04 is required'
|
|
503
|
-
. /etc/os-release
|
|
504
|
-
[[ "$ID" == ubuntu && "$VERSION_ID" == 24.04 && "$VERSION_CODENAME" == noble ]] || fail 'Only Ubuntu 24.04 (noble) is supported'
|
|
505
|
-
SCRIPT="$(realpath -- "$0")"
|
|
506
|
-
REPO_ROOT="$(cd -- "$(dirname -- "$SCRIPT")/.." && pwd -P)"
|
|
507
|
-
[[ "$SCRIPT" == "$REPO_ROOT/.agents/cloud-setup.sh" ]] || fail 'Run the generated repository script'
|
|
508
|
-
if [[ "$(id -u)" != 0 ]]; then
|
|
509
|
-
command -v sudo >/dev/null && sudo -n true || fail 'Root or passwordless sudo is required'
|
|
510
|
-
exec sudo -n -- bash "$SCRIPT" "$MODE"
|
|
511
|
-
fi
|
|
512
|
-
OUTPUT_UID="$(printenv SUDO_UID || id -u)"
|
|
513
|
-
[[ "$OUTPUT_UID" =~ ^[0-9]+$ ]] || fail 'Invalid invoking user'
|
|
514
|
-
OUTPUT_GID="$(getent passwd "$OUTPUT_UID" | cut -d: -f4)"
|
|
515
|
-
[[ "$OUTPUT_GID" =~ ^[0-9]+$ ]] || fail 'Invoking user is unavailable'
|
|
516
|
-
OUTPUT_USER="$(getent passwd "$OUTPUT_UID" | cut -d: -f1)"
|
|
517
|
-
OUTPUT_HOME="$(getent passwd "$OUTPUT_UID" | cut -d: -f6)"
|
|
518
|
-
[[ -n "$OUTPUT_USER" && "$OUTPUT_HOME" == /* && -d "$OUTPUT_HOME" ]] || fail 'Invoking user requires an existing home directory'
|
|
519
|
-
root() { "$@"; }
|
|
520
|
-
service_run() { runuser -u stll-cloud -- "$@" 9>&-; }
|
|
521
|
-
BUN_VERSION='@BUN@'
|
|
522
|
-
NODE_VERSION='@NODE@'
|
|
523
|
-
NODE_DIR="/opt/stll-cloud/node/$NODE_VERSION"
|
|
524
|
-
BUN_DIR="/opt/stll-cloud/bun/$BUN_VERSION"
|
|
525
|
-
export PATH="$NODE_DIR/bin:$BUN_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
526
|
-
BUN_CACHE="/var/cache/stll-cloud/bun/$OUTPUT_UID"
|
|
527
|
-
@ENV_ASSIGNMENTS@
|
|
528
|
-
[[ -f "$REPO_ROOT/.node-version" && ! -L "$REPO_ROOT/.node-version" ]] || fail 'A regular root .node-version is required'
|
|
529
|
-
[[ "$(awk 'NF { if (++count > 1 || NF != 1) exit 1; value=$1 } END { if (count != 1) exit 1; print value }' "$REPO_ROOT/.node-version")" == "$NODE_VERSION" ]] || fail 'Node selector changed; regenerate cloud-setup.sh'
|
|
530
|
-
cd -- "$REPO_ROOT"
|
|
531
|
-
case "$(uname -m)" in
|
|
532
|
-
x86_64) ARCH=amd64; NODE_ARCH=x64; BUN_ARCH=x64 ;;
|
|
533
|
-
aarch64|arm64) ARCH=arm64; NODE_ARCH=arm64; BUN_ARCH=aarch64 ;;
|
|
534
|
-
*) fail 'Only amd64 and arm64 are supported' ;;
|
|
535
|
-
esac
|
|
536
|
-
|
|
537
|
-
runtime_ready() {
|
|
538
|
-
[[ -x "$NODE_DIR/bin/node" && "$($NODE_DIR/bin/node --version)" == "v$NODE_VERSION" ]] || fail 'Pinned Node is unavailable; run install'
|
|
539
|
-
[[ -x "$BUN_DIR/bun" && "$($BUN_DIR/bun --version)" == "$BUN_VERSION" ]] || fail 'Pinned Bun is unavailable; run install'
|
|
540
|
-
}
|
|
541
|
-
install_runtimes() {
|
|
542
|
-
if ! command -v curl >/dev/null || ! command -v unzip >/dev/null || ! command -v xz >/dev/null || ! command -v openssl >/dev/null || ! command -v gpg >/dev/null; then
|
|
543
|
-
apt-get update
|
|
544
|
-
DEBIAN_FRONTEND=noninteractive apt_install ca-certificates curl unzip xz-utils openssl gnupg util-linux
|
|
545
|
-
fi
|
|
546
|
-
local scratch node_archive bun_archive bun_folder
|
|
547
|
-
scratch="$(mktemp -d)"
|
|
548
|
-
trap 'rm -rf -- "$scratch"' RETURN
|
|
549
|
-
if [[ ! -x "$NODE_DIR/bin/node" ]] || [[ "$($NODE_DIR/bin/node --version)" != "v$NODE_VERSION" ]]; then
|
|
550
|
-
node_archive="node-v$NODE_VERSION-linux-$NODE_ARCH.tar.xz"
|
|
551
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "https://nodejs.org/dist/v$NODE_VERSION/$node_archive" -o "$scratch/$node_archive"
|
|
552
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt" -o "$scratch/node-sums"
|
|
553
|
-
(cd "$scratch"; awk -v name="$node_archive" '$2 == name { print; count++ } END { if (count != 1) exit 1 }' node-sums | sha256sum --strict -c -)
|
|
554
|
-
install -d -m 755 "$NODE_DIR"
|
|
555
|
-
tar -xJf "$scratch/$node_archive" --strip-components=1 -C "$NODE_DIR"
|
|
556
|
-
fi
|
|
557
|
-
if [[ ! -x "$BUN_DIR/bun" ]] || [[ "$($BUN_DIR/bun --version)" != "$BUN_VERSION" ]]; then
|
|
558
|
-
bun_folder="bun-linux-$BUN_ARCH"
|
|
559
|
-
bun_archive="$bun_folder.zip"
|
|
560
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION/$bun_archive" -o "$scratch/$bun_archive"
|
|
561
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION/SHASUMS256.txt" -o "$scratch/bun-sums"
|
|
562
|
-
(cd "$scratch"; awk -v name="$bun_archive" '$2 == name || $2 == "*" name { print $1 " " name; count++ } END { if (count != 1) exit 1 }' bun-sums | sha256sum --strict -c -)
|
|
563
|
-
unzip -q "$scratch/$bun_archive" -d "$scratch/bun"
|
|
564
|
-
install -d -m 755 "$BUN_DIR"
|
|
565
|
-
install -m 755 "$scratch/bun/$bun_folder/bun" "$BUN_DIR/bun"
|
|
566
|
-
fi
|
|
567
|
-
runtime_ready
|
|
568
|
-
ln -sfnT -- "$NODE_DIR/bin/node" /usr/local/bin/node
|
|
569
|
-
ln -sfnT -- "$NODE_DIR/bin/npm" /usr/local/bin/npm
|
|
570
|
-
ln -sfnT -- "$NODE_DIR/bin/npx" /usr/local/bin/npx
|
|
571
|
-
ln -sfnT -- "$BUN_DIR/bun" /usr/local/bin/bun
|
|
572
|
-
rm -rf -- "$scratch"
|
|
573
|
-
trap - RETURN
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
dependency_install() {
|
|
577
|
-
local directory
|
|
578
|
-
for directory in /var/cache/stll-cloud /var/cache/stll-cloud/bun; do
|
|
579
|
-
[[ ! -L "$directory" ]] || fail 'Dependency cache parent must not be a symlink'
|
|
580
|
-
if [[ -e "$directory" ]]; then
|
|
581
|
-
[[ -d "$directory" && "$(stat -c '%u' "$directory")" == 0 ]] || fail 'Dependency cache parent must be root-owned'
|
|
582
|
-
fi
|
|
583
|
-
install -d -m 755 -o root -g root "$directory"
|
|
584
|
-
done
|
|
585
|
-
[[ ! -L "$BUN_CACHE" ]] || fail 'Dependency cache must not be a symlink'
|
|
586
|
-
if [[ -e "$BUN_CACHE" ]]; then
|
|
587
|
-
[[ -d "$BUN_CACHE" && "$(stat -c '%u' "$BUN_CACHE")" == "$OUTPUT_UID" ]] || fail 'Dependency cache ownership differs from the invoking user'
|
|
588
|
-
fi
|
|
589
|
-
install -d -m 700 -o "$OUTPUT_UID" -g "$OUTPUT_GID" "$BUN_CACHE"
|
|
590
|
-
runuser -u "$OUTPUT_USER" -- env HOME="$OUTPUT_HOME" BUN_INSTALL_CACHE_DIR="$BUN_CACHE" PATH="$PATH" "$BUN_DIR/bun" install --frozen-lockfile
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
@SERVICE_INSTALL@
|
|
594
|
-
@SERVICE_START@
|
|
595
|
-
|
|
596
|
-
@ENV_FUNCTIONS@
|
|
597
|
-
|
|
598
|
-
if [[ "$MODE" == install ]]; then
|
|
599
|
-
install_runtimes
|
|
600
|
-
install_services
|
|
601
|
-
dependency_install
|
|
602
|
-
exit 0
|
|
603
|
-
fi
|
|
604
|
-
runtime_ready
|
|
605
|
-
@ENV_VALIDATE@
|
|
606
|
-
prepare_state
|
|
607
|
-
start_services
|
|
608
|
-
@ENV_WRITE@
|
|
609
|
-
`;
|
|
610
|
-
const environmentFunctions = String.raw`
|
|
611
|
-
validate_environment() {
|
|
612
|
-
local part target="$REPO_ROOT" env_path="$REPO_ROOT/$ENV_FILE" first_line temporary
|
|
613
|
-
while IFS= read -r part; do
|
|
614
|
-
target="$target/$part"
|
|
615
|
-
[[ ! -L "$target" ]] || fail 'Environment path must not contain symbolic links'
|
|
616
|
-
done < <(printf '%s\n' "$ENV_FILE" | tr '/' '\n')
|
|
617
|
-
[[ -d "$(dirname -- "$env_path")" ]] || fail 'Environment parent directory is missing'
|
|
618
|
-
if [[ -e "$env_path" ]]; then
|
|
619
|
-
[[ -f "$env_path" ]] || fail 'Environment target must be a regular file'
|
|
620
|
-
IFS= read -r first_line < "$env_path" || fail 'Refusing to overwrite an unowned environment file'
|
|
621
|
-
[[ "$first_line" == "$ENV_MARKER" ]] || fail 'Refusing to overwrite an unowned environment file'
|
|
622
|
-
fi
|
|
623
|
-
}
|
|
624
|
-
write_environment() {
|
|
625
|
-
validate_environment
|
|
626
|
-
local env_path="$REPO_ROOT/$ENV_FILE" temporary
|
|
627
|
-
temporary="$(mktemp "$(dirname -- "$env_path")/.stll-cloud-env.XXXXXX")"
|
|
628
|
-
chmod 600 "$temporary"
|
|
629
|
-
{
|
|
630
|
-
printf '%s\n' "$ENV_MARKER" 'NODE_ENV=test'
|
|
631
|
-
if [[ -n "$DATABASE_URL" ]]; then printf 'DATABASE_URL=%s\n' "$DATABASE_URL"; fi
|
|
632
|
-
if [[ -n "$REDIS_URL" ]]; then printf 'REDIS_URL=%s\n' "$REDIS_URL"; fi
|
|
633
|
-
} > "$temporary"
|
|
634
|
-
chown "$OUTPUT_UID:$OUTPUT_GID" "$temporary"
|
|
635
|
-
mv -f -- "$temporary" "$env_path"
|
|
636
|
-
}
|
|
637
|
-
`;
|
|
638
|
-
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
639
|
-
const generateCloudSetup = ({ policy, cloud, nodeVersion }) => {
|
|
640
|
-
const selected = parseCloudSetup(cloud);
|
|
641
|
-
if (selected === void 0) throw new Error("cloud declaration is required");
|
|
642
|
-
const exactNode = nodeVersion.trim();
|
|
643
|
-
if ((0, semver.valid)(exactNode) !== exactNode || !nodeSelectorMatches(exactNode, policy.node)) throw new Error("cloud requires a tracked exact .node-version within the shared Node series");
|
|
644
|
-
const services = cloudServices.filter((service) => selected.services.includes(service));
|
|
645
|
-
const replacements = /* @__PURE__ */ new Map([
|
|
646
|
-
["@BUN@", policy.bun],
|
|
647
|
-
["@NODE@", exactNode],
|
|
648
|
-
["@ENV_ASSIGNMENTS@", selected.envFile === void 0 ? "" : `ENV_FILE=${shellQuote(selected.envFile)}\nENV_MARKER=${shellQuote(cloudEnvironmentMarker)}`],
|
|
649
|
-
["@ENV_FUNCTIONS@", selected.envFile === void 0 ? "" : environmentFunctions],
|
|
650
|
-
["@ENV_VALIDATE@", selected.envFile === void 0 ? "" : "DATABASE_URL=''\nREDIS_URL=''\nvalidate_environment"],
|
|
651
|
-
["@ENV_WRITE@", selected.envFile === void 0 ? "" : "write_environment"],
|
|
652
|
-
["@SERVICE_INSTALL@", renderCloudServiceInstall({
|
|
653
|
-
services,
|
|
654
|
-
postgres: policy.postgres,
|
|
655
|
-
valkey: policy.valkey,
|
|
656
|
-
valkeyArtifacts: policy.valkeyArtifacts
|
|
657
|
-
})],
|
|
658
|
-
["@SERVICE_START@", renderCloudServiceStart({
|
|
659
|
-
services,
|
|
660
|
-
postgres: policy.postgres,
|
|
661
|
-
valkey: policy.valkey
|
|
662
|
-
})]
|
|
663
|
-
]);
|
|
664
|
-
return script.replace(/@[A-Z0-9_]+@/g, (token) => {
|
|
665
|
-
const value = replacements.get(token);
|
|
666
|
-
if (value === void 0) throw new Error(`Missing cloud setup field: ${token}`);
|
|
667
|
-
return value;
|
|
95
|
+
const validConsumerCron = (text) => {
|
|
96
|
+
const fields = text.trim().split(/\s+/);
|
|
97
|
+
return fields.length === cronFields.length && cronFields.every((field, index) => {
|
|
98
|
+
const fieldText = fields.at(index);
|
|
99
|
+
return fieldText !== void 0 && cronFieldValues(fieldText, field) !== void 0;
|
|
668
100
|
});
|
|
669
101
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
"template",
|
|
682
|
-
"spec"
|
|
683
|
-
],
|
|
684
|
-
DaemonSet: [
|
|
685
|
-
"spec",
|
|
686
|
-
"template",
|
|
687
|
-
"spec"
|
|
688
|
-
],
|
|
689
|
-
StatefulSet: [
|
|
690
|
-
"spec",
|
|
691
|
-
"template",
|
|
692
|
-
"spec"
|
|
693
|
-
],
|
|
694
|
-
ReplicaSet: [
|
|
695
|
-
"spec",
|
|
696
|
-
"template",
|
|
697
|
-
"spec"
|
|
698
|
-
],
|
|
699
|
-
ReplicationController: [
|
|
700
|
-
"spec",
|
|
701
|
-
"template",
|
|
702
|
-
"spec"
|
|
703
|
-
],
|
|
704
|
-
Job: [
|
|
705
|
-
"spec",
|
|
706
|
-
"template",
|
|
707
|
-
"spec"
|
|
708
|
-
],
|
|
709
|
-
CronJob: [
|
|
710
|
-
"spec",
|
|
711
|
-
"jobTemplate",
|
|
712
|
-
"spec",
|
|
713
|
-
"template",
|
|
714
|
-
"spec"
|
|
715
|
-
]
|
|
716
|
-
};
|
|
717
|
-
/** Shared semantic image enumeration for runtime checks and update-root generation. */
|
|
718
|
-
const containerDocumentImages = (document, file) => {
|
|
719
|
-
if (!record$7(document)) return void 0;
|
|
720
|
-
const services = document["services"];
|
|
721
|
-
if (isComposeDefinitionPath(file) && record$7(services)) {
|
|
722
|
-
const images = [];
|
|
723
|
-
for (const [name, service] of Object.entries(services)) if (record$7(service) && service["image"] !== void 0) images.push({
|
|
724
|
-
image: service["image"],
|
|
725
|
-
path: [
|
|
726
|
-
"services",
|
|
727
|
-
name,
|
|
728
|
-
"image"
|
|
729
|
-
]
|
|
102
|
+
const nightlySchedule = (value) => {
|
|
103
|
+
if (!record$6(value)) return false;
|
|
104
|
+
const schedule = value["schedule"];
|
|
105
|
+
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
106
|
+
const coveredDays = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const entry of schedule) {
|
|
108
|
+
if (!record$6(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
109
|
+
const fields = entry["cron"].trim().split(/\s+/);
|
|
110
|
+
const selections = cronFields.map((field, index) => {
|
|
111
|
+
const text = fields.at(index);
|
|
112
|
+
return text === void 0 ? void 0 : cronFieldValues(text, field);
|
|
730
113
|
});
|
|
114
|
+
if (selections.at(2)?.size !== 31 || selections.at(3)?.size !== 12) continue;
|
|
115
|
+
for (const day of selections.at(4) ?? []) coveredDays.add(day);
|
|
116
|
+
}
|
|
117
|
+
return coveredDays.size === 7;
|
|
118
|
+
};
|
|
119
|
+
const parseConsumerChecks = (input) => {
|
|
120
|
+
if (input === void 0) return [];
|
|
121
|
+
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
122
|
+
const targets = /* @__PURE__ */ new Set();
|
|
123
|
+
return input.map((entry) => {
|
|
124
|
+
if (!record$6(entry) || Object.keys(entry).some((key) => ![
|
|
125
|
+
"workflow",
|
|
126
|
+
"job",
|
|
127
|
+
"packages",
|
|
128
|
+
"toolingVersion",
|
|
129
|
+
"fixturePath"
|
|
130
|
+
].includes(key)) || typeof entry["workflow"] !== "string" || require_toolchain_inputs.githubAutomationFileKind(entry["workflow"]) !== "workflow" || !/^\.github\/workflows\/[^/]+\.ya?ml$/.test(entry["workflow"]) || typeof entry["job"] !== "string" || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(entry["job"]) || !packageDirectories(entry["packages"]) || typeof entry["toolingVersion"] !== "string" || !/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(entry["toolingVersion"]) || !repositoryDirectory(entry["fixturePath"])) throw new Error("consumerChecks requires canonical workflow, job, unique package directories, exact toolingVersion and repository-relative fixturePath");
|
|
131
|
+
const target = `${entry["workflow"]}:${entry["job"]}`;
|
|
132
|
+
if (targets.has(target)) throw new Error("consumerChecks targets must be unique");
|
|
133
|
+
targets.add(target);
|
|
731
134
|
return {
|
|
732
|
-
|
|
733
|
-
|
|
135
|
+
workflow: entry["workflow"],
|
|
136
|
+
job: entry["job"],
|
|
137
|
+
packages: entry["packages"],
|
|
138
|
+
toolingVersion: entry["toolingVersion"],
|
|
139
|
+
fixturePath: entry["fixturePath"]
|
|
734
140
|
};
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
let spec = resource;
|
|
754
|
-
for (const key of specPath) spec = record$7(spec) ? spec[key] : void 0;
|
|
755
|
-
if (!record$7(spec)) return;
|
|
756
|
-
for (const key of [
|
|
757
|
-
"containers",
|
|
758
|
-
"initContainers",
|
|
759
|
-
"ephemeralContainers"
|
|
760
|
-
]) {
|
|
761
|
-
const containers = spec[key];
|
|
762
|
-
if (!Array.isArray(containers)) continue;
|
|
763
|
-
for (const [index, container] of containers.entries()) if (record$7(container) && container["image"] !== void 0) images.push({
|
|
764
|
-
image: container["image"],
|
|
765
|
-
path: [
|
|
766
|
-
...prefix,
|
|
767
|
-
...specPath,
|
|
768
|
-
key,
|
|
769
|
-
index,
|
|
770
|
-
"image"
|
|
771
|
-
]
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
/** Consumer checks are scoped declarations, never general runtime exceptions. */
|
|
144
|
+
const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
145
|
+
const diagnostics = [];
|
|
146
|
+
const exercised = /* @__PURE__ */ new Set();
|
|
147
|
+
const add = (message) => diagnostics.push({
|
|
148
|
+
rule: "configuration",
|
|
149
|
+
path: "stll-toolchain.json",
|
|
150
|
+
line: 1,
|
|
151
|
+
message
|
|
152
|
+
});
|
|
153
|
+
for (const [workflow, text] of Object.entries(files)) {
|
|
154
|
+
if (require_toolchain_inputs.githubAutomationFileKind(workflow) !== "workflow") continue;
|
|
155
|
+
try {
|
|
156
|
+
const document = (0, yaml.parseDocument)(text, {
|
|
157
|
+
uniqueKeys: true,
|
|
158
|
+
merge: true
|
|
772
159
|
});
|
|
160
|
+
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
161
|
+
const source = document.toJS({ maxAliasCount: 100 });
|
|
162
|
+
if (!record$6(source) || !record$6(source["jobs"])) continue;
|
|
163
|
+
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
164
|
+
if (!record$6(job) || typeof job["uses"] !== "string") continue;
|
|
165
|
+
const [workflowPath, ref] = job["uses"].split("@");
|
|
166
|
+
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
167
|
+
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
168
|
+
if (declaration === void 0) {
|
|
169
|
+
add(`${workflow}:${jobName} requires a consumerChecks declaration`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (!nightlySchedule(source["on"])) throw new Error(`${workflow} consumer checks require a nonempty valid on.schedule covering every day`);
|
|
173
|
+
if ("if" in job || "needs" in job || "strategy" in job) throw new Error(`${workflow}:${jobName} consumer checks require a stand-alone unconditional reusable job without if or needs or strategy`);
|
|
174
|
+
exercised.add(declaration);
|
|
175
|
+
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
176
|
+
if (job["uses"].split("@").length !== 2 || ref === void 0 || !/^[a-f0-9]{40}$/.test(ref) || approved === void 0 || ref !== approved.sha || "steps" in job || "runs-on" in job) throw new Error(`${workflow}:${jobName} must invoke the approved immutable consumer workflow as a reusable job`);
|
|
177
|
+
const inputs = job["with"];
|
|
178
|
+
if (!record$6(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
179
|
+
const names = /* @__PURE__ */ new Set();
|
|
180
|
+
for (const key of Object.keys(inputs)) {
|
|
181
|
+
const normalized = key.toLowerCase();
|
|
182
|
+
if (names.has(normalized) || ![
|
|
183
|
+
"packages",
|
|
184
|
+
"consumer-node",
|
|
185
|
+
"tooling-version",
|
|
186
|
+
"fixture-path"
|
|
187
|
+
].includes(normalized) || normalized !== key) throw new Error(`${workflow}:${jobName} requires unique canonical consumer input names`);
|
|
188
|
+
names.add(normalized);
|
|
189
|
+
}
|
|
190
|
+
if (inputs["consumer-node"] !== policy.consumerNode) throw new Error(`${workflow}:${jobName} consumer-node must be ${policy.consumerNode}`);
|
|
191
|
+
if (declaration.toolingVersion !== consumerRunnerVersion) throw new Error(`${workflow}:${jobName} toolingVersion must match installed @stll/oxlint-config ${consumerRunnerVersion}`);
|
|
192
|
+
if (inputs["tooling-version"] !== declaration.toolingVersion) throw new Error(`${workflow}:${jobName} tooling-version must match consumerChecks.toolingVersion`);
|
|
193
|
+
if (inputs["fixture-path"] !== declaration.fixturePath) throw new Error(`${workflow}:${jobName} fixture-path must match consumerChecks.fixturePath`);
|
|
194
|
+
const fixtureManifest = node_path.default.posix.join(declaration.fixturePath, "consumer-compat.json");
|
|
195
|
+
const fixtureSource = files[fixtureManifest];
|
|
196
|
+
if (fixtureSource === void 0) throw new Error(`consumerChecks fixture configuration must be tracked: ${fixtureManifest}`);
|
|
197
|
+
const fixtures = require_toolchain_schema.parseConsumerFixtures(JSON.parse(fixtureSource));
|
|
198
|
+
require_toolchain_schema.assertConsumerFixtureSelection({
|
|
199
|
+
selected: declaration.packages,
|
|
200
|
+
fixtures
|
|
201
|
+
});
|
|
202
|
+
const rawPackages = inputs["packages"];
|
|
203
|
+
if (typeof rawPackages !== "string") throw new Error(`${workflow}:${jobName} packages must be a static JSON array`);
|
|
204
|
+
const selected = JSON.parse(rawPackages);
|
|
205
|
+
if (!packageDirectories(selected) || selected.length !== declaration.packages.length || !selected.every((directory) => declaration.packages.includes(directory))) throw new Error(`${workflow}:${jobName} packages must exactly match consumerChecks`);
|
|
206
|
+
const workspacePackages = require_toolchain_schema.discoverConsumerPackages(files);
|
|
207
|
+
const packagesByDirectory = new Map([...workspacePackages.values()].map((entry) => [entry.directory, entry]));
|
|
208
|
+
for (const directory of declaration.packages) {
|
|
209
|
+
const manifestPath = node_path.default.posix.join(directory, "package.json");
|
|
210
|
+
if (files[manifestPath] === void 0) throw new Error(`consumerChecks package manifest must be tracked: ${manifestPath}`);
|
|
211
|
+
const selectedPackage = packagesByDirectory.get(directory);
|
|
212
|
+
if (selectedPackage === void 0) throw new Error(`consumerChecks package must be a discovered workspace member: ${manifestPath}`);
|
|
213
|
+
const fixture = fixtures.find((candidate) => candidate.package === directory);
|
|
214
|
+
if (fixture === void 0) throw new Error(`missing declared consumer fixture: ${directory}`);
|
|
215
|
+
require_toolchain_schema.assertConsumerFixtureKind({
|
|
216
|
+
fixture,
|
|
217
|
+
pkg: selectedPackage
|
|
218
|
+
});
|
|
219
|
+
const manifest = selectedPackage.manifest;
|
|
220
|
+
if (manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "") throw new Error(`consumerChecks requires a named published package: ${manifestPath}`);
|
|
221
|
+
const closure = require_toolchain_schema.consumerPackageClosure({
|
|
222
|
+
selected: selectedPackage,
|
|
223
|
+
packages: workspacePackages,
|
|
224
|
+
files
|
|
225
|
+
});
|
|
226
|
+
for (const member of closure.values()) require_toolchain_schema.assertConsumerNodeSupport({
|
|
227
|
+
manifest: member.manifest,
|
|
228
|
+
node: policy.consumerNode,
|
|
229
|
+
label: node_path.default.posix.join(member.directory, "package.json")
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (declarations.some((entry) => entry.workflow === workflow) || text.toLowerCase().includes("stella/.github/.github/workflows/package-consumer-compat.yml")) add(error instanceof Error ? error.message : String(error));
|
|
773
235
|
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
return
|
|
777
|
-
ecosystem: "docker",
|
|
778
|
-
images
|
|
779
|
-
};
|
|
236
|
+
}
|
|
237
|
+
for (const declaration of declarations) if (!exercised.has(declaration)) add(`${declaration.workflow}:${declaration.job} consumerChecks declaration has no matching reusable invocation`);
|
|
238
|
+
return diagnostics;
|
|
780
239
|
};
|
|
781
240
|
//#endregion
|
|
782
241
|
//#region src/toolchain-images.ts
|
|
@@ -801,69 +260,9 @@ const canonicalDockerRuntime = (image) => {
|
|
|
801
260
|
return (registry === void 0 ? image : image.slice(registry.length + 1)).replace(/^library\//i, "");
|
|
802
261
|
};
|
|
803
262
|
//#endregion
|
|
804
|
-
//#region src/toolchain-inputs.ts
|
|
805
|
-
const excludedInputPath = (file) => file === "" || file.split("/").some((part) => part === "node_modules" || part === "vendor");
|
|
806
|
-
const javascriptDependencyLockfiles = [
|
|
807
|
-
"bun.lock",
|
|
808
|
-
"package-lock.json",
|
|
809
|
-
"yarn.lock",
|
|
810
|
-
"pnpm-lock.yaml"
|
|
811
|
-
];
|
|
812
|
-
/** Match project, environment, local, and fragment filenames recognized by mise. */
|
|
813
|
-
const isMiseConfigPath = (file) => !excludedInputPath(file) && (/(?:^|\/)\.?mise(?:\.[^./][^/]*)?\.toml$/.test(file) || /(?:^|\/)(?:\.?mise|\.config\/mise)\/config(?:\.[^./][^/]*)?\.toml$/.test(file) || /(?:^|\/)(?:\.?mise|\.config\/mise)\/conf\.d\/[^./][^/]*\.toml$/.test(file));
|
|
814
|
-
/** GitHub action metadata may live at the repository root or in any directory. */
|
|
815
|
-
const githubAutomationFileKind = (file) => {
|
|
816
|
-
if (file === "") return void 0;
|
|
817
|
-
if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(file)) return "workflow";
|
|
818
|
-
if (/(?:^|\/)action\.ya?ml$/.test(file)) return "action";
|
|
819
|
-
};
|
|
820
|
-
/** Dependabot updates workflows and repository-root action metadata. */
|
|
821
|
-
const isDependabotGithubActionsPath = (file) => {
|
|
822
|
-
const kind = githubAutomationFileKind(file);
|
|
823
|
-
return kind === "workflow" || kind === "action" && !file.includes("/");
|
|
824
|
-
};
|
|
825
|
-
/** Use one filename discriminator for reader selection and Python ecosystem decisions. */
|
|
826
|
-
const pythonDependencyManifestKind = (file) => {
|
|
827
|
-
if (excludedInputPath(file)) return void 0;
|
|
828
|
-
const name = file.split("/").at(-1);
|
|
829
|
-
if (name === "pyproject.toml") return "project";
|
|
830
|
-
if (name === "Pipfile") return "pipfile";
|
|
831
|
-
if (name === "Pipfile.lock") return "pipfile-lock";
|
|
832
|
-
if (name === "setup.py" || name === "setup.cfg") return "setup";
|
|
833
|
-
if (name !== void 0 && /^requirements[^/]*\.(?:txt|in)$/.test(name)) return "requirements";
|
|
834
|
-
};
|
|
835
|
-
const isPythonDependencyManifest = (file) => pythonDependencyManifestKind(file) !== void 0;
|
|
836
|
-
/** Docker definition names use dot-delimited variants, excluding source and documentation. */
|
|
837
|
-
const isDockerDefinitionPath = (file) => {
|
|
838
|
-
if (excludedInputPath(file)) return false;
|
|
839
|
-
const name = file.split("/").at(-1) ?? "";
|
|
840
|
-
if (/\.(?:[cm]?[jt]sx?|mdx?|rst|txt|html?|py|rs|go|java|sh|c|cc|cpp|cxx|h|hpp|cs|rb|php|swift|kt|css|scss)$/i.test(name)) return false;
|
|
841
|
-
return /^(?:[^./]+\.)*(?:dockerfile|containerfile)(?:\.[^./]+)*$/i.test(name);
|
|
842
|
-
};
|
|
843
|
-
/** Lockfiles establish ecosystem presence without reading their dependency graphs. */
|
|
844
|
-
const toolchainInputKind = (file) => {
|
|
845
|
-
if (file === ".agents/cloud-setup.sh") return "config";
|
|
846
|
-
if (githubAutomationFileKind(file) !== void 0) return "config";
|
|
847
|
-
if (excludedInputPath(file)) return void 0;
|
|
848
|
-
if (pythonDependencyManifestKind(file) === "pipfile-lock" || javascriptDependencyLockfiles.some((name) => file.split("/").at(-1) === name) || /(?:^|\/)uv\.lock$/.test(file)) return "presence";
|
|
849
|
-
if (githubAutomationFileKind(file) !== void 0 || isMiseConfigPath(file) || isPythonDependencyManifest(file) || isDockerDefinitionPath(file) || isComposeDefinitionPath(file) || isKubernetesDefinitionPath(file) || /(?:^|\/)(?:package\.json|pnpm-workspace\.yaml|stll-toolchain\.json|\.bun-version|\.node-version|\.nvmrc|\.python-version|rust-toolchain(?:\.toml)?|\.tool-versions|\.?mise\.toml|uv\.toml|\.uv\.toml|\.github\/dependabot\.ya?ml|Cargo\.toml)$/.test(file)) return "config";
|
|
850
|
-
};
|
|
851
|
-
//#endregion
|
|
852
|
-
//#region src/toolchain-workspaces.ts
|
|
853
|
-
/** Match manifest directories with the same positive and negative workspace patterns. */
|
|
854
|
-
const workspaceContains = ({ directory, file, patterns, rootMembership }) => {
|
|
855
|
-
const relative = node_path.default.posix.relative(directory, node_path.default.posix.dirname(file));
|
|
856
|
-
if (relative === "" && rootMembership === "implicit") return true;
|
|
857
|
-
if (relative === ".." || relative.startsWith("../")) return false;
|
|
858
|
-
if (!Array.isArray(patterns)) return false;
|
|
859
|
-
const normalize = (pattern) => node_path.default.posix.normalize(pattern);
|
|
860
|
-
const selected = patterns.filter((value) => typeof value === "string");
|
|
861
|
-
return (0, picomatch.default)(selected.filter((pattern) => !pattern.startsWith("!")).map(normalize))(relative) && !(0, picomatch.default)(selected.filter((pattern) => pattern.startsWith("!")).map((pattern) => normalize(pattern.slice(1))))(relative);
|
|
862
|
-
};
|
|
863
|
-
//#endregion
|
|
864
263
|
//#region src/toolchain-dependabot.ts
|
|
865
264
|
const dependabotRules = ["dependabot-policy"];
|
|
866
|
-
const record$
|
|
265
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
867
266
|
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
868
267
|
const sameStrings = (value, expected) => {
|
|
869
268
|
if (!strings(value) || value.length !== expected.length) return false;
|
|
@@ -885,8 +284,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
885
284
|
case "cargo": return parsed["workspace"];
|
|
886
285
|
case "uv": {
|
|
887
286
|
const tool = parsed["tool"];
|
|
888
|
-
const uv = record$
|
|
889
|
-
return record$
|
|
287
|
+
const uv = record$5(tool) ? tool["uv"] : void 0;
|
|
288
|
+
return record$5(uv) ? uv["workspace"] : void 0;
|
|
890
289
|
}
|
|
891
290
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
892
291
|
}
|
|
@@ -906,7 +305,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
906
305
|
const document = (0, yaml.parseDocument)(text);
|
|
907
306
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
908
307
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
909
|
-
if (!record$
|
|
308
|
+
if (!record$5(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
910
309
|
const patterns = parsed["packages"] ?? [];
|
|
911
310
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
912
311
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${node_path.default.posix.normalize(pattern.slice(1))}` : node_path.default.posix.normalize(pattern)));
|
|
@@ -918,13 +317,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
918
317
|
}
|
|
919
318
|
try {
|
|
920
319
|
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : (0, smol_toml.parse)(files[file] ?? "");
|
|
921
|
-
if (!record$
|
|
320
|
+
if (!record$5(parsed)) continue;
|
|
922
321
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
923
322
|
const value = workspaceTable(parsed, ecosystem);
|
|
924
|
-
const patterns = record$
|
|
925
|
-
const members = ecosystem === "cargo" && record$
|
|
323
|
+
const patterns = record$5(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
324
|
+
const members = ecosystem === "cargo" && record$5(value) && patterns === void 0 ? [] : patterns;
|
|
926
325
|
if (strings(members)) {
|
|
927
|
-
const excluded = ecosystem !== "npm" && record$
|
|
326
|
+
const excluded = ecosystem !== "npm" && record$5(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
928
327
|
workspaces.set(node_path.default.posix.dirname(file), [...members, ...excluded]);
|
|
929
328
|
}
|
|
930
329
|
} catch {}
|
|
@@ -933,7 +332,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
933
332
|
const declaredMember = (root, patterns, directory) => {
|
|
934
333
|
const relative = node_path.default.posix.relative(root, directory);
|
|
935
334
|
if (relative === "" || relative === ".." || relative.startsWith("../")) return false;
|
|
936
|
-
return workspaceContains({
|
|
335
|
+
return require_toolchain_inputs.workspaceContains({
|
|
937
336
|
directory: root,
|
|
938
337
|
file: node_path.default.posix.join(directory, "package.json"),
|
|
939
338
|
patterns,
|
|
@@ -944,7 +343,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
944
343
|
if (ecosystem === "npm") {
|
|
945
344
|
for (const file of manifests) {
|
|
946
345
|
const directory = node_path.default.posix.dirname(file);
|
|
947
|
-
if (javascriptDependencyLockfiles.some((name) => files[node_path.default.posix.join(directory, name)] !== void 0)) installable.add(directory);
|
|
346
|
+
if (require_toolchain_inputs.javascriptDependencyLockfiles.some((name) => files[node_path.default.posix.join(directory, name)] !== void 0)) installable.add(directory);
|
|
948
347
|
}
|
|
949
348
|
let changed = true;
|
|
950
349
|
while (changed) {
|
|
@@ -975,11 +374,11 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
975
374
|
for (const directory of memberDirectories) try {
|
|
976
375
|
const parsed = (0, smol_toml.parse)(files[node_path.default.posix.join(directory, "Cargo.toml")] ?? "");
|
|
977
376
|
const workspace = (0, smol_toml.parse)(files[node_path.default.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
978
|
-
const inherited = record$
|
|
377
|
+
const inherited = record$5(workspace) ? workspace["dependencies"] : void 0;
|
|
979
378
|
const dependencyOwners = [parsed];
|
|
980
379
|
const targets = parsed["target"];
|
|
981
|
-
if (record$
|
|
982
|
-
for (const target of Object.values(targets)) if (record$
|
|
380
|
+
if (record$5(targets)) {
|
|
381
|
+
for (const target of Object.values(targets)) if (record$5(target)) dependencyOwners.push(target);
|
|
983
382
|
}
|
|
984
383
|
for (const owner of dependencyOwners) for (const key of [
|
|
985
384
|
"dependencies",
|
|
@@ -987,12 +386,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
987
386
|
"build-dependencies"
|
|
988
387
|
]) {
|
|
989
388
|
const dependencies = owner[key];
|
|
990
|
-
if (!record$
|
|
389
|
+
if (!record$5(dependencies)) continue;
|
|
991
390
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
992
|
-
if (!record$
|
|
391
|
+
if (!record$5(dependency)) continue;
|
|
993
392
|
const isInherited = dependency["workspace"] === true;
|
|
994
|
-
const resolved = isInherited && record$
|
|
995
|
-
if (!record$
|
|
393
|
+
const resolved = isInherited && record$5(inherited) ? inherited[name] : dependency;
|
|
394
|
+
if (!record$5(resolved) || typeof resolved["path"] !== "string") continue;
|
|
996
395
|
if (node_path.default.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
997
396
|
const target = node_path.default.posix.normalize(node_path.default.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
998
397
|
const relative = node_path.default.posix.relative(root, target);
|
|
@@ -1020,19 +419,19 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
1020
419
|
if (files[node_path.default.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
1021
420
|
try {
|
|
1022
421
|
const manifest = JSON.parse(files[node_path.default.posix.join(root, "package.json")] ?? "{}");
|
|
1023
|
-
if (record$
|
|
422
|
+
if (record$5(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
1024
423
|
} catch {}
|
|
1025
424
|
return "npm";
|
|
1026
425
|
};
|
|
1027
426
|
const pythonEcosystem = (file, files) => {
|
|
1028
|
-
const manifestKind = pythonDependencyManifestKind(file);
|
|
427
|
+
const manifestKind = require_toolchain_inputs.pythonDependencyManifestKind(file);
|
|
1029
428
|
if (manifestKind === "pipfile" || manifestKind === "pipfile-lock" || manifestKind === "setup") return "pip";
|
|
1030
429
|
const root = node_path.default.posix.dirname(file);
|
|
1031
430
|
if (files[node_path.default.posix.join(root, "uv.lock")] !== void 0 || files[node_path.default.posix.join(root, "uv.toml")] !== void 0) return "uv";
|
|
1032
431
|
const pyproject = files[node_path.default.posix.join(root, "pyproject.toml")];
|
|
1033
432
|
if (pyproject !== void 0) try {
|
|
1034
433
|
const tool = (0, smol_toml.parse)(pyproject)["tool"];
|
|
1035
|
-
if (record$
|
|
434
|
+
if (record$5(tool) && record$5(tool["uv"])) return "uv";
|
|
1036
435
|
} catch {}
|
|
1037
436
|
return "pip";
|
|
1038
437
|
};
|
|
@@ -1050,17 +449,17 @@ const ecosystemRoots = (files) => {
|
|
|
1050
449
|
requireRoot(pythonEcosystem(node_path.default.posix.join(root, "pyproject.toml"), files), directory);
|
|
1051
450
|
}
|
|
1052
451
|
for (const file of Object.keys(files)) {
|
|
1053
|
-
if (isDependabotGithubActionsPath(file)) requireRoot("github-actions", "/");
|
|
452
|
+
if (require_toolchain_inputs.isDependabotGithubActionsPath(file)) requireRoot("github-actions", "/");
|
|
1054
453
|
const name = node_path.default.posix.basename(file);
|
|
1055
|
-
if (isDockerDefinitionPath(file)) requireRoot("docker", directoryOf(file));
|
|
1056
|
-
if (isComposeDefinitionPath(file) || isKubernetesDefinitionPath(file)) for (const document of (0, yaml.parseAllDocuments)(files[file] ?? "", { merge: true })) {
|
|
454
|
+
if (require_toolchain_inputs.isDockerDefinitionPath(file)) requireRoot("docker", directoryOf(file));
|
|
455
|
+
if (require_toolchain_inputs.isComposeDefinitionPath(file) || require_toolchain_inputs.isKubernetesDefinitionPath(file)) for (const document of (0, yaml.parseAllDocuments)(files[file] ?? "", { merge: true })) {
|
|
1057
456
|
if (document.errors.length > 0) continue;
|
|
1058
457
|
try {
|
|
1059
|
-
const classified = containerDocumentImages(document.toJS({ maxAliasCount: 100 }), file);
|
|
458
|
+
const classified = require_toolchain_inputs.containerDocumentImages(document.toJS({ maxAliasCount: 100 }), file);
|
|
1060
459
|
if (classified !== void 0) requireRoot(classified.ecosystem, directoryOf(file));
|
|
1061
460
|
} catch {}
|
|
1062
461
|
}
|
|
1063
|
-
if (name === "uv.lock" || name === "uv.toml" || name !== "pyproject.toml" && isPythonDependencyManifest(file)) requireRoot(pythonEcosystem(file, files), directoryOf(file));
|
|
462
|
+
if (name === "uv.lock" || name === "uv.toml" || name !== "pyproject.toml" && require_toolchain_inputs.isPythonDependencyManifest(file)) requireRoot(pythonEcosystem(file, files), directoryOf(file));
|
|
1064
463
|
}
|
|
1065
464
|
return roots;
|
|
1066
465
|
};
|
|
@@ -1103,7 +502,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1103
502
|
add("cannot resolve Dependabot YAML aliases");
|
|
1104
503
|
return diagnostics;
|
|
1105
504
|
}
|
|
1106
|
-
if (!record$
|
|
505
|
+
if (!record$5(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
1107
506
|
add("Dependabot must declare version: 2 and an updates array");
|
|
1108
507
|
return diagnostics;
|
|
1109
508
|
}
|
|
@@ -1116,7 +515,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1116
515
|
});
|
|
1117
516
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
1118
517
|
} catch {}
|
|
1119
|
-
const legacyUpdates = record$
|
|
518
|
+
const legacyUpdates = record$5(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
1120
519
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
1121
520
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
1122
521
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -1130,7 +529,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1130
529
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
1131
530
|
};
|
|
1132
531
|
const report = (key, message) => add(message, lineOf(key));
|
|
1133
|
-
if (!record$
|
|
532
|
+
if (!record$5(update) || typeof update["package-ecosystem"] !== "string") {
|
|
1134
533
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
1135
534
|
continue;
|
|
1136
535
|
}
|
|
@@ -1164,21 +563,21 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1164
563
|
covered.set(ecosystem, matches);
|
|
1165
564
|
const schedule = update["schedule"];
|
|
1166
565
|
const legacyUpdate = legacyUpdates.at(index);
|
|
1167
|
-
const legacySchedule = record$
|
|
1168
|
-
if (record$
|
|
1169
|
-
if (!record$
|
|
566
|
+
const legacySchedule = record$5(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
567
|
+
if (record$5(schedule) && schedule["time"] === policy.schedule.time && (!record$5(legacySchedule) || legacySchedule["time"] !== policy.schedule.time)) report("schedule", `${ecosystem}: quote schedule time to preserve its string value in YAML 1.1`);
|
|
568
|
+
if (!record$5(schedule) || Object.keys(schedule).length !== Object.keys(policy.schedule).length || Object.entries(policy.schedule).some(([key, value]) => schedule[key] !== value)) report("schedule", `${ecosystem}: schedule must match the shared policy`);
|
|
1170
569
|
const cooldown = update["cooldown"];
|
|
1171
|
-
if (!record$
|
|
570
|
+
if (!record$5(cooldown) || Object.keys(cooldown).length !== 1 || cooldown["default-days"] !== policy.cooldown.defaultDays) report("cooldown", `${ecosystem}: cooldown must contain only default-days: ${policy.cooldown.defaultDays}`);
|
|
1172
571
|
const groups = update["groups"];
|
|
1173
|
-
if (!record$
|
|
572
|
+
if (!record$5(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
1174
573
|
const group = groups[name];
|
|
1175
|
-
return !record$
|
|
574
|
+
return !record$5(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
1176
575
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
1177
576
|
const ignored = ignoredFor(ecosystem, policy);
|
|
1178
577
|
const ignore = update["ignore"];
|
|
1179
578
|
if (ignore !== void 0 && (!Array.isArray(ignore) || ignore.length === 0)) report("ignore", `${ecosystem}: omit ignore or provide a nonempty array`);
|
|
1180
|
-
if (Array.isArray(ignore) && ignore.some((entry) => !record$
|
|
1181
|
-
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$
|
|
579
|
+
if (Array.isArray(ignore) && ignore.some((entry) => !record$5(entry) || typeof entry["dependency-name"] !== "string" || !ignored.includes(entry["dependency-name"]))) report("ignore", `${ecosystem}: ignore only packages owned by the shared policy`);
|
|
580
|
+
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$5(entry) && entry["dependency-name"] === name && Object.keys(entry).length === 1)) report("ignore", `${ecosystem}: ignore all updates for shared pin ${name}`);
|
|
1182
581
|
}
|
|
1183
582
|
for (const [ecosystem, directories] of roots) for (const directory of directories) if (!covered.get(ecosystem)?.has(directory)) add(`add a ${ecosystem} update entry covering ${directory}`);
|
|
1184
583
|
return diagnostics;
|
|
@@ -1190,7 +589,7 @@ const locatorValidators = {
|
|
|
1190
589
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
1191
590
|
};
|
|
1192
591
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
1193
|
-
const record$
|
|
592
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1194
593
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
1195
594
|
const parseDynamicSelectors = (input) => {
|
|
1196
595
|
if (input === void 0) return [];
|
|
@@ -1198,7 +597,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
1198
597
|
const result = [];
|
|
1199
598
|
const keys = /* @__PURE__ */ new Set();
|
|
1200
599
|
for (const entry of input) {
|
|
1201
|
-
if (!record$
|
|
600
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
1202
601
|
"path",
|
|
1203
602
|
"kind",
|
|
1204
603
|
"reason",
|
|
@@ -1234,7 +633,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
1234
633
|
};
|
|
1235
634
|
//#endregion
|
|
1236
635
|
//#region src/toolchain-engine-floors.ts
|
|
1237
|
-
const record$
|
|
636
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1238
637
|
const repositoryPath = (value) => typeof value === "string" && value !== "" && !/^[A-Za-z]:/.test(value) && !/[\\\s]/.test(value) && !Array.from(value).some((character) => character.charCodeAt(0) < 32) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
1239
638
|
const parseEngineFloors = (input) => {
|
|
1240
639
|
if (input === void 0) return [];
|
|
@@ -1242,7 +641,7 @@ const parseEngineFloors = (input) => {
|
|
|
1242
641
|
const result = [];
|
|
1243
642
|
const jobs = /* @__PURE__ */ new Set();
|
|
1244
643
|
for (const entry of input) {
|
|
1245
|
-
if (!record$
|
|
644
|
+
if (!record$3(entry) || Object.keys(entry).some((key) => ![
|
|
1246
645
|
"package",
|
|
1247
646
|
"workflow",
|
|
1248
647
|
"job"
|
|
@@ -1263,16 +662,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
1263
662
|
const workflowText = files[entry.workflow];
|
|
1264
663
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
1265
664
|
const manifest = JSON.parse(manifestText);
|
|
1266
|
-
if (!record$
|
|
665
|
+
if (!record$3(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$3(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
|
|
1267
666
|
const range = manifest["engines"]["node"];
|
|
1268
667
|
const minimum = (0, semver.validRange)(range) === null ? null : (0, semver.minVersion)(range);
|
|
1269
668
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
1270
669
|
const document = (0, yaml.parseDocument)(workflowText, { merge: true });
|
|
1271
670
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
1272
671
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
1273
|
-
if (!record$
|
|
672
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
1274
673
|
const job = workflow["jobs"][entry.job];
|
|
1275
|
-
if (!record$
|
|
674
|
+
if (!record$3(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
1276
675
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
1277
676
|
return {
|
|
1278
677
|
...entry,
|
|
@@ -1292,7 +691,7 @@ const packageRules = [
|
|
|
1292
691
|
"typescript-layout",
|
|
1293
692
|
"node-engine"
|
|
1294
693
|
];
|
|
1295
|
-
const record$
|
|
694
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1296
695
|
const shellSubstitutions = (command) => {
|
|
1297
696
|
const frames = [{
|
|
1298
697
|
kind: "root",
|
|
@@ -1357,7 +756,7 @@ const compilerCommandSegments = (command) => {
|
|
|
1357
756
|
const segments = [[]];
|
|
1358
757
|
for (const source of [command, ...shellSubstitutions(command)]) {
|
|
1359
758
|
segments.push([]);
|
|
1360
|
-
const shellWords = source.match(/(?:[^\s"'\\;&|()]|\\[
|
|
759
|
+
const shellWords = source.match(/(?:[^\s"'\\;&|()]|\\[\s\S]|"(?:[^"\\]|\\[\s\S])*"|'[^']*')+|&&|\|\||[();&|\n]/g);
|
|
1361
760
|
for (const word of shellWords ?? []) if (/^(?:&&|\|\||[;&|\n])$/.test(word)) segments.push([]);
|
|
1362
761
|
else segments.at(-1)?.push(word);
|
|
1363
762
|
}
|
|
@@ -1444,29 +843,29 @@ const launcherValueOptions = new Map(Object.entries({
|
|
|
1444
843
|
"--input-type"
|
|
1445
844
|
]
|
|
1446
845
|
}).map(([launcher, options]) => [launcher, new Set(options)]));
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
continue;
|
|
1456
|
-
}
|
|
1457
|
-
if (quote === void 0 && (character === "'" || character === "\"")) {
|
|
1458
|
-
quote = character;
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
const next = word.at(index + 1);
|
|
1462
|
-
if (character === "\\" && next !== void 0 && quote !== "'" && (quote === void 0 || /[$`"\\\n]/.test(next))) {
|
|
1463
|
-
result += next;
|
|
1464
|
-
index += 1;
|
|
1465
|
-
} else result += character;
|
|
846
|
+
const decodeShellWord = (word) => {
|
|
847
|
+
let result = "";
|
|
848
|
+
let quote;
|
|
849
|
+
for (let index = 0; index < word.length; index += 1) {
|
|
850
|
+
const character = word.at(index);
|
|
851
|
+
if (character === quote) {
|
|
852
|
+
quote = void 0;
|
|
853
|
+
continue;
|
|
1466
854
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
855
|
+
if (quote === void 0 && (character === "'" || character === "\"")) {
|
|
856
|
+
quote = character;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const next = word.at(index + 1);
|
|
860
|
+
if (character === "\\" && next !== void 0 && quote !== "'" && (quote === void 0 || /[$`"\\\n]/.test(next))) {
|
|
861
|
+
if (next !== "\n") result += next;
|
|
862
|
+
index += 1;
|
|
863
|
+
} else result += character;
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
};
|
|
867
|
+
const invokesCompiler = (words) => {
|
|
868
|
+
const tokens = words.map(decodeShellWord);
|
|
1470
869
|
const compiler = (word) => /(?:^|\/)(?:tsc|tsgo)(?:\.js)?$/.test(word);
|
|
1471
870
|
let start = 0;
|
|
1472
871
|
const skipOptions = (index, launcher) => {
|
|
@@ -1523,10 +922,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1523
922
|
const document = pnpm ? (0, yaml.parseDocument)(text, { lineCounter }) : void 0;
|
|
1524
923
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
1525
924
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
1526
|
-
if (!record$
|
|
925
|
+
if (!record$2(json)) throw new Error("manifest must be an object");
|
|
1527
926
|
if (pnpm) {
|
|
1528
|
-
if (json["catalog"] !== void 0 && !record$
|
|
1529
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
927
|
+
if (json["catalog"] !== void 0 && !record$2(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
928
|
+
if (json["catalogs"] !== void 0 && (!record$2(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$2(catalog)))) throw new Error("named catalogs must be mappings");
|
|
1530
929
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
1531
930
|
}
|
|
1532
931
|
manifests.set(file, json);
|
|
@@ -1540,13 +939,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1540
939
|
catalog: json["overrides"],
|
|
1541
940
|
prefix: ["overrides"]
|
|
1542
941
|
},
|
|
1543
|
-
...record$
|
|
942
|
+
...record$2(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1544
943
|
catalog,
|
|
1545
944
|
prefix: ["catalogs", name]
|
|
1546
945
|
})) : []
|
|
1547
946
|
];
|
|
1548
947
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1549
|
-
if (!record$
|
|
948
|
+
if (!record$2(catalog)) continue;
|
|
1550
949
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1551
950
|
const node = document.getIn([...prefix, name], true);
|
|
1552
951
|
if (!(0, yaml.isNode)(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1565,7 +964,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1565
964
|
});
|
|
1566
965
|
}
|
|
1567
966
|
}
|
|
1568
|
-
const catalogSources = (json) => [json, ...record$
|
|
967
|
+
const catalogSources = (json) => [json, ...record$2(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1569
968
|
const pnpmWorkspaceFor = (file) => {
|
|
1570
969
|
let directory = node_path.default.posix.dirname(file);
|
|
1571
970
|
while (true) {
|
|
@@ -1582,14 +981,14 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1582
981
|
const owner = pnpmWorkspaceFor(file);
|
|
1583
982
|
if (owner !== void 0) {
|
|
1584
983
|
const { directory, workspace } = owner;
|
|
1585
|
-
if (node_path.default.posix.basename(file) === "package.json" && !workspaceContains({
|
|
984
|
+
if (node_path.default.posix.basename(file) === "package.json" && !require_toolchain_inputs.workspaceContains({
|
|
1586
985
|
directory,
|
|
1587
986
|
file,
|
|
1588
987
|
patterns: workspace["packages"],
|
|
1589
988
|
rootMembership: "implicit"
|
|
1590
989
|
})) return void 0;
|
|
1591
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1592
|
-
return record$
|
|
990
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$2(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
991
|
+
return record$2(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1593
992
|
}
|
|
1594
993
|
let directory = node_path.default.posix.dirname(file);
|
|
1595
994
|
while (true) {
|
|
@@ -1597,7 +996,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1597
996
|
if (manifest !== void 0) {
|
|
1598
997
|
for (const source of catalogSources(manifest)) {
|
|
1599
998
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1600
|
-
if (record$
|
|
999
|
+
if (record$2(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1601
1000
|
}
|
|
1602
1001
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1603
1002
|
}
|
|
@@ -1611,7 +1010,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1611
1010
|
if (visited.has(value)) return void 0;
|
|
1612
1011
|
visited.add(value);
|
|
1613
1012
|
const catalog = catalogFor(file, value.slice(8));
|
|
1614
|
-
value = record$
|
|
1013
|
+
value = record$2(catalog) ? catalog[name] : void 0;
|
|
1615
1014
|
}
|
|
1616
1015
|
return value;
|
|
1617
1016
|
};
|
|
@@ -1627,7 +1026,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1627
1026
|
const workspaces = manifests.get(node_path.default.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1628
1027
|
if (workspaces !== void 0) return {
|
|
1629
1028
|
directory,
|
|
1630
|
-
patterns: record$
|
|
1029
|
+
patterns: record$2(workspaces) ? workspaces["packages"] : workspaces,
|
|
1631
1030
|
source: "json"
|
|
1632
1031
|
};
|
|
1633
1032
|
if (directory === ".") return void 0;
|
|
@@ -1639,13 +1038,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1639
1038
|
if (owner === void 0) return "nonmember";
|
|
1640
1039
|
const { directory, patterns, source } = owner;
|
|
1641
1040
|
if (source === "json" && !Array.isArray(patterns)) return "mismatch";
|
|
1642
|
-
if (!workspaceContains({
|
|
1041
|
+
if (!require_toolchain_inputs.workspaceContains({
|
|
1643
1042
|
directory,
|
|
1644
1043
|
file,
|
|
1645
1044
|
patterns,
|
|
1646
1045
|
rootMembership: "implicit"
|
|
1647
1046
|
})) return "nonmember";
|
|
1648
|
-
const candidates = [...manifests.entries()].filter(([candidate, json]) => node_path.default.posix.basename(candidate) === "package.json" && json["name"] === name && workspaceContains({
|
|
1047
|
+
const candidates = [...manifests.entries()].filter(([candidate, json]) => node_path.default.posix.basename(candidate) === "package.json" && json["name"] === name && require_toolchain_inputs.workspaceContains({
|
|
1649
1048
|
directory,
|
|
1650
1049
|
file: candidate,
|
|
1651
1050
|
patterns,
|
|
@@ -1683,7 +1082,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1683
1082
|
"peerDependencies"
|
|
1684
1083
|
]) {
|
|
1685
1084
|
const dependencies = json[section];
|
|
1686
|
-
if (!record$
|
|
1085
|
+
if (!record$2(dependencies)) continue;
|
|
1687
1086
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1688
1087
|
if (!tsSpecifiers.has(name)) continue;
|
|
1689
1088
|
const resolved = resolve(file, name, value);
|
|
@@ -1709,7 +1108,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1709
1108
|
const workspaceLayouts = /* @__PURE__ */ new Map();
|
|
1710
1109
|
const workspaceLayoutFor = (file) => {
|
|
1711
1110
|
const owner = workspaceOwnerFor(file);
|
|
1712
|
-
if (owner === void 0 || !workspaceContains({
|
|
1111
|
+
if (owner === void 0 || !require_toolchain_inputs.workspaceContains({
|
|
1713
1112
|
...owner,
|
|
1714
1113
|
file,
|
|
1715
1114
|
rootMembership: "implicit"
|
|
@@ -1721,7 +1120,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1721
1120
|
for (const [candidate, member] of manifests) {
|
|
1722
1121
|
if (node_path.default.posix.basename(candidate) !== "package.json") continue;
|
|
1723
1122
|
const memberOwner = workspaceOwnerFor(candidate);
|
|
1724
|
-
if (memberOwner?.directory !== owner.directory || memberOwner.source !== owner.source || !workspaceContains({
|
|
1123
|
+
if (memberOwner?.directory !== owner.directory || memberOwner.source !== owner.source || !require_toolchain_inputs.workspaceContains({
|
|
1725
1124
|
...owner,
|
|
1726
1125
|
file: candidate,
|
|
1727
1126
|
rootMembership: "implicit"
|
|
@@ -1745,7 +1144,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1745
1144
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1746
1145
|
});
|
|
1747
1146
|
const checkEntries = (entries, location) => {
|
|
1748
|
-
if (!record$
|
|
1147
|
+
if (!record$2(entries)) return;
|
|
1749
1148
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1750
1149
|
const value = resolve(file, name, raw);
|
|
1751
1150
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1782,12 +1181,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1782
1181
|
"devDependencies",
|
|
1783
1182
|
"optionalDependencies"
|
|
1784
1183
|
]) checkEntries(json[key]);
|
|
1785
|
-
if (record$
|
|
1184
|
+
if (record$2(json["peerDependencies"])) {
|
|
1786
1185
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1787
1186
|
}
|
|
1788
1187
|
for (const source of catalogSources(json)) {
|
|
1789
1188
|
checkEntries(source["catalog"]);
|
|
1790
|
-
if (!record$
|
|
1189
|
+
if (!record$2(source["catalogs"])) continue;
|
|
1791
1190
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1792
1191
|
}
|
|
1793
1192
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1799,7 +1198,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1799
1198
|
"optionalDependencies"
|
|
1800
1199
|
]) {
|
|
1801
1200
|
const entries = resolutionRoot[section];
|
|
1802
|
-
if (!record$
|
|
1201
|
+
if (!record$2(entries)) continue;
|
|
1803
1202
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1804
1203
|
}
|
|
1805
1204
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1807,7 +1206,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1807
1206
|
for (const [candidate, member] of manifests) {
|
|
1808
1207
|
if (node_path.default.posix.basename(candidate) !== "package.json") continue;
|
|
1809
1208
|
const memberOwner = workspaceOwnerFor(candidate);
|
|
1810
|
-
if (!(candidate === node_path.default.posix.join(node_path.default.posix.dirname(file), "package.json")) && !(owner !== void 0 && owner.directory === node_path.default.posix.dirname(file) && memberOwner?.directory === owner.directory && memberOwner.source === owner.source && workspaceContains({
|
|
1209
|
+
if (!(candidate === node_path.default.posix.join(node_path.default.posix.dirname(file), "package.json")) && !(owner !== void 0 && owner.directory === node_path.default.posix.dirname(file) && memberOwner?.directory === owner.directory && memberOwner.source === owner.source && require_toolchain_inputs.workspaceContains({
|
|
1811
1210
|
directory: owner.directory,
|
|
1812
1211
|
file: candidate,
|
|
1813
1212
|
patterns: owner.patterns,
|
|
@@ -1819,7 +1218,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1819
1218
|
"optionalDependencies"
|
|
1820
1219
|
]) {
|
|
1821
1220
|
const entries = member[section];
|
|
1822
|
-
if (!record$
|
|
1221
|
+
if (!record$2(entries)) continue;
|
|
1823
1222
|
for (const [name, value] of Object.entries(entries)) {
|
|
1824
1223
|
if (!tsSpecifiers.has(name)) continue;
|
|
1825
1224
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1829,7 +1228,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1829
1228
|
}
|
|
1830
1229
|
}
|
|
1831
1230
|
const checkResolutionMap = (entries, parent) => {
|
|
1832
|
-
if (!record$
|
|
1231
|
+
if (!record$2(entries)) return;
|
|
1833
1232
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1834
1233
|
const target = selector === "." ? parent : selector;
|
|
1835
1234
|
if (target === void 0) continue;
|
|
@@ -1852,7 +1251,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1852
1251
|
});
|
|
1853
1252
|
continue;
|
|
1854
1253
|
}
|
|
1855
|
-
if (record$
|
|
1254
|
+
if (record$2(replacement)) {
|
|
1856
1255
|
checkResolutionMap(replacement, target);
|
|
1857
1256
|
continue;
|
|
1858
1257
|
}
|
|
@@ -1886,7 +1285,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1886
1285
|
};
|
|
1887
1286
|
checkResolutionMap(json["overrides"]);
|
|
1888
1287
|
checkResolutionMap(json["resolutions"]);
|
|
1889
|
-
if (record$
|
|
1288
|
+
if (record$2(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1890
1289
|
const localEntries = toolchainEntries(file, json);
|
|
1891
1290
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1892
1291
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1910,7 +1309,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1910
1309
|
key: "dependencies",
|
|
1911
1310
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1912
1311
|
});
|
|
1913
|
-
const command = record$
|
|
1312
|
+
const command = record$2(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1914
1313
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1915
1314
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1916
1315
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1928,7 +1327,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1928
1327
|
}
|
|
1929
1328
|
}
|
|
1930
1329
|
}
|
|
1931
|
-
if (record$
|
|
1330
|
+
if (record$2(json["peerDependencies"])) {
|
|
1932
1331
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1933
1332
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1934
1333
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1952,7 +1351,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1952
1351
|
});
|
|
1953
1352
|
}
|
|
1954
1353
|
}
|
|
1955
|
-
if (record$
|
|
1354
|
+
if (record$2(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !require_toolchain_inputs.nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1956
1355
|
file,
|
|
1957
1356
|
rule: "node-engine",
|
|
1958
1357
|
key: "node",
|
|
@@ -1974,7 +1373,7 @@ const runtimeRules = [
|
|
|
1974
1373
|
"runtime-workflow",
|
|
1975
1374
|
"action-pins"
|
|
1976
1375
|
];
|
|
1977
|
-
const record$
|
|
1376
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1978
1377
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1979
1378
|
const compareRelease = (left, right) => {
|
|
1980
1379
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -2069,7 +1468,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2069
1468
|
const lineOf = (key) => Math.max(1, lines.findIndex((line) => line.includes(key)) + 1);
|
|
2070
1469
|
const pin = ({ rule, value, expected, label, line = 1, selector }) => {
|
|
2071
1470
|
let matches = value === expected;
|
|
2072
|
-
if (selector === "node") matches = nodeSelectorMatches(value, expected);
|
|
1471
|
+
if (selector === "node") matches = require_toolchain_inputs.nodeSelectorMatches(value, expected);
|
|
2073
1472
|
else if (label.includes("python")) matches = pythonSelectorMatches(value, expected);
|
|
2074
1473
|
if (!matches) add({
|
|
2075
1474
|
rule,
|
|
@@ -2138,11 +1537,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2138
1537
|
const parsed = toml("rust-version");
|
|
2139
1538
|
if (parsed !== void 0) {
|
|
2140
1539
|
const toolchain = parsed["toolchain"];
|
|
2141
|
-
const components = record$
|
|
1540
|
+
const components = record$1(toolchain) ? toolchain["components"] : void 0;
|
|
2142
1541
|
const expected = Array.isArray(components) && components.includes("rustc-dev") ? policy.rustCompilerDevelopment : policy.rust;
|
|
2143
1542
|
pin({
|
|
2144
1543
|
rule: "rust-version",
|
|
2145
|
-
value: record$
|
|
1544
|
+
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
2146
1545
|
expected,
|
|
2147
1546
|
label: "Rust channel",
|
|
2148
1547
|
line: lineOf("channel")
|
|
@@ -2170,14 +1569,14 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2170
1569
|
};
|
|
2171
1570
|
if (parsed !== void 0) {
|
|
2172
1571
|
const workspace = parsed["workspace"];
|
|
2173
|
-
const defaults = record$
|
|
2174
|
-
if (record$
|
|
1572
|
+
const defaults = record$1(workspace) ? workspace["package"] : void 0;
|
|
1573
|
+
if (record$1(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2175
1574
|
const pkg = parsed["package"];
|
|
2176
|
-
const version = record$
|
|
2177
|
-
if (version !== void 0) if (!record$
|
|
1575
|
+
const version = record$1(pkg) ? pkg["rust-version"] : void 0;
|
|
1576
|
+
if (version !== void 0) if (!record$1(version)) rustFloor(version);
|
|
2178
1577
|
else {
|
|
2179
1578
|
let inherited;
|
|
2180
|
-
if (version["workspace"] === true && record$
|
|
1579
|
+
if (version["workspace"] === true && record$1(pkg)) {
|
|
2181
1580
|
const explicit = pkg["workspace"];
|
|
2182
1581
|
let directory = node_path.default.posix.dirname(file);
|
|
2183
1582
|
const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !node_path.default.posix.isAbsolute(explicit) ? node_path.default.posix.normalize(node_path.default.posix.join(directory, explicit)) : void 0;
|
|
@@ -2185,9 +1584,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2185
1584
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2186
1585
|
while (true) {
|
|
2187
1586
|
const table = readManifest(node_path.default.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2188
|
-
if (record$
|
|
1587
|
+
if (record$1(table)) {
|
|
2189
1588
|
const fields = table["package"];
|
|
2190
|
-
inherited = record$
|
|
1589
|
+
inherited = record$1(fields) ? fields["rust-version"] : void 0;
|
|
2191
1590
|
break;
|
|
2192
1591
|
}
|
|
2193
1592
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2201,7 +1600,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2201
1600
|
}
|
|
2202
1601
|
if (name === "Pipfile") {
|
|
2203
1602
|
const requires = toml("python-version")?.["requires"];
|
|
2204
|
-
if (record$
|
|
1603
|
+
if (record$1(requires)) {
|
|
2205
1604
|
const minor = requires["python_version"];
|
|
2206
1605
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2207
1606
|
rule: "python-version",
|
|
@@ -2220,7 +1619,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2220
1619
|
const parsed = toml("python-version");
|
|
2221
1620
|
if (parsed !== void 0) {
|
|
2222
1621
|
const project = parsed["project"];
|
|
2223
|
-
if (record$
|
|
1622
|
+
if (record$1(project) && project["requires-python"] !== void 0) {
|
|
2224
1623
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2225
1624
|
rule: "python-version",
|
|
2226
1625
|
line: lineOf("requires-python"),
|
|
@@ -2228,9 +1627,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2228
1627
|
});
|
|
2229
1628
|
}
|
|
2230
1629
|
const tool = parsed["tool"];
|
|
2231
|
-
const configuredUv = record$
|
|
1630
|
+
const configuredUv = record$1(tool) ? tool["uv"] : void 0;
|
|
2232
1631
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2233
|
-
if (record$
|
|
1632
|
+
if (record$1(uv) && uv["python"] !== void 0) pin({
|
|
2234
1633
|
rule: "python-version",
|
|
2235
1634
|
value: uv["python"],
|
|
2236
1635
|
expected: policy.python,
|
|
@@ -2239,13 +1638,13 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2239
1638
|
});
|
|
2240
1639
|
}
|
|
2241
1640
|
}
|
|
2242
|
-
if (isMiseConfigPath(file)) {
|
|
1641
|
+
if (require_toolchain_inputs.isMiseConfigPath(file)) {
|
|
2243
1642
|
const tools = toml("runtime-manager")?.["tools"];
|
|
2244
|
-
if (record$
|
|
1643
|
+
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
2245
1644
|
const configured = tools[tool];
|
|
2246
1645
|
if (configured !== void 0) pin({
|
|
2247
1646
|
rule: "runtime-manager",
|
|
2248
|
-
value: record$
|
|
1647
|
+
value: record$1(configured) ? configured["version"] : configured,
|
|
2249
1648
|
expected,
|
|
2250
1649
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
2251
1650
|
label: `mise ${tool}`,
|
|
@@ -2343,7 +1742,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2343
1742
|
line
|
|
2344
1743
|
});
|
|
2345
1744
|
};
|
|
2346
|
-
if (isDockerDefinitionPath(file)) {
|
|
1745
|
+
if (require_toolchain_inputs.isDockerDefinitionPath(file)) {
|
|
2347
1746
|
const variables = /* @__PURE__ */ new Map();
|
|
2348
1747
|
const escape = /^\s*#\s*escape\s*=\s*([\\`])\s*$/m.exec(text)?.[1] ?? "\\";
|
|
2349
1748
|
const continuation = new RegExp(`${escape === "`" ? "`" : "\\\\"}\\s*$`);
|
|
@@ -2476,7 +1875,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2476
1875
|
message: `unterminated Docker heredoc ${heredoc.delimiter}`
|
|
2477
1876
|
});
|
|
2478
1877
|
}
|
|
2479
|
-
if (isComposeDefinitionPath(file) || isKubernetesDefinitionPath(file)) {
|
|
1878
|
+
if (require_toolchain_inputs.isComposeDefinitionPath(file) || require_toolchain_inputs.isKubernetesDefinitionPath(file)) {
|
|
2480
1879
|
const lineCounter = new yaml.LineCounter();
|
|
2481
1880
|
for (const containerDocument of (0, yaml.parseAllDocuments)(text, {
|
|
2482
1881
|
lineCounter,
|
|
@@ -2485,7 +1884,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2485
1884
|
})) {
|
|
2486
1885
|
const contents = containerDocument.contents;
|
|
2487
1886
|
const declaredResource = (0, yaml.isMap)(contents) && (0, yaml.isScalar)(contents.get("apiVersion", true)) && (0, yaml.isScalar)(contents.get("kind", true));
|
|
2488
|
-
const recognizedContainer = isComposeDefinitionPath(file) || declaredResource;
|
|
1887
|
+
const recognizedContainer = require_toolchain_inputs.isComposeDefinitionPath(file) || declaredResource;
|
|
2489
1888
|
if (containerDocument.errors.length > 0) {
|
|
2490
1889
|
if (recognizedContainer) add({
|
|
2491
1890
|
rule: "runtime-docker",
|
|
@@ -2495,7 +1894,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2495
1894
|
continue;
|
|
2496
1895
|
}
|
|
2497
1896
|
try {
|
|
2498
|
-
const classified = containerDocumentImages(containerDocument.toJS({ maxAliasCount: 100 }), file);
|
|
1897
|
+
const classified = require_toolchain_inputs.containerDocumentImages(containerDocument.toJS({ maxAliasCount: 100 }), file);
|
|
2499
1898
|
for (const entry of classified?.images ?? []) {
|
|
2500
1899
|
const node = containerDocument.getIn(entry.path, true);
|
|
2501
1900
|
const line = ((0, yaml.isMap)(node) || (0, yaml.isScalar)(node) || (0, yaml.isSeq)(node) || (0, yaml.isAlias)(node)) && node.range !== void 0 && node.range !== null ? lineCounter.linePos(node.range[0]).line : 1;
|
|
@@ -2515,7 +1914,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2515
1914
|
}
|
|
2516
1915
|
}
|
|
2517
1916
|
}
|
|
2518
|
-
const automationKind = githubAutomationFileKind(file);
|
|
1917
|
+
const automationKind = require_toolchain_inputs.githubAutomationFileKind(file);
|
|
2519
1918
|
if (automationKind === void 0) return diagnostics;
|
|
2520
1919
|
const document = (0, yaml.parseDocument)(text, {
|
|
2521
1920
|
uniqueKeys: true,
|
|
@@ -2530,7 +1929,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2530
1929
|
return diagnostics;
|
|
2531
1930
|
}
|
|
2532
1931
|
const nodeLine = (node) => {
|
|
2533
|
-
if (!record$
|
|
1932
|
+
if (!record$1(node) || !Array.isArray(node["range"])) return 1;
|
|
2534
1933
|
const offset = node["range"].at(0);
|
|
2535
1934
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
2536
1935
|
};
|
|
@@ -2644,7 +2043,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2644
2043
|
const manifest = JSON.parse(content ?? "");
|
|
2645
2044
|
pin({
|
|
2646
2045
|
rule,
|
|
2647
|
-
value: record$
|
|
2046
|
+
value: record$1(manifest) ? manifest["packageManager"] : void 0,
|
|
2648
2047
|
expected: `bun@${policy.bun}`,
|
|
2649
2048
|
label: "setup-bun version file packageManager",
|
|
2650
2049
|
line
|
|
@@ -3123,192 +2522,6 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3123
2522
|
return diagnostics;
|
|
3124
2523
|
};
|
|
3125
2524
|
//#endregion
|
|
3126
|
-
//#region src/toolchain-schema.ts
|
|
3127
|
-
const packagePinKeys = [
|
|
3128
|
-
"oxlint",
|
|
3129
|
-
"oxlint-tsgolint",
|
|
3130
|
-
"@oxlint/plugins",
|
|
3131
|
-
"@stll/oxlint-plugin",
|
|
3132
|
-
"oxfmt",
|
|
3133
|
-
"lefthook"
|
|
3134
|
-
];
|
|
3135
|
-
const valkeyArchitectures = ["amd64", "arm64"];
|
|
3136
|
-
const valkeyArtifactPackages = {
|
|
3137
|
-
server: "valkey-server",
|
|
3138
|
-
tools: "valkey-tools"
|
|
3139
|
-
};
|
|
3140
|
-
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3141
|
-
const artifactRecord = (value, keys) => {
|
|
3142
|
-
if (!record$1(value) || Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !Object.hasOwn(value, key))) throw new Error("valkeyArtifacts requires complete, closed artifact objects");
|
|
3143
|
-
return value;
|
|
3144
|
-
};
|
|
3145
|
-
const parseValkeyArtifacts = (input, version) => {
|
|
3146
|
-
const architectures = artifactRecord(input, valkeyArchitectures);
|
|
3147
|
-
const line = version.split(".").slice(0, 2).join(".");
|
|
3148
|
-
const parseArchitecture = (architecture) => {
|
|
3149
|
-
const packages = artifactRecord(architectures[architecture], Object.keys(valkeyArtifactPackages));
|
|
3150
|
-
const parseArtifact = (kind) => {
|
|
3151
|
-
const artifact = artifactRecord(packages[kind], ["url", "sha256"]);
|
|
3152
|
-
const url = artifact["url"];
|
|
3153
|
-
const sha256 = artifact["sha256"];
|
|
3154
|
-
const expected = `https://download.valkey.io/packaging/valkey-${line}/deb/ubuntu2404/${architecture}/${valkeyArtifactPackages[kind]}_${version}-1.noble_${architecture}.deb`;
|
|
3155
|
-
if (typeof url !== "string" || url !== expected) throw new Error("valkeyArtifacts URL must select the exact official versioned Ubuntu artifact");
|
|
3156
|
-
if (typeof sha256 !== "string" || !/^[a-f0-9]{64}$/.test(sha256)) throw new Error("valkeyArtifacts requires a lowercase SHA256 pin");
|
|
3157
|
-
return {
|
|
3158
|
-
url,
|
|
3159
|
-
sha256
|
|
3160
|
-
};
|
|
3161
|
-
};
|
|
3162
|
-
return {
|
|
3163
|
-
server: parseArtifact("server"),
|
|
3164
|
-
tools: parseArtifact("tools")
|
|
3165
|
-
};
|
|
3166
|
-
};
|
|
3167
|
-
return {
|
|
3168
|
-
amd64: parseArchitecture("amd64"),
|
|
3169
|
-
arm64: parseArchitecture("arm64")
|
|
3170
|
-
};
|
|
3171
|
-
};
|
|
3172
|
-
/** Reject unsupported or incomplete installed policies before inspecting a consumer. */
|
|
3173
|
-
const parseToolchainPolicy = (input) => {
|
|
3174
|
-
if (!record$1(input) || input["schemaVersion"] !== 1) throw new Error("toolchain.json must use schemaVersion 1");
|
|
3175
|
-
const string = (object, key) => {
|
|
3176
|
-
const value = object[key];
|
|
3177
|
-
if (typeof value !== "string" || value.trim() === "") throw new Error(`toolchain.json requires a nonempty ${key}`);
|
|
3178
|
-
return value;
|
|
3179
|
-
};
|
|
3180
|
-
const packages = {};
|
|
3181
|
-
for (const key of packagePinKeys) packages[key] = string(input, key);
|
|
3182
|
-
const bun = string(input, "bun");
|
|
3183
|
-
const typescript = string(input, "typescript");
|
|
3184
|
-
const node = string(input, "node");
|
|
3185
|
-
const python = string(input, "python");
|
|
3186
|
-
const postgres = string(input, "postgres");
|
|
3187
|
-
const valkey = string(input, "valkey");
|
|
3188
|
-
const rust = string(input, "rust");
|
|
3189
|
-
const rustCompilerDevelopment = string(input, "rustCompilerDevelopment");
|
|
3190
|
-
for (const [name, value] of Object.entries({
|
|
3191
|
-
...packages,
|
|
3192
|
-
bun,
|
|
3193
|
-
typescript
|
|
3194
|
-
})) if ((0, semver.valid)(value) !== value) throw new Error(`toolchain.json ${name} must be an exact release`);
|
|
3195
|
-
if (!/^[1-9]\d*$/.test(postgres) || !Number.isSafeInteger(Number(postgres))) throw new Error("PostgreSQL must be a canonical positive major");
|
|
3196
|
-
if ((0, semver.valid)(valkey) !== valkey || !/^\d+\.\d+\.\d+$/.test(valkey)) throw new Error("Valkey must be an exact stable release");
|
|
3197
|
-
const valkeyArtifacts = parseValkeyArtifacts(input["valkeyArtifacts"], valkey);
|
|
3198
|
-
if (!nodePolicyValid(node)) throw new Error("Node must be a canonical major N.x series");
|
|
3199
|
-
if (!/^\d+\.\d+(?:\.\d+)?$/.test(python)) throw new Error("Python must be an exact minor or patch release");
|
|
3200
|
-
if (!/^\d+\.\d+\.\d+$/.test(rust)) throw new Error("Rust must be an exact stable release");
|
|
3201
|
-
if (!/^nightly-\d{4}-\d{2}-\d{2}$/.test(rustCompilerDevelopment)) throw new Error("Rust compiler development must be a dated nightly");
|
|
3202
|
-
const compatibility = input["typescript6Compatibility"];
|
|
3203
|
-
if (!record$1(compatibility)) throw new Error("missing TypeScript compatibility policy");
|
|
3204
|
-
const version = string(compatibility, "version");
|
|
3205
|
-
const packageAlias = string(compatibility, "packageAlias");
|
|
3206
|
-
if ((0, semver.valid)(version) !== version) throw new Error("TypeScript compatibility must be an exact release");
|
|
3207
|
-
const layouts = input["typescriptInstallLayouts"];
|
|
3208
|
-
if (!Array.isArray(layouts) || layouts.length === 0) throw new Error("missing TypeScript install layouts");
|
|
3209
|
-
const typescriptInstallLayouts = layouts.map((layout) => {
|
|
3210
|
-
if (!record$1(layout)) throw new Error("invalid TypeScript install layout");
|
|
3211
|
-
const compilerPackage = string(layout, "compilerPackage");
|
|
3212
|
-
const compilerSpecifier = string(layout, "compilerSpecifier");
|
|
3213
|
-
const typecheckCommand = string(layout, "typecheckCommand");
|
|
3214
|
-
switch (layout["type"]) {
|
|
3215
|
-
case "direct":
|
|
3216
|
-
if (compilerSpecifier !== typescript || compilerPackage !== "typescript") throw new Error("direct TypeScript layout differs from shared version");
|
|
3217
|
-
return {
|
|
3218
|
-
type: "direct",
|
|
3219
|
-
compilerPackage,
|
|
3220
|
-
compilerSpecifier,
|
|
3221
|
-
typecheckCommand
|
|
3222
|
-
};
|
|
3223
|
-
case "split-compatibility": {
|
|
3224
|
-
const compatibilityPackage = string(layout, "compatibilityPackage");
|
|
3225
|
-
const compatibilitySpecifier = string(layout, "compatibilitySpecifier");
|
|
3226
|
-
if (compilerSpecifier !== `npm:typescript@${typescript}` || compatibilitySpecifier !== version) throw new Error("split TypeScript layout differs from shared versions");
|
|
3227
|
-
return {
|
|
3228
|
-
type: "split-compatibility",
|
|
3229
|
-
compilerPackage,
|
|
3230
|
-
compilerSpecifier,
|
|
3231
|
-
compatibilityPackage,
|
|
3232
|
-
compatibilitySpecifier,
|
|
3233
|
-
typecheckCommand
|
|
3234
|
-
};
|
|
3235
|
-
}
|
|
3236
|
-
default: throw new Error("unsupported TypeScript install layout");
|
|
3237
|
-
}
|
|
3238
|
-
});
|
|
3239
|
-
const actionsInput = input["actions"];
|
|
3240
|
-
if (!record$1(actionsInput) || Object.keys(actionsInput).length === 0) throw new Error("missing approved actions");
|
|
3241
|
-
const actions = {};
|
|
3242
|
-
for (const [name, action] of Object.entries(actionsInput)) {
|
|
3243
|
-
if (!record$1(action)) throw new Error(`invalid action policy: ${name}`);
|
|
3244
|
-
const sha = string(action, "sha");
|
|
3245
|
-
const actionVersion = string(action, "version");
|
|
3246
|
-
if (!/^[a-f0-9]{40}$/.test(sha) || !/^v\d+(?:\.\d+)*$/.test(actionVersion)) throw new Error(`invalid action SHA/version: ${name}`);
|
|
3247
|
-
actions[name] = {
|
|
3248
|
-
sha,
|
|
3249
|
-
version: actionVersion
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
const dependabotInput = input["dependabot"];
|
|
3253
|
-
if (!record$1(dependabotInput) || !record$1(dependabotInput["schedule"]) || !record$1(dependabotInput["cooldown"]) || !record$1(dependabotInput["groups"])) throw new Error("missing Dependabot policy");
|
|
3254
|
-
const scheduleInput = dependabotInput["schedule"];
|
|
3255
|
-
const schedule = {
|
|
3256
|
-
interval: string(scheduleInput, "interval"),
|
|
3257
|
-
day: string(scheduleInput, "day"),
|
|
3258
|
-
time: string(scheduleInput, "time"),
|
|
3259
|
-
timezone: string(scheduleInput, "timezone")
|
|
3260
|
-
};
|
|
3261
|
-
const defaultDays = dependabotInput["cooldown"]["defaultDays"];
|
|
3262
|
-
if (typeof defaultDays !== "number" || !Number.isInteger(defaultDays) || defaultDays < 5) throw new Error("Dependabot cooldown must be at least five days");
|
|
3263
|
-
const strings = (value) => {
|
|
3264
|
-
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) throw new Error("Dependabot group values must be string arrays");
|
|
3265
|
-
return value;
|
|
3266
|
-
};
|
|
3267
|
-
const groups = {};
|
|
3268
|
-
for (const [name, group] of Object.entries(dependabotInput["groups"])) {
|
|
3269
|
-
if (!record$1(group)) throw new Error(`invalid Dependabot group: ${name}`);
|
|
3270
|
-
groups[name] = {
|
|
3271
|
-
patterns: strings(group["patterns"]),
|
|
3272
|
-
updateTypes: strings(group["updateTypes"])
|
|
3273
|
-
};
|
|
3274
|
-
}
|
|
3275
|
-
return {
|
|
3276
|
-
bun,
|
|
3277
|
-
typescript,
|
|
3278
|
-
node,
|
|
3279
|
-
postgres,
|
|
3280
|
-
valkey,
|
|
3281
|
-
valkeyArtifacts,
|
|
3282
|
-
python,
|
|
3283
|
-
rust,
|
|
3284
|
-
rustCompilerDevelopment,
|
|
3285
|
-
packages,
|
|
3286
|
-
actions,
|
|
3287
|
-
typescriptInstallLayouts,
|
|
3288
|
-
typescript6Compatibility: {
|
|
3289
|
-
version,
|
|
3290
|
-
packageAlias
|
|
3291
|
-
},
|
|
3292
|
-
dependabot: {
|
|
3293
|
-
schedule,
|
|
3294
|
-
cooldown: { defaultDays },
|
|
3295
|
-
groups,
|
|
3296
|
-
ignoredPackages: [.../* @__PURE__ */ new Set([
|
|
3297
|
-
...Object.keys(packages),
|
|
3298
|
-
...typescriptInstallLayouts.flatMap((layout) => layout.type === "direct" ? [layout.compilerPackage] : [layout.compilerPackage, layout.compatibilityPackage]),
|
|
3299
|
-
packageAlias,
|
|
3300
|
-
"bun-types"
|
|
3301
|
-
])],
|
|
3302
|
-
ignoredActions: Object.keys(actions),
|
|
3303
|
-
ignoredImages: [
|
|
3304
|
-
"node",
|
|
3305
|
-
"python",
|
|
3306
|
-
"oven/bun"
|
|
3307
|
-
]
|
|
3308
|
-
}
|
|
3309
|
-
};
|
|
3310
|
-
};
|
|
3311
|
-
//#endregion
|
|
3312
2525
|
//#region src/toolchain-guard.ts
|
|
3313
2526
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3314
2527
|
...packageRules,
|
|
@@ -3320,7 +2533,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3320
2533
|
const array = (value) => Array.isArray(value);
|
|
3321
2534
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3322
2535
|
const parseToolchainConfiguration = (input) => {
|
|
3323
|
-
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors and
|
|
2536
|
+
if (!record(input) || Object.keys(input).some((key) => key !== "optOuts" && key !== "cloud" && key !== "engineFloors" && key !== "dynamicSelectors" && key !== "consumerChecks") || input["optOuts"] !== void 0 && !array(input["optOuts"])) throw new Error("stll-toolchain.json accepts only optOuts, cloud, engineFloors, dynamicSelectors and consumerChecks declarations");
|
|
3324
2537
|
const disabled = /* @__PURE__ */ new Set();
|
|
3325
2538
|
const optOuts = input["optOuts"] ?? [];
|
|
3326
2539
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3332,9 +2545,10 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3332
2545
|
}
|
|
3333
2546
|
return {
|
|
3334
2547
|
disabled,
|
|
3335
|
-
cloud: parseCloudSetup(input["cloud"]),
|
|
2548
|
+
cloud: require_toolchain_inputs.parseCloudSetup(input["cloud"]),
|
|
3336
2549
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3337
|
-
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"])
|
|
2550
|
+
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
2551
|
+
consumerChecks: parseConsumerChecks(input["consumerChecks"])
|
|
3338
2552
|
};
|
|
3339
2553
|
};
|
|
3340
2554
|
/** Read one tracked configuration snapshot for guards and policy generation. */
|
|
@@ -3352,7 +2566,7 @@ const readToolchainInputs = (root) => {
|
|
|
3352
2566
|
const trackedFiles = new Set(tracked);
|
|
3353
2567
|
const resolvedRoot = (0, node_fs.realpathSync)(root);
|
|
3354
2568
|
for (const file of tracked) {
|
|
3355
|
-
const kind = toolchainInputKind(file);
|
|
2569
|
+
const kind = require_toolchain_inputs.toolchainInputKind(file);
|
|
3356
2570
|
if (kind === void 0) continue;
|
|
3357
2571
|
if (kind === "presence") {
|
|
3358
2572
|
files[file] = "";
|
|
@@ -3397,14 +2611,11 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3397
2611
|
}).trim();
|
|
3398
2612
|
repository = /^(?:https?:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(origin)?.[1];
|
|
3399
2613
|
} catch {}
|
|
3400
|
-
let
|
|
3401
|
-
let cloud;
|
|
2614
|
+
let configuration = parseToolchainConfiguration({});
|
|
3402
2615
|
const engineFloors = [];
|
|
3403
2616
|
let dynamicSelectors = [];
|
|
3404
2617
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
3405
|
-
|
|
3406
|
-
disabled = configuration.disabled;
|
|
3407
|
-
cloud = configuration.cloud;
|
|
2618
|
+
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
3408
2619
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
3409
2620
|
for (const entry of configuration.engineFloors) engineFloors.push(resolveEngineFloor(entry, files));
|
|
3410
2621
|
} catch (error) {
|
|
@@ -3415,30 +2626,31 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3415
2626
|
message: error instanceof Error ? error.message : String(error)
|
|
3416
2627
|
});
|
|
3417
2628
|
}
|
|
3418
|
-
const
|
|
2629
|
+
const cloud = configuration.cloud;
|
|
2630
|
+
const script = files[require_toolchain_inputs.cloudSetupPath];
|
|
3419
2631
|
if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
|
|
3420
2632
|
rule: "cloud-setup-drift",
|
|
3421
|
-
path: cloudSetupPath,
|
|
2633
|
+
path: require_toolchain_inputs.cloudSetupPath,
|
|
3422
2634
|
line: 1,
|
|
3423
2635
|
message: "cloud setup script requires an explicit cloud declaration in stll-toolchain.json"
|
|
3424
2636
|
});
|
|
3425
2637
|
if (cloud !== void 0) try {
|
|
3426
2638
|
const nodeVersion = files[".node-version"];
|
|
3427
2639
|
if (nodeVersion === void 0) throw new Error("cloud setup requires a tracked root .node-version with an exact stable release");
|
|
3428
|
-
if (script !== generateCloudSetup({
|
|
2640
|
+
if (script !== require_toolchain_inputs.generateCloudSetup({
|
|
3429
2641
|
policy,
|
|
3430
2642
|
cloud,
|
|
3431
2643
|
nodeVersion: nodeVersion.trim()
|
|
3432
2644
|
})) diagnostics.push({
|
|
3433
2645
|
rule: "cloud-setup-drift",
|
|
3434
|
-
path: cloudSetupPath,
|
|
2646
|
+
path: require_toolchain_inputs.cloudSetupPath,
|
|
3435
2647
|
line: 1,
|
|
3436
2648
|
message: "cloud setup script is missing or differs from shared policy; run stll-cloud-setup"
|
|
3437
2649
|
});
|
|
3438
2650
|
} catch (error) {
|
|
3439
2651
|
diagnostics.push({
|
|
3440
2652
|
rule: "cloud-setup-drift",
|
|
3441
|
-
path: cloudSetupPath,
|
|
2653
|
+
path: require_toolchain_inputs.cloudSetupPath,
|
|
3442
2654
|
line: 1,
|
|
3443
2655
|
message: error instanceof Error ? error.message : String(error)
|
|
3444
2656
|
});
|
|
@@ -3446,6 +2658,11 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3446
2658
|
const exercisedFloors = /* @__PURE__ */ new Set();
|
|
3447
2659
|
const mismatchedFloors = /* @__PURE__ */ new Set();
|
|
3448
2660
|
const selectorMatches = /* @__PURE__ */ new Map();
|
|
2661
|
+
diagnostics.push(...checkConsumerChecks({
|
|
2662
|
+
declarations: configuration.consumerChecks,
|
|
2663
|
+
files,
|
|
2664
|
+
policy
|
|
2665
|
+
}));
|
|
3449
2666
|
diagnostics.push(...checkPackageFiles({
|
|
3450
2667
|
files,
|
|
3451
2668
|
policy
|
|
@@ -3488,7 +2705,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3488
2705
|
files,
|
|
3489
2706
|
policy: policy.dependabot
|
|
3490
2707
|
}));
|
|
3491
|
-
return diagnostics.filter((diagnostic) => !disabled.has(diagnostic.rule));
|
|
2708
|
+
return diagnostics.filter((diagnostic) => !configuration.disabled.has(diagnostic.rule));
|
|
3492
2709
|
};
|
|
3493
2710
|
//#endregion
|
|
3494
2711
|
Object.defineProperty(exports, "checkToolchain", {
|
|
@@ -3497,30 +2714,12 @@ Object.defineProperty(exports, "checkToolchain", {
|
|
|
3497
2714
|
return checkToolchain;
|
|
3498
2715
|
}
|
|
3499
2716
|
});
|
|
3500
|
-
Object.defineProperty(exports, "cloudSetupPath", {
|
|
3501
|
-
enumerable: true,
|
|
3502
|
-
get: function() {
|
|
3503
|
-
return cloudSetupPath;
|
|
3504
|
-
}
|
|
3505
|
-
});
|
|
3506
|
-
Object.defineProperty(exports, "generateCloudSetup", {
|
|
3507
|
-
enumerable: true,
|
|
3508
|
-
get: function() {
|
|
3509
|
-
return generateCloudSetup;
|
|
3510
|
-
}
|
|
3511
|
-
});
|
|
3512
2717
|
Object.defineProperty(exports, "parseToolchainConfiguration", {
|
|
3513
2718
|
enumerable: true,
|
|
3514
2719
|
get: function() {
|
|
3515
2720
|
return parseToolchainConfiguration;
|
|
3516
2721
|
}
|
|
3517
2722
|
});
|
|
3518
|
-
Object.defineProperty(exports, "parseToolchainPolicy", {
|
|
3519
|
-
enumerable: true,
|
|
3520
|
-
get: function() {
|
|
3521
|
-
return parseToolchainPolicy;
|
|
3522
|
-
}
|
|
3523
|
-
});
|
|
3524
2723
|
Object.defineProperty(exports, "readToolchainInputs", {
|
|
3525
2724
|
enumerable: true,
|
|
3526
2725
|
get: function() {
|