@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.mjs
CHANGED
|
@@ -1,758 +1,238 @@
|
|
|
1
|
+
import { _ as nodeSupportRangeMatches, a as isPythonDependencyManifest, c as toolchainInputKind, d as isKubernetesDefinitionPath, f as workspaceContains, g as nodeSelectorMatches, i as isMiseConfigPath, l as containerDocumentImages, m as generateCloudSetup, n as isDependabotGithubActionsPath, o as javascriptDependencyLockfiles, p as cloudSetupPath, r as isDockerDefinitionPath, s as pythonDependencyManifestKind, t as githubAutomationFileKind, u as isComposeDefinitionPath, v as parseCloudSetup } from "./toolchain-inputs.mjs";
|
|
2
|
+
import { _ as parseConsumerFixtures, d as consumerPackageClosure, g as discoverConsumerPackages, i as assertConsumerFixtureKind, n as assertConsumerNodeSupport, o as assertConsumerFixtureSelection } from "./toolchain-schema.mjs";
|
|
1
3
|
import { readFileSync, realpathSync } from "node:fs";
|
|
2
4
|
import { execFileSync } from "node:child_process";
|
|
3
5
|
import path from "node:path";
|
|
4
|
-
import { minVersion, satisfies,
|
|
5
|
-
import picomatch from "picomatch";
|
|
6
|
-
import { parse } from "smol-toml";
|
|
6
|
+
import { minVersion, satisfies, valid, validRange } from "semver";
|
|
7
7
|
import { LineCounter, isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, parseDocument } from "yaml";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# Prevent package maintainer scripts from starting unmanaged system services.
|
|
11
|
-
apt_install() (
|
|
12
|
-
# EXIT runs after function locals expire; this subshell owns the trap state.
|
|
13
|
-
backup=''
|
|
14
|
-
policy_state=untouched
|
|
15
|
-
[[ ! -L /var/lock/stll-cloud-apt.lock ]] || fail 'Invalid package install lock'
|
|
16
|
-
exec 8>/var/lock/stll-cloud-apt.lock
|
|
17
|
-
flock -x -w 120 8 || fail 'Another package installation is active'
|
|
18
|
-
[[ ! -L /usr/sbin/policy-rc.d ]] || fail 'Unsupported service-start policy symlink'
|
|
19
|
-
[[ ! -e /usr/sbin/policy-rc.d || -f /usr/sbin/policy-rc.d ]] || fail 'Unsupported service-start policy file'
|
|
20
|
-
backup="$(mktemp -d)"
|
|
21
|
-
cleanup_package_policy() {
|
|
22
|
-
local status=$?
|
|
23
|
-
trap - EXIT HUP INT TERM
|
|
24
|
-
if [[ "$policy_state" == installed ]]; then
|
|
25
|
-
if [[ -f "$backup/original" ]]; then
|
|
26
|
-
if ! cp -p "$backup/original" /usr/sbin/policy-rc.d; then status=1; fi
|
|
27
|
-
else
|
|
28
|
-
if ! rm -f /usr/sbin/policy-rc.d; then status=1; fi
|
|
29
|
-
fi
|
|
30
|
-
fi
|
|
31
|
-
rm -rf -- "$backup"
|
|
32
|
-
exit "$status"
|
|
33
|
-
}
|
|
34
|
-
trap cleanup_package_policy EXIT
|
|
35
|
-
trap 'exit 129' HUP
|
|
36
|
-
trap 'exit 130' INT
|
|
37
|
-
trap 'exit 143' TERM
|
|
38
|
-
if [[ -e /usr/sbin/policy-rc.d ]]; then cp -p /usr/sbin/policy-rc.d "$backup/original"; fi
|
|
39
|
-
policy_state=installed
|
|
40
|
-
printf '%s\n' '#!/bin/sh' 'exit 101' > /usr/sbin/policy-rc.d
|
|
41
|
-
chmod 755 /usr/sbin/policy-rc.d
|
|
42
|
-
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@"
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
`;
|
|
46
|
-
const postgresSourceLine = "deb [signed-by=/usr/share/keyrings/stll-pgdg.asc] https://apt.postgresql.org/pub/repos/apt noble-pgdg main";
|
|
47
|
-
const postgresSigningFingerprint = "B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8";
|
|
48
|
-
const postgresSources = String.raw`
|
|
49
|
-
pgdg_key_fingerprint() {
|
|
50
|
-
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 }'
|
|
51
|
-
}
|
|
52
|
-
pgdg_sources_ready() {
|
|
53
|
-
local managed="$1" file mode records suite keyring override fingerprint selected_keyring=''
|
|
54
|
-
shift
|
|
55
|
-
PGDG_SOURCE_STATE=missing
|
|
56
|
-
[[ ! -L "$managed" ]] || fail "Managed PGDG source must not be a symlink: $managed"
|
|
57
|
-
for file in "$@"; do
|
|
58
|
-
[[ -e "$file" ]] || continue
|
|
59
|
-
[[ -f "$file" ]] || fail "APT source must be a regular file: $file"
|
|
60
|
-
case "$file" in *.sources) mode=sources ;; *) mode=list ;; esac
|
|
61
|
-
records="$(awk -v mode="$mode" '
|
|
62
|
-
function trim(value) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); return value }
|
|
63
|
-
function pgdg_uri(uri) {
|
|
64
|
-
uri = tolower(uri)
|
|
65
|
-
if (uri !~ /^[a-z][a-z0-9+.-]*:\/\//) return 0
|
|
66
|
-
sub(/^[a-z][a-z0-9+.-]*:\/\//, "", uri)
|
|
67
|
-
sub(/[\/?#].*$/, "", uri)
|
|
68
|
-
sub(/^.*@/, "", uri)
|
|
69
|
-
sub(/:.*$/, "", uri)
|
|
70
|
-
return uri == "apt.postgresql.org" || uri == "apt.postgresql.org."
|
|
71
|
-
}
|
|
72
|
-
function contains_pgdg(value, tokens, count, token_index) {
|
|
73
|
-
count = split(value, tokens, /[[:space:]]+/)
|
|
74
|
-
for (token_index = 1; token_index <= count; token_index++) if (pgdg_uri(tokens[token_index])) return 1
|
|
75
|
-
return 0
|
|
76
|
-
}
|
|
77
|
-
function unsafe_field(name) {
|
|
78
|
-
return name ~ /^(trusted|allow-insecure|allow-weak|allow-downgrade-to-insecure)$/
|
|
79
|
-
}
|
|
80
|
-
function disabled_override(value) {
|
|
81
|
-
value = tolower(trim(value))
|
|
82
|
-
gsub(/^["\047]|["\047]$/, "", value)
|
|
83
|
-
return value ~ /^(no|false|without|off|disable|0)$/
|
|
84
|
-
}
|
|
85
|
-
function stanza(override_index) {
|
|
86
|
-
override = 0
|
|
87
|
-
for (override_index = 1; override_index <= override_count; override_index++) {
|
|
88
|
-
if (!disabled_override(override_values[override_index])) override = 1
|
|
89
|
-
delete override_values[override_index]
|
|
90
|
-
}
|
|
91
|
-
if (tolower(trim(enabled)) != "no" && pgdg) print (trim(suites) == "" ? "-" : trim(suites)) "\t" (trim(keyring) == "" ? "-" : trim(keyring)) "\t" (override ? "unsafe" : "safe")
|
|
92
|
-
enabled = ""; pgdg = 0; field = ""; suites = ""; keyring = ""; override = 0; override_count = 0
|
|
93
|
-
}
|
|
94
|
-
/^[[:space:]]*#/ { if (mode == "list" || $0 ~ /^#/) next }
|
|
95
|
-
{
|
|
96
|
-
line = $0
|
|
97
|
-
if (mode == "list") {
|
|
98
|
-
sub(/[[:space:]]*#.*$/, "", line)
|
|
99
|
-
if (line !~ /^[[:space:]]*deb(-src)?[[:space:]]/ || !contains_pgdg(line)) next
|
|
100
|
-
keyring = ""; override = 0
|
|
101
|
-
if (match(line, /\[[^]]*\]/)) {
|
|
102
|
-
options = substr(line, RSTART + 1, RLENGTH - 2)
|
|
103
|
-
count = split(options, tokens, /[[:space:]]+/)
|
|
104
|
-
for (token_index = 1; token_index <= count; token_index++) {
|
|
105
|
-
if (tolower(tokens[token_index]) ~ /^signed-by=/) { keyring = tokens[token_index]; sub(/^[^=]*=/, "", keyring) }
|
|
106
|
-
option_name = tolower(tokens[token_index]); sub(/=.*/, "", option_name)
|
|
107
|
-
option_value = tokens[token_index]; sub(/^[^=]*=/, "", option_value)
|
|
108
|
-
if (unsafe_field(option_name) && !disabled_override(option_value)) override = 1
|
|
109
|
-
}
|
|
110
|
-
sub(/\[[^]]*\]/, "", line)
|
|
111
|
-
}
|
|
112
|
-
count = split(line, tokens, /[[:space:]]+/)
|
|
113
|
-
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")
|
|
114
|
-
next
|
|
115
|
-
}
|
|
116
|
-
if (line ~ /^[[:space:]]*$/) { stanza(); next }
|
|
117
|
-
if (line ~ /^[[:space:]]/) {
|
|
118
|
-
if (field == "uris" && contains_pgdg(line)) pgdg = 1
|
|
119
|
-
if (field == "enabled") enabled = enabled " " line
|
|
120
|
-
if (field == "suites") suites = suites " " line
|
|
121
|
-
if (field == "signed-by") keyring = keyring " " line
|
|
122
|
-
if (unsafe_field(field)) override_values[override_count] = override_values[override_count] " " line
|
|
123
|
-
next
|
|
124
|
-
}
|
|
125
|
-
field = tolower(line)
|
|
126
|
-
sub(/:.*/, "", field)
|
|
127
|
-
value = line
|
|
128
|
-
sub(/^[^:]*:[[:space:]]*/, "", value)
|
|
129
|
-
if (field == "uris" && contains_pgdg(value)) pgdg = 1
|
|
130
|
-
if (field == "enabled") enabled = value
|
|
131
|
-
if (field == "suites") suites = value
|
|
132
|
-
if (field == "signed-by") keyring = value
|
|
133
|
-
if (unsafe_field(field)) override_values[++override_count] = value
|
|
134
|
-
}
|
|
135
|
-
END { if (mode == "sources") stanza() }
|
|
136
|
-
' "$file")" || fail "Cannot inspect APT source: $file"
|
|
137
|
-
if [[ "$file" == "$managed" && -z "$records" ]]; then
|
|
138
|
-
fail "Managed PGDG source has no active PGDG entry: $file"
|
|
139
|
-
fi
|
|
140
|
-
while IFS=$'\t' read -r suite keyring override; do
|
|
141
|
-
[[ -n "$suite" ]] || continue
|
|
142
|
-
[[ "$override" == safe ]] || fail "PGDG source enables an unsafe trust override: $file"
|
|
143
|
-
[[ "$suite" == noble-pgdg && "$keyring" == /* && ! "$keyring" =~ [[:space:],] ]] || fail "PGDG source requires noble-pgdg and one Signed-By keyring: $file"
|
|
144
|
-
[[ -z "$selected_keyring" || "$selected_keyring" == "$keyring" ]] || fail "Conflicting PGDG Signed-By keyrings: $file"
|
|
145
|
-
fingerprint="$(pgdg_key_fingerprint "$keyring")" || fail "Cannot verify PGDG Signed-By keyring: $file"
|
|
146
|
-
[[ "$fingerprint" == @PGDG_FINGERPRINT@ ]] || fail "PGDG Signed-By fingerprint mismatch: $file"
|
|
147
|
-
selected_keyring="$keyring"
|
|
148
|
-
PGDG_SOURCE_STATE=reuse
|
|
149
|
-
done <<< "$records"
|
|
150
|
-
done
|
|
151
|
-
}
|
|
152
|
-
`;
|
|
153
|
-
const postgresInstall = String.raw`
|
|
154
|
-
postgres_ready() {
|
|
155
|
-
local binary
|
|
156
|
-
for binary in postgres initdb pg_ctl psql createdb; do
|
|
157
|
-
[[ -x "/usr/lib/postgresql/@POSTGRES@/bin/$binary" ]] || return 1
|
|
158
|
-
done
|
|
159
|
-
/usr/lib/postgresql/@POSTGRES@/bin/postgres --version | grep -Eq '^postgres \(PostgreSQL\) @POSTGRES@\.'
|
|
160
|
-
}
|
|
161
|
-
if ! postgres_ready; then
|
|
162
|
-
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
|
|
163
|
-
if [[ "$PGDG_SOURCE_STATE" == missing ]]; then
|
|
164
|
-
local key key_fingerprint
|
|
165
|
-
key="$(mktemp)"
|
|
166
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 https://www.postgresql.org/media/keys/ACCC4CF8.asc -o "$key"
|
|
167
|
-
key_fingerprint="$(pgdg_key_fingerprint "$key")"
|
|
168
|
-
[[ "$key_fingerprint" == @PGDG_FINGERPRINT@ ]] || fail 'PostgreSQL signing key mismatch'
|
|
169
|
-
install -d -m 755 /usr/share/keyrings
|
|
170
|
-
install -m 644 "$key" /usr/share/keyrings/stll-pgdg.asc
|
|
171
|
-
rm -- "$key"
|
|
172
|
-
printf '%s\n' '@PGDG_SOURCE@' > /etc/apt/sources.list.d/stll-pgdg.list
|
|
173
|
-
fi
|
|
174
|
-
apt-get update
|
|
175
|
-
apt_install postgresql-@POSTGRES@ postgresql-client-@POSTGRES@
|
|
176
|
-
fi
|
|
177
|
-
postgres_ready || fail 'PostgreSQL toolchain is incomplete or differs from the policy major'
|
|
178
|
-
`;
|
|
179
|
-
const valkeyInstall = String.raw`
|
|
180
|
-
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
|
|
181
|
-
local scratch server_url server_sha tools_url tools_sha package
|
|
182
|
-
scratch="$(mktemp -d)"
|
|
183
|
-
chmod 755 "$scratch"
|
|
184
|
-
case "$ARCH" in
|
|
185
|
-
amd64) server_url='@AMD64_SERVER_URL@'; server_sha='@AMD64_SERVER_SHA@'; tools_url='@AMD64_TOOLS_URL@'; tools_sha='@AMD64_TOOLS_SHA@' ;;
|
|
186
|
-
arm64) server_url='@ARM64_SERVER_URL@'; server_sha='@ARM64_SERVER_SHA@'; tools_url='@ARM64_TOOLS_URL@'; tools_sha='@ARM64_TOOLS_SHA@' ;;
|
|
187
|
-
*) fail 'Unsupported Valkey architecture' ;;
|
|
188
|
-
esac
|
|
189
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "$server_url" -o "$scratch/valkey-server.deb"
|
|
190
|
-
curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 "$tools_url" -o "$scratch/valkey-tools.deb"
|
|
191
|
-
(cd "$scratch"; printf '%s %s\n' "$server_sha" valkey-server.deb "$tools_sha" valkey-tools.deb | sha256sum --strict -c -)
|
|
192
|
-
for package in valkey-server valkey-tools; do
|
|
193
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Package)" == "$package" ]] || fail 'Valkey artifact package mismatch'
|
|
194
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Version)" == '@VALKEY@-1.noble' ]] || fail 'Valkey artifact version mismatch'
|
|
195
|
-
[[ "$(dpkg-deb -f "$scratch/$package.deb" Architecture)" == "$ARCH" ]] || fail 'Valkey artifact architecture mismatch'
|
|
196
|
-
done
|
|
197
|
-
apt-get update
|
|
198
|
-
apt_install "$scratch/valkey-server.deb" "$scratch/valkey-tools.deb"
|
|
199
|
-
rm -rf -- "$scratch"
|
|
200
|
-
fi
|
|
201
|
-
[[ "$(valkey-server --version | sed -nE 's/.*v=([0-9.]+).*/\1/p')" == '@VALKEY@' ]] || fail 'Valkey version differs from policy'
|
|
202
|
-
[[ "$(valkey-cli --version)" == 'valkey-cli @VALKEY@' ]] || fail 'Valkey client version differs from policy'
|
|
203
|
-
`;
|
|
204
|
-
const state = String.raw`
|
|
205
|
-
service_account_ready() {
|
|
206
|
-
local uid gid shell
|
|
207
|
-
uid="$(id -u stll-cloud)" || fail 'Service user is unavailable; run install'
|
|
208
|
-
gid="$(id -g stll-cloud)"
|
|
209
|
-
shell="$(getent passwd stll-cloud | cut -d: -f7)"
|
|
210
|
-
[[ "$uid" =~ ^[1-9][0-9]*$ && "$gid" =~ ^[1-9][0-9]*$ && "$shell" == /usr/sbin/nologin ]] || fail 'Service user must be unprivileged with no login shell'
|
|
211
|
-
}
|
|
212
|
-
prepare_state() {
|
|
213
|
-
service_account_ready
|
|
214
|
-
[[ ! -L /var/lib/stll-cloud ]] || fail 'Service state root must not be a symlink'
|
|
215
|
-
if [[ -e /var/lib/stll-cloud ]]; then
|
|
216
|
-
[[ -d /var/lib/stll-cloud && "$(stat -c '%u:%a' /var/lib/stll-cloud)" == 0:755 ]] || fail 'Invalid service state root'
|
|
217
|
-
else
|
|
218
|
-
install -d -m 755 -o root -g root /var/lib/stll-cloud
|
|
219
|
-
fi
|
|
220
|
-
STATE="/var/lib/stll-cloud/$(printf '%s' "$REPO_ROOT" | sha256sum | cut -d' ' -f1)"
|
|
221
|
-
[[ ! -L "$STATE" ]] || fail 'Service state must not be a symlink'
|
|
222
|
-
if [[ -e "$STATE" ]]; then
|
|
223
|
-
[[ -d "$STATE" && "$(stat -c '%u:%a' "$STATE")" == 0:710 && "$(stat -c '%g' "$STATE")" == "$(id -g stll-cloud)" ]] || fail 'Invalid service state ownership'
|
|
224
|
-
else
|
|
225
|
-
install -d -m 710 -o root -g stll-cloud "$STATE"
|
|
226
|
-
fi
|
|
227
|
-
[[ ! -L "$STATE/lock" ]] || fail 'Service lock must not be a symlink'
|
|
228
|
-
exec 9>"$STATE/lock"
|
|
229
|
-
flock -x -w 10 9 || fail 'Another setup process owns the service state'
|
|
230
|
-
}
|
|
231
|
-
`;
|
|
232
|
-
const renderCloudServiceInstall = ({ services, postgres, valkey, valkeyArtifacts }) => {
|
|
233
|
-
const parts = services.map((service) => {
|
|
234
|
-
switch (service) {
|
|
235
|
-
case "postgres": return postgresInstall.replaceAll("@POSTGRES@", postgres).replaceAll("@PGDG_SOURCE@", postgresSourceLine).replaceAll("@PGDG_FINGERPRINT@", postgresSigningFingerprint);
|
|
236
|
-
case "valkey": {
|
|
237
|
-
const replacements = /* @__PURE__ */ new Map([
|
|
238
|
-
["@VALKEY@", valkey],
|
|
239
|
-
["@AMD64_SERVER_URL@", valkeyArtifacts.amd64.server.url],
|
|
240
|
-
["@AMD64_SERVER_SHA@", valkeyArtifacts.amd64.server.sha256],
|
|
241
|
-
["@AMD64_TOOLS_URL@", valkeyArtifacts.amd64.tools.url],
|
|
242
|
-
["@AMD64_TOOLS_SHA@", valkeyArtifacts.amd64.tools.sha256],
|
|
243
|
-
["@ARM64_SERVER_URL@", valkeyArtifacts.arm64.server.url],
|
|
244
|
-
["@ARM64_SERVER_SHA@", valkeyArtifacts.arm64.server.sha256],
|
|
245
|
-
["@ARM64_TOOLS_URL@", valkeyArtifacts.arm64.tools.url],
|
|
246
|
-
["@ARM64_TOOLS_SHA@", valkeyArtifacts.arm64.tools.sha256]
|
|
247
|
-
]);
|
|
248
|
-
return valkeyInstall.replace(/@[A-Z0-9_]+@/g, (token) => {
|
|
249
|
-
const value = replacements.get(token);
|
|
250
|
-
if (value === void 0) throw new Error(`Missing Valkey artifact field: ${token}`);
|
|
251
|
-
return value;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
default: throw new Error(`Unsupported service: ${String(service)}`);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
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";
|
|
258
|
-
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`;
|
|
259
|
-
};
|
|
8
|
+
import picomatch from "picomatch";
|
|
9
|
+
import { parse as parse$2 } from "smol-toml";
|
|
260
10
|
//#endregion
|
|
261
|
-
//#region src/
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
11
|
+
//#region src/toolchain-consumer-checks.ts
|
|
12
|
+
const consumerRunnerVersion = "0.14.1";
|
|
13
|
+
const consumerCheckWorkflow = "stella/.github/.github/workflows/package-consumer-compat.yml";
|
|
14
|
+
const record$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
const repositoryDirectory = (value) => typeof value === "string" && value !== "" && !value.includes("\\") && !value.includes("${{") && !value.split("").some((character) => {
|
|
16
|
+
const code = character.charCodeAt(0);
|
|
17
|
+
return code < 32 || code === 127;
|
|
18
|
+
}) && !/^[A-Za-z]:/.test(value) && !path.posix.isAbsolute(value) && (value === "." || value.split("/").every((part) => part !== "" && part !== "." && part !== ".."));
|
|
19
|
+
const packageDirectories = (value) => Array.isArray(value) && value.length > 0 && value.every(repositoryDirectory) && new Set(value).size === value.length;
|
|
20
|
+
const cronFields = [
|
|
21
|
+
{
|
|
22
|
+
minimum: 0,
|
|
23
|
+
maximum: 59
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
minimum: 0,
|
|
27
|
+
maximum: 23
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
minimum: 1,
|
|
31
|
+
maximum: 31
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
minimum: 1,
|
|
35
|
+
maximum: 12,
|
|
36
|
+
names: Object.fromEntries([
|
|
37
|
+
"JAN",
|
|
38
|
+
"FEB",
|
|
39
|
+
"MAR",
|
|
40
|
+
"APR",
|
|
41
|
+
"MAY",
|
|
42
|
+
"JUN",
|
|
43
|
+
"JUL",
|
|
44
|
+
"AUG",
|
|
45
|
+
"SEP",
|
|
46
|
+
"OCT",
|
|
47
|
+
"NOV",
|
|
48
|
+
"DEC"
|
|
49
|
+
].map((name, index) => [name, index + 1]))
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
minimum: 0,
|
|
53
|
+
maximum: 6,
|
|
54
|
+
names: Object.fromEntries([
|
|
55
|
+
"SUN",
|
|
56
|
+
"MON",
|
|
57
|
+
"TUE",
|
|
58
|
+
"WED",
|
|
59
|
+
"THU",
|
|
60
|
+
"FRI",
|
|
61
|
+
"SAT"
|
|
62
|
+
].map((name, index) => [name, index]))
|
|
286
63
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
install: cloudInstallCommand,
|
|
293
|
-
envFile
|
|
64
|
+
];
|
|
65
|
+
const cronFieldValues = (text, field) => {
|
|
66
|
+
const value = (token) => {
|
|
67
|
+
const number = /^\d+$/.test(token) ? Number(token) : field.names?.[token.toUpperCase()];
|
|
68
|
+
return number !== void 0 && Number.isSafeInteger(number) && number >= field.minimum && number <= field.maximum ? number : void 0;
|
|
294
69
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
service_password() {
|
|
316
|
-
local file="$1"
|
|
317
|
-
service_file "$file" 0
|
|
318
|
-
if [[ ! -e "$file" ]]; then
|
|
319
|
-
(umask 077; openssl rand -hex 32 > "$file")
|
|
320
|
-
root chown root:root "$file"
|
|
321
|
-
fi
|
|
322
|
-
[[ "$(cat "$file")" =~ ^[0-9a-f]{64}$ ]] || fail "Invalid service credential state"
|
|
323
|
-
}
|
|
324
|
-
service_process_matches() {
|
|
325
|
-
local pid="$1" executable="$2" owner actual expected
|
|
326
|
-
[[ "$pid" =~ ^[1-9][0-9]*$ && -d "/proc/$pid" ]] || return 1
|
|
327
|
-
owner="$(stat -c '%u' "/proc/$pid" 2>/dev/null)" || return 2
|
|
328
|
-
[[ "$owner" =~ ^[0-9]+$ ]] || return 2
|
|
329
|
-
[[ "$owner" == "$SERVICE_UID" ]] || return 1
|
|
330
|
-
actual="$(readlink -f "/proc/$pid/exe" 2>/dev/null)" || return 2
|
|
331
|
-
expected="$(readlink -f "$executable")" || return 2
|
|
332
|
-
[[ -n "$actual" && -n "$expected" && "$actual" != *' (deleted)' ]] || return 2
|
|
333
|
-
[[ "$actual" == "$expected" ]]
|
|
334
|
-
}
|
|
335
|
-
service_process() {
|
|
336
|
-
service_process_matches "$1" "$2" || fail "Managed service process identity differs from the recorded state"
|
|
337
|
-
}
|
|
338
|
-
service_prune_pid() {
|
|
339
|
-
local file="$1" executable="$2" service="$3" pid identity
|
|
340
|
-
[[ -e "$file" ]] || return 0
|
|
341
|
-
pid="$(head -n 1 "$file")"
|
|
342
|
-
# PostgreSQL encodes a standalone backend with a negative PID.
|
|
343
|
-
if [[ "$service" == postgres ]]; then pid="$(printf '%s' "$pid" | sed 's/^-//')"; fi
|
|
344
|
-
if service_process_matches "$pid" "$executable"; then return 0; else identity=$?; fi
|
|
345
|
-
[[ "$identity" != 2 ]] || fail "Cannot verify recorded live service process; retaining its PID file"
|
|
346
|
-
# Let PostgreSQL check orphan shared memory before removing a dead-PID lock.
|
|
347
|
-
if [[ "$service" == postgres && "$pid" =~ ^[1-9][0-9]*$ && ! -d "/proc/$pid" ]]; then return 0; fi
|
|
348
|
-
if [[ "$service" == postgres ]]; then
|
|
349
|
-
[[ "$pid" =~ ^[1-9][0-9]*$ && "$SERVICE_UID" != 0 ]] || fail "Cannot safely recover PostgreSQL PID state"
|
|
350
|
-
# The root controller is not our nonroot postgres. Preserve the remaining
|
|
351
|
-
# lock rows so PostgreSQL's EPERM/ancestor path still checks orphan memory.
|
|
352
|
-
root sed -i "1s/.*/$$/" "$file"
|
|
353
|
-
return 0
|
|
354
|
-
fi
|
|
355
|
-
# Restored state can name an unrelated live process; never signal that PID.
|
|
356
|
-
root rm -- "$file"
|
|
357
|
-
}
|
|
358
|
-
`;
|
|
359
|
-
const postgresStart = String.raw`
|
|
360
|
-
local pg_bin='/usr/lib/postgresql/@POSTGRES@/bin' pg_data="$STATE/postgres" pg_password pg_details pg_pid pg_version
|
|
361
|
-
service_password "$STATE/postgres.password"
|
|
362
|
-
pg_password="$(cat "$STATE/postgres.password")"
|
|
363
|
-
service_directory "$pg_data"
|
|
364
|
-
for file in PG_VERSION postmaster.pid postgresql.conf pg_hba.conf pg_ident.conf postgresql.auto.conf; do
|
|
365
|
-
service_file "$pg_data/$file" "$SERVICE_UID"
|
|
366
|
-
done
|
|
367
|
-
if [[ ! -e "$pg_data/PG_VERSION" ]]; then
|
|
368
|
-
[[ -z "$(ls -A "$pg_data")" ]] || fail "PostgreSQL data directory is not initialized"
|
|
369
|
-
service_file "$STATE/postgres.init-password" "$SERVICE_UID"
|
|
370
|
-
(umask 077; printf '%s\n' "$pg_password" > "$STATE/postgres.init-password")
|
|
371
|
-
root chown stll-cloud:stll-cloud "$STATE/postgres.init-password"
|
|
372
|
-
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"
|
|
373
|
-
root rm "$STATE/postgres.init-password"
|
|
374
|
-
fi
|
|
375
|
-
[[ -f "$pg_data/PG_VERSION" && "$(cat "$pg_data/PG_VERSION")" == '@POSTGRES@' ]] || fail "PostgreSQL data has a different major version"
|
|
376
|
-
service_prune_pid "$pg_data/postmaster.pid" "$pg_bin/postgres" postgres
|
|
377
|
-
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
|
|
378
|
-
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"
|
|
379
|
-
fi
|
|
380
|
-
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"
|
|
381
|
-
pg_version="$(printf '%s\n' "$pg_details" | cut -d '|' -f4)"
|
|
382
|
-
[[ "$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"
|
|
383
|
-
(( pg_version / 10000 == @POSTGRES@ )) || fail "PostgreSQL endpoint has a different major version"
|
|
384
|
-
pg_pid="$(head -n 1 "$pg_data/postmaster.pid")"
|
|
385
|
-
service_process "$pg_pid" "$pg_bin/postgres"
|
|
386
|
-
[[ "$(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"
|
|
387
|
-
DATABASE_URL="postgresql://stll_cloud:$pg_password@127.0.0.1:55432/cloud_test"
|
|
388
|
-
`;
|
|
389
|
-
const valkeyStart = String.raw`
|
|
390
|
-
local vk_data="$STATE/valkey" vk_password vk_config vk_info vk_pid vk_expected ready=0
|
|
391
|
-
service_password "$STATE/valkey.password"
|
|
392
|
-
vk_password="$(cat "$STATE/valkey.password")"
|
|
393
|
-
service_directory "$vk_data"
|
|
394
|
-
vk_config="$vk_data/valkey.conf"
|
|
395
|
-
service_file "$vk_config" "$SERVICE_UID"
|
|
396
|
-
for file in valkey.pid valkey.log dump.rdb; do
|
|
397
|
-
[[ ! -L "$vk_data/$file" ]] || fail "Valkey state must not contain symbolic links"
|
|
398
|
-
if [[ -e "$vk_data/$file" ]]; then
|
|
399
|
-
[[ -f "$vk_data/$file" && "$(stat -c '%u' "$vk_data/$file")" == "$SERVICE_UID" ]] || fail "Invalid Valkey state file"
|
|
400
|
-
fi
|
|
401
|
-
done
|
|
402
|
-
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')"
|
|
403
|
-
if [[ ! -e "$vk_config" ]]; then
|
|
404
|
-
(umask 077; printf '%s\n' "$vk_expected" > "$vk_config")
|
|
405
|
-
root chown stll-cloud:stll-cloud "$vk_config"
|
|
406
|
-
fi
|
|
407
|
-
[[ "$(cat "$vk_config")" == "$vk_expected" ]] || fail "Valkey configuration differs from managed settings"
|
|
408
|
-
service_prune_pid "$vk_data/valkey.pid" "$(command -v valkey-server)" valkey
|
|
409
|
-
if [[ ! -e "$vk_data/valkey.pid" ]]; then
|
|
410
|
-
service_run valkey-server "$vk_config" || fail "Valkey start failed; port 56379 must be available"
|
|
411
|
-
fi
|
|
412
|
-
for ((attempt=0; attempt<20; attempt++)); do
|
|
413
|
-
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
|
|
414
|
-
if [[ "$vk_info" == *$'valkey_version:@VALKEY@\r\n'* && "$vk_info" == *"config_file:$vk_config"$'\r\n'* ]]; then ready=1; break; fi
|
|
415
|
-
fi
|
|
416
|
-
sleep 0.25
|
|
417
|
-
done
|
|
418
|
-
[[ "$ready" == 1 ]] || fail "Valkey authenticated readiness or managed version/configuration check failed"
|
|
419
|
-
[[ -f "$vk_data/valkey.pid" ]] || fail "Valkey managed process identifier is unavailable"
|
|
420
|
-
vk_pid="$(cat "$vk_data/valkey.pid")"
|
|
421
|
-
service_process "$vk_pid" "$(command -v valkey-server)"
|
|
422
|
-
[[ "$vk_info" == *"process_id:$vk_pid"$'\r\n'* ]] || fail "Valkey endpoint has a different process identifier"
|
|
423
|
-
[[ "$(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"
|
|
424
|
-
[[ "$(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"
|
|
425
|
-
REDIS_URL="redis://:$vk_password@127.0.0.1:56379/0"
|
|
426
|
-
`;
|
|
427
|
-
/** Render only declared services; installation and environment-file ownership stay with the caller. */
|
|
428
|
-
const renderCloudServiceStart = ({ services, postgres, valkey }) => {
|
|
429
|
-
if (!/^[1-9][0-9]*$/.test(postgres) || !/^\d+\.\d+\.\d+$/.test(valkey)) throw new Error("Invalid cloud service runtime policy");
|
|
430
|
-
if (services.length === 0) return "start_services() { :; }\n";
|
|
431
|
-
const bodies = services.map((service) => {
|
|
432
|
-
switch (service) {
|
|
433
|
-
case "postgres": return postgresStart.replaceAll("@POSTGRES@", postgres);
|
|
434
|
-
case "valkey": return valkeyStart.replaceAll("@VALKEY@", valkey);
|
|
435
|
-
default: throw new Error(`Unsupported cloud service: ${String(service)}`);
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
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`;
|
|
439
|
-
};
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/toolchain-node.ts
|
|
442
|
-
const numericComponent = "(?:0|[1-9]\\d*)";
|
|
443
|
-
const policyPattern = new RegExp(`^${numericComponent}\\.x$`);
|
|
444
|
-
const selectorPattern = new RegExp(`^(${numericComponent})(?:\\.(?:x|${numericComponent}(?:\\.${numericComponent})?))?$`);
|
|
445
|
-
/** A Node policy selects the complete stable release series of one major. */
|
|
446
|
-
const nodePolicyValid = (value) => policyPattern.test(value) && validRange(value) !== null;
|
|
447
|
-
/** Static selectors may narrow that series, but cannot float across majors. */
|
|
448
|
-
const nodeSelectorMatches = (value, policy) => {
|
|
449
|
-
if (!nodePolicyValid(policy) || typeof value !== "string") return false;
|
|
450
|
-
const major = selectorPattern.exec(value)?.[1];
|
|
451
|
-
return major !== void 0 && major === policy.slice(0, -2) && validRange(value) !== null;
|
|
452
|
-
};
|
|
453
|
-
/** Engines must support every stable release in the approved major series. */
|
|
454
|
-
const nodeSupportRangeMatches = (policy, range) => {
|
|
455
|
-
if (!nodePolicyValid(policy)) return false;
|
|
456
|
-
try {
|
|
457
|
-
return subset(policy, range);
|
|
458
|
-
} catch {
|
|
459
|
-
return false;
|
|
70
|
+
const values = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const part of text.split(",")) {
|
|
72
|
+
const split = part.split("/");
|
|
73
|
+
if (split.length > 2) return void 0;
|
|
74
|
+
const [base, stepText] = split;
|
|
75
|
+
const step = stepText === void 0 ? 1 : Number(stepText);
|
|
76
|
+
if (stepText !== void 0 && !/^\d+$/.test(stepText)) return void 0;
|
|
77
|
+
if (!Number.isSafeInteger(step) || step <= 0 || base === void 0) return void 0;
|
|
78
|
+
const bounds = base.split("-");
|
|
79
|
+
if (bounds.length > 2) return void 0;
|
|
80
|
+
const lower = bounds.at(0);
|
|
81
|
+
if (lower === void 0) return void 0;
|
|
82
|
+
const start = base === "*" ? field.minimum : value(lower);
|
|
83
|
+
const upper = bounds.at(1);
|
|
84
|
+
let end = start;
|
|
85
|
+
if (base === "*" || bounds.length === 1 && stepText !== void 0) end = field.maximum;
|
|
86
|
+
else if (upper !== void 0) end = value(upper);
|
|
87
|
+
if (start === void 0 || end === void 0 || start > end) return void 0;
|
|
88
|
+
for (let selected = start; selected <= end; selected += step) values.add(selected);
|
|
460
89
|
}
|
|
90
|
+
return values;
|
|
461
91
|
};
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
# Generated by stll-cloud-setup. Regenerate with stll-cloud-setup.
|
|
468
|
-
# Networked installation and offline service startup are separate lifecycle steps.
|
|
469
|
-
# Cached filesystems may not preserve processes; run start at every session start.
|
|
470
|
-
# Supported platform: Ubuntu 24.04 (noble), amd64/arm64, root or passwordless sudo.
|
|
471
|
-
set -euo pipefail
|
|
472
|
-
|
|
473
|
-
usage() { echo 'Usage: cloud-setup.sh install|start' >&2; exit 2; }
|
|
474
|
-
[[ "$#" == 1 ]] || usage
|
|
475
|
-
case "$1" in install|start) MODE="$1" ;; *) usage ;; esac
|
|
476
|
-
fail() { echo "cloud-setup: $*" >&2; exit 1; }
|
|
477
|
-
trap 'echo "cloud-setup: command failed at line $LINENO" >&2' ERR
|
|
478
|
-
[[ -f /etc/os-release ]] || fail 'Ubuntu 24.04 is required'
|
|
479
|
-
. /etc/os-release
|
|
480
|
-
[[ "$ID" == ubuntu && "$VERSION_ID" == 24.04 && "$VERSION_CODENAME" == noble ]] || fail 'Only Ubuntu 24.04 (noble) is supported'
|
|
481
|
-
SCRIPT="$(realpath -- "$0")"
|
|
482
|
-
REPO_ROOT="$(cd -- "$(dirname -- "$SCRIPT")/.." && pwd -P)"
|
|
483
|
-
[[ "$SCRIPT" == "$REPO_ROOT/.agents/cloud-setup.sh" ]] || fail 'Run the generated repository script'
|
|
484
|
-
if [[ "$(id -u)" != 0 ]]; then
|
|
485
|
-
command -v sudo >/dev/null && sudo -n true || fail 'Root or passwordless sudo is required'
|
|
486
|
-
exec sudo -n -- bash "$SCRIPT" "$MODE"
|
|
487
|
-
fi
|
|
488
|
-
OUTPUT_UID="$(printenv SUDO_UID || id -u)"
|
|
489
|
-
[[ "$OUTPUT_UID" =~ ^[0-9]+$ ]] || fail 'Invalid invoking user'
|
|
490
|
-
OUTPUT_GID="$(getent passwd "$OUTPUT_UID" | cut -d: -f4)"
|
|
491
|
-
[[ "$OUTPUT_GID" =~ ^[0-9]+$ ]] || fail 'Invoking user is unavailable'
|
|
492
|
-
OUTPUT_USER="$(getent passwd "$OUTPUT_UID" | cut -d: -f1)"
|
|
493
|
-
OUTPUT_HOME="$(getent passwd "$OUTPUT_UID" | cut -d: -f6)"
|
|
494
|
-
[[ -n "$OUTPUT_USER" && "$OUTPUT_HOME" == /* && -d "$OUTPUT_HOME" ]] || fail 'Invoking user requires an existing home directory'
|
|
495
|
-
root() { "$@"; }
|
|
496
|
-
service_run() { runuser -u stll-cloud -- "$@" 9>&-; }
|
|
497
|
-
BUN_VERSION='@BUN@'
|
|
498
|
-
NODE_VERSION='@NODE@'
|
|
499
|
-
NODE_DIR="/opt/stll-cloud/node/$NODE_VERSION"
|
|
500
|
-
BUN_DIR="/opt/stll-cloud/bun/$BUN_VERSION"
|
|
501
|
-
export PATH="$NODE_DIR/bin:$BUN_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
502
|
-
BUN_CACHE="/var/cache/stll-cloud/bun/$OUTPUT_UID"
|
|
503
|
-
@ENV_ASSIGNMENTS@
|
|
504
|
-
[[ -f "$REPO_ROOT/.node-version" && ! -L "$REPO_ROOT/.node-version" ]] || fail 'A regular root .node-version is required'
|
|
505
|
-
[[ "$(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'
|
|
506
|
-
cd -- "$REPO_ROOT"
|
|
507
|
-
case "$(uname -m)" in
|
|
508
|
-
x86_64) ARCH=amd64; NODE_ARCH=x64; BUN_ARCH=x64 ;;
|
|
509
|
-
aarch64|arm64) ARCH=arm64; NODE_ARCH=arm64; BUN_ARCH=aarch64 ;;
|
|
510
|
-
*) fail 'Only amd64 and arm64 are supported' ;;
|
|
511
|
-
esac
|
|
512
|
-
|
|
513
|
-
runtime_ready() {
|
|
514
|
-
[[ -x "$NODE_DIR/bin/node" && "$($NODE_DIR/bin/node --version)" == "v$NODE_VERSION" ]] || fail 'Pinned Node is unavailable; run install'
|
|
515
|
-
[[ -x "$BUN_DIR/bun" && "$($BUN_DIR/bun --version)" == "$BUN_VERSION" ]] || fail 'Pinned Bun is unavailable; run install'
|
|
516
|
-
}
|
|
517
|
-
install_runtimes() {
|
|
518
|
-
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
|
|
519
|
-
apt-get update
|
|
520
|
-
DEBIAN_FRONTEND=noninteractive apt_install ca-certificates curl unzip xz-utils openssl gnupg util-linux
|
|
521
|
-
fi
|
|
522
|
-
local scratch node_archive bun_archive bun_folder
|
|
523
|
-
scratch="$(mktemp -d)"
|
|
524
|
-
trap 'rm -rf -- "$scratch"' RETURN
|
|
525
|
-
if [[ ! -x "$NODE_DIR/bin/node" ]] || [[ "$($NODE_DIR/bin/node --version)" != "v$NODE_VERSION" ]]; then
|
|
526
|
-
node_archive="node-v$NODE_VERSION-linux-$NODE_ARCH.tar.xz"
|
|
527
|
-
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"
|
|
528
|
-
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"
|
|
529
|
-
(cd "$scratch"; awk -v name="$node_archive" '$2 == name { print; count++ } END { if (count != 1) exit 1 }' node-sums | sha256sum --strict -c -)
|
|
530
|
-
install -d -m 755 "$NODE_DIR"
|
|
531
|
-
tar -xJf "$scratch/$node_archive" --strip-components=1 -C "$NODE_DIR"
|
|
532
|
-
fi
|
|
533
|
-
if [[ ! -x "$BUN_DIR/bun" ]] || [[ "$($BUN_DIR/bun --version)" != "$BUN_VERSION" ]]; then
|
|
534
|
-
bun_folder="bun-linux-$BUN_ARCH"
|
|
535
|
-
bun_archive="$bun_folder.zip"
|
|
536
|
-
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"
|
|
537
|
-
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"
|
|
538
|
-
(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 -)
|
|
539
|
-
unzip -q "$scratch/$bun_archive" -d "$scratch/bun"
|
|
540
|
-
install -d -m 755 "$BUN_DIR"
|
|
541
|
-
install -m 755 "$scratch/bun/$bun_folder/bun" "$BUN_DIR/bun"
|
|
542
|
-
fi
|
|
543
|
-
runtime_ready
|
|
544
|
-
ln -sfnT -- "$NODE_DIR/bin/node" /usr/local/bin/node
|
|
545
|
-
ln -sfnT -- "$NODE_DIR/bin/npm" /usr/local/bin/npm
|
|
546
|
-
ln -sfnT -- "$NODE_DIR/bin/npx" /usr/local/bin/npx
|
|
547
|
-
ln -sfnT -- "$BUN_DIR/bun" /usr/local/bin/bun
|
|
548
|
-
rm -rf -- "$scratch"
|
|
549
|
-
trap - RETURN
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
dependency_install() {
|
|
553
|
-
local directory
|
|
554
|
-
for directory in /var/cache/stll-cloud /var/cache/stll-cloud/bun; do
|
|
555
|
-
[[ ! -L "$directory" ]] || fail 'Dependency cache parent must not be a symlink'
|
|
556
|
-
if [[ -e "$directory" ]]; then
|
|
557
|
-
[[ -d "$directory" && "$(stat -c '%u' "$directory")" == 0 ]] || fail 'Dependency cache parent must be root-owned'
|
|
558
|
-
fi
|
|
559
|
-
install -d -m 755 -o root -g root "$directory"
|
|
560
|
-
done
|
|
561
|
-
[[ ! -L "$BUN_CACHE" ]] || fail 'Dependency cache must not be a symlink'
|
|
562
|
-
if [[ -e "$BUN_CACHE" ]]; then
|
|
563
|
-
[[ -d "$BUN_CACHE" && "$(stat -c '%u' "$BUN_CACHE")" == "$OUTPUT_UID" ]] || fail 'Dependency cache ownership differs from the invoking user'
|
|
564
|
-
fi
|
|
565
|
-
install -d -m 700 -o "$OUTPUT_UID" -g "$OUTPUT_GID" "$BUN_CACHE"
|
|
566
|
-
runuser -u "$OUTPUT_USER" -- env HOME="$OUTPUT_HOME" BUN_INSTALL_CACHE_DIR="$BUN_CACHE" PATH="$PATH" "$BUN_DIR/bun" install --frozen-lockfile
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
@SERVICE_INSTALL@
|
|
570
|
-
@SERVICE_START@
|
|
571
|
-
|
|
572
|
-
@ENV_FUNCTIONS@
|
|
573
|
-
|
|
574
|
-
if [[ "$MODE" == install ]]; then
|
|
575
|
-
install_runtimes
|
|
576
|
-
install_services
|
|
577
|
-
dependency_install
|
|
578
|
-
exit 0
|
|
579
|
-
fi
|
|
580
|
-
runtime_ready
|
|
581
|
-
@ENV_VALIDATE@
|
|
582
|
-
prepare_state
|
|
583
|
-
start_services
|
|
584
|
-
@ENV_WRITE@
|
|
585
|
-
`;
|
|
586
|
-
const environmentFunctions = String.raw`
|
|
587
|
-
validate_environment() {
|
|
588
|
-
local part target="$REPO_ROOT" env_path="$REPO_ROOT/$ENV_FILE" first_line temporary
|
|
589
|
-
while IFS= read -r part; do
|
|
590
|
-
target="$target/$part"
|
|
591
|
-
[[ ! -L "$target" ]] || fail 'Environment path must not contain symbolic links'
|
|
592
|
-
done < <(printf '%s\n' "$ENV_FILE" | tr '/' '\n')
|
|
593
|
-
[[ -d "$(dirname -- "$env_path")" ]] || fail 'Environment parent directory is missing'
|
|
594
|
-
if [[ -e "$env_path" ]]; then
|
|
595
|
-
[[ -f "$env_path" ]] || fail 'Environment target must be a regular file'
|
|
596
|
-
IFS= read -r first_line < "$env_path" || fail 'Refusing to overwrite an unowned environment file'
|
|
597
|
-
[[ "$first_line" == "$ENV_MARKER" ]] || fail 'Refusing to overwrite an unowned environment file'
|
|
598
|
-
fi
|
|
599
|
-
}
|
|
600
|
-
write_environment() {
|
|
601
|
-
validate_environment
|
|
602
|
-
local env_path="$REPO_ROOT/$ENV_FILE" temporary
|
|
603
|
-
temporary="$(mktemp "$(dirname -- "$env_path")/.stll-cloud-env.XXXXXX")"
|
|
604
|
-
chmod 600 "$temporary"
|
|
605
|
-
{
|
|
606
|
-
printf '%s\n' "$ENV_MARKER" 'NODE_ENV=test'
|
|
607
|
-
if [[ -n "$DATABASE_URL" ]]; then printf 'DATABASE_URL=%s\n' "$DATABASE_URL"; fi
|
|
608
|
-
if [[ -n "$REDIS_URL" ]]; then printf 'REDIS_URL=%s\n' "$REDIS_URL"; fi
|
|
609
|
-
} > "$temporary"
|
|
610
|
-
chown "$OUTPUT_UID:$OUTPUT_GID" "$temporary"
|
|
611
|
-
mv -f -- "$temporary" "$env_path"
|
|
612
|
-
}
|
|
613
|
-
`;
|
|
614
|
-
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
615
|
-
const generateCloudSetup = ({ policy, cloud, nodeVersion }) => {
|
|
616
|
-
const selected = parseCloudSetup(cloud);
|
|
617
|
-
if (selected === void 0) throw new Error("cloud declaration is required");
|
|
618
|
-
const exactNode = nodeVersion.trim();
|
|
619
|
-
if (valid(exactNode) !== exactNode || !nodeSelectorMatches(exactNode, policy.node)) throw new Error("cloud requires a tracked exact .node-version within the shared Node series");
|
|
620
|
-
const services = cloudServices.filter((service) => selected.services.includes(service));
|
|
621
|
-
const replacements = /* @__PURE__ */ new Map([
|
|
622
|
-
["@BUN@", policy.bun],
|
|
623
|
-
["@NODE@", exactNode],
|
|
624
|
-
["@ENV_ASSIGNMENTS@", selected.envFile === void 0 ? "" : `ENV_FILE=${shellQuote(selected.envFile)}\nENV_MARKER=${shellQuote(cloudEnvironmentMarker)}`],
|
|
625
|
-
["@ENV_FUNCTIONS@", selected.envFile === void 0 ? "" : environmentFunctions],
|
|
626
|
-
["@ENV_VALIDATE@", selected.envFile === void 0 ? "" : "DATABASE_URL=''\nREDIS_URL=''\nvalidate_environment"],
|
|
627
|
-
["@ENV_WRITE@", selected.envFile === void 0 ? "" : "write_environment"],
|
|
628
|
-
["@SERVICE_INSTALL@", renderCloudServiceInstall({
|
|
629
|
-
services,
|
|
630
|
-
postgres: policy.postgres,
|
|
631
|
-
valkey: policy.valkey,
|
|
632
|
-
valkeyArtifacts: policy.valkeyArtifacts
|
|
633
|
-
})],
|
|
634
|
-
["@SERVICE_START@", renderCloudServiceStart({
|
|
635
|
-
services,
|
|
636
|
-
postgres: policy.postgres,
|
|
637
|
-
valkey: policy.valkey
|
|
638
|
-
})]
|
|
639
|
-
]);
|
|
640
|
-
return script.replace(/@[A-Z0-9_]+@/g, (token) => {
|
|
641
|
-
const value = replacements.get(token);
|
|
642
|
-
if (value === void 0) throw new Error(`Missing cloud setup field: ${token}`);
|
|
643
|
-
return value;
|
|
92
|
+
const validConsumerCron = (text) => {
|
|
93
|
+
const fields = text.trim().split(/\s+/);
|
|
94
|
+
return fields.length === cronFields.length && cronFields.every((field, index) => {
|
|
95
|
+
const fieldText = fields.at(index);
|
|
96
|
+
return fieldText !== void 0 && cronFieldValues(fieldText, field) !== void 0;
|
|
644
97
|
});
|
|
645
98
|
};
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
"template",
|
|
658
|
-
"spec"
|
|
659
|
-
],
|
|
660
|
-
DaemonSet: [
|
|
661
|
-
"spec",
|
|
662
|
-
"template",
|
|
663
|
-
"spec"
|
|
664
|
-
],
|
|
665
|
-
StatefulSet: [
|
|
666
|
-
"spec",
|
|
667
|
-
"template",
|
|
668
|
-
"spec"
|
|
669
|
-
],
|
|
670
|
-
ReplicaSet: [
|
|
671
|
-
"spec",
|
|
672
|
-
"template",
|
|
673
|
-
"spec"
|
|
674
|
-
],
|
|
675
|
-
ReplicationController: [
|
|
676
|
-
"spec",
|
|
677
|
-
"template",
|
|
678
|
-
"spec"
|
|
679
|
-
],
|
|
680
|
-
Job: [
|
|
681
|
-
"spec",
|
|
682
|
-
"template",
|
|
683
|
-
"spec"
|
|
684
|
-
],
|
|
685
|
-
CronJob: [
|
|
686
|
-
"spec",
|
|
687
|
-
"jobTemplate",
|
|
688
|
-
"spec",
|
|
689
|
-
"template",
|
|
690
|
-
"spec"
|
|
691
|
-
]
|
|
692
|
-
};
|
|
693
|
-
/** Shared semantic image enumeration for runtime checks and update-root generation. */
|
|
694
|
-
const containerDocumentImages = (document, file) => {
|
|
695
|
-
if (!record$7(document)) return void 0;
|
|
696
|
-
const services = document["services"];
|
|
697
|
-
if (isComposeDefinitionPath(file) && record$7(services)) {
|
|
698
|
-
const images = [];
|
|
699
|
-
for (const [name, service] of Object.entries(services)) if (record$7(service) && service["image"] !== void 0) images.push({
|
|
700
|
-
image: service["image"],
|
|
701
|
-
path: [
|
|
702
|
-
"services",
|
|
703
|
-
name,
|
|
704
|
-
"image"
|
|
705
|
-
]
|
|
99
|
+
const nightlySchedule = (value) => {
|
|
100
|
+
if (!record$6(value)) return false;
|
|
101
|
+
const schedule = value["schedule"];
|
|
102
|
+
if (!Array.isArray(schedule) || schedule.length === 0) return false;
|
|
103
|
+
const coveredDays = /* @__PURE__ */ new Set();
|
|
104
|
+
for (const entry of schedule) {
|
|
105
|
+
if (!record$6(entry) || typeof entry["cron"] !== "string" || !validConsumerCron(entry["cron"])) return false;
|
|
106
|
+
const fields = entry["cron"].trim().split(/\s+/);
|
|
107
|
+
const selections = cronFields.map((field, index) => {
|
|
108
|
+
const text = fields.at(index);
|
|
109
|
+
return text === void 0 ? void 0 : cronFieldValues(text, field);
|
|
706
110
|
});
|
|
111
|
+
if (selections.at(2)?.size !== 31 || selections.at(3)?.size !== 12) continue;
|
|
112
|
+
for (const day of selections.at(4) ?? []) coveredDays.add(day);
|
|
113
|
+
}
|
|
114
|
+
return coveredDays.size === 7;
|
|
115
|
+
};
|
|
116
|
+
const parseConsumerChecks = (input) => {
|
|
117
|
+
if (input === void 0) return [];
|
|
118
|
+
if (!Array.isArray(input)) throw new Error("consumerChecks must be an array");
|
|
119
|
+
const targets = /* @__PURE__ */ new Set();
|
|
120
|
+
return input.map((entry) => {
|
|
121
|
+
if (!record$6(entry) || Object.keys(entry).some((key) => ![
|
|
122
|
+
"workflow",
|
|
123
|
+
"job",
|
|
124
|
+
"packages",
|
|
125
|
+
"toolingVersion",
|
|
126
|
+
"fixturePath"
|
|
127
|
+
].includes(key)) || typeof entry["workflow"] !== "string" || 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");
|
|
128
|
+
const target = `${entry["workflow"]}:${entry["job"]}`;
|
|
129
|
+
if (targets.has(target)) throw new Error("consumerChecks targets must be unique");
|
|
130
|
+
targets.add(target);
|
|
707
131
|
return {
|
|
708
|
-
|
|
709
|
-
|
|
132
|
+
workflow: entry["workflow"],
|
|
133
|
+
job: entry["job"],
|
|
134
|
+
packages: entry["packages"],
|
|
135
|
+
toolingVersion: entry["toolingVersion"],
|
|
136
|
+
fixturePath: entry["fixturePath"]
|
|
710
137
|
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
let spec = resource;
|
|
730
|
-
for (const key of specPath) spec = record$7(spec) ? spec[key] : void 0;
|
|
731
|
-
if (!record$7(spec)) return;
|
|
732
|
-
for (const key of [
|
|
733
|
-
"containers",
|
|
734
|
-
"initContainers",
|
|
735
|
-
"ephemeralContainers"
|
|
736
|
-
]) {
|
|
737
|
-
const containers = spec[key];
|
|
738
|
-
if (!Array.isArray(containers)) continue;
|
|
739
|
-
for (const [index, container] of containers.entries()) if (record$7(container) && container["image"] !== void 0) images.push({
|
|
740
|
-
image: container["image"],
|
|
741
|
-
path: [
|
|
742
|
-
...prefix,
|
|
743
|
-
...specPath,
|
|
744
|
-
key,
|
|
745
|
-
index,
|
|
746
|
-
"image"
|
|
747
|
-
]
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
/** Consumer checks are scoped declarations, never general runtime exceptions. */
|
|
141
|
+
const checkConsumerChecks = ({ declarations, files, policy }) => {
|
|
142
|
+
const diagnostics = [];
|
|
143
|
+
const exercised = /* @__PURE__ */ new Set();
|
|
144
|
+
const add = (message) => diagnostics.push({
|
|
145
|
+
rule: "configuration",
|
|
146
|
+
path: "stll-toolchain.json",
|
|
147
|
+
line: 1,
|
|
148
|
+
message
|
|
149
|
+
});
|
|
150
|
+
for (const [workflow, text] of Object.entries(files)) {
|
|
151
|
+
if (githubAutomationFileKind(workflow) !== "workflow") continue;
|
|
152
|
+
try {
|
|
153
|
+
const document = parseDocument(text, {
|
|
154
|
+
uniqueKeys: true,
|
|
155
|
+
merge: true
|
|
748
156
|
});
|
|
157
|
+
if (document.errors.length > 0) throw new Error("invalid workflow YAML");
|
|
158
|
+
const source = document.toJS({ maxAliasCount: 100 });
|
|
159
|
+
if (!record$6(source) || !record$6(source["jobs"])) continue;
|
|
160
|
+
for (const [jobName, job] of Object.entries(source["jobs"])) {
|
|
161
|
+
if (!record$6(job) || typeof job["uses"] !== "string") continue;
|
|
162
|
+
const [workflowPath, ref] = job["uses"].split("@");
|
|
163
|
+
if (workflowPath?.toLowerCase() !== "stella/.github/.github/workflows/package-consumer-compat.yml") continue;
|
|
164
|
+
const declaration = declarations.find((entry) => entry.workflow === workflow && entry.job === jobName);
|
|
165
|
+
if (declaration === void 0) {
|
|
166
|
+
add(`${workflow}:${jobName} requires a consumerChecks declaration`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!nightlySchedule(source["on"])) throw new Error(`${workflow} consumer checks require a nonempty valid on.schedule covering every day`);
|
|
170
|
+
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`);
|
|
171
|
+
exercised.add(declaration);
|
|
172
|
+
const approved = Object.entries(policy.actions).find(([name]) => name.toLowerCase() === consumerCheckWorkflow)?.[1];
|
|
173
|
+
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`);
|
|
174
|
+
const inputs = job["with"];
|
|
175
|
+
if (!record$6(inputs)) throw new Error(`${workflow}:${jobName} requires consumer inputs`);
|
|
176
|
+
const names = /* @__PURE__ */ new Set();
|
|
177
|
+
for (const key of Object.keys(inputs)) {
|
|
178
|
+
const normalized = key.toLowerCase();
|
|
179
|
+
if (names.has(normalized) || ![
|
|
180
|
+
"packages",
|
|
181
|
+
"consumer-node",
|
|
182
|
+
"tooling-version",
|
|
183
|
+
"fixture-path"
|
|
184
|
+
].includes(normalized) || normalized !== key) throw new Error(`${workflow}:${jobName} requires unique canonical consumer input names`);
|
|
185
|
+
names.add(normalized);
|
|
186
|
+
}
|
|
187
|
+
if (inputs["consumer-node"] !== policy.consumerNode) throw new Error(`${workflow}:${jobName} consumer-node must be ${policy.consumerNode}`);
|
|
188
|
+
if (declaration.toolingVersion !== consumerRunnerVersion) throw new Error(`${workflow}:${jobName} toolingVersion must match installed @stll/oxlint-config ${consumerRunnerVersion}`);
|
|
189
|
+
if (inputs["tooling-version"] !== declaration.toolingVersion) throw new Error(`${workflow}:${jobName} tooling-version must match consumerChecks.toolingVersion`);
|
|
190
|
+
if (inputs["fixture-path"] !== declaration.fixturePath) throw new Error(`${workflow}:${jobName} fixture-path must match consumerChecks.fixturePath`);
|
|
191
|
+
const fixtureManifest = path.posix.join(declaration.fixturePath, "consumer-compat.json");
|
|
192
|
+
const fixtureSource = files[fixtureManifest];
|
|
193
|
+
if (fixtureSource === void 0) throw new Error(`consumerChecks fixture configuration must be tracked: ${fixtureManifest}`);
|
|
194
|
+
const fixtures = parseConsumerFixtures(JSON.parse(fixtureSource));
|
|
195
|
+
assertConsumerFixtureSelection({
|
|
196
|
+
selected: declaration.packages,
|
|
197
|
+
fixtures
|
|
198
|
+
});
|
|
199
|
+
const rawPackages = inputs["packages"];
|
|
200
|
+
if (typeof rawPackages !== "string") throw new Error(`${workflow}:${jobName} packages must be a static JSON array`);
|
|
201
|
+
const selected = JSON.parse(rawPackages);
|
|
202
|
+
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`);
|
|
203
|
+
const workspacePackages = discoverConsumerPackages(files);
|
|
204
|
+
const packagesByDirectory = new Map([...workspacePackages.values()].map((entry) => [entry.directory, entry]));
|
|
205
|
+
for (const directory of declaration.packages) {
|
|
206
|
+
const manifestPath = path.posix.join(directory, "package.json");
|
|
207
|
+
if (files[manifestPath] === void 0) throw new Error(`consumerChecks package manifest must be tracked: ${manifestPath}`);
|
|
208
|
+
const selectedPackage = packagesByDirectory.get(directory);
|
|
209
|
+
if (selectedPackage === void 0) throw new Error(`consumerChecks package must be a discovered workspace member: ${manifestPath}`);
|
|
210
|
+
const fixture = fixtures.find((candidate) => candidate.package === directory);
|
|
211
|
+
if (fixture === void 0) throw new Error(`missing declared consumer fixture: ${directory}`);
|
|
212
|
+
assertConsumerFixtureKind({
|
|
213
|
+
fixture,
|
|
214
|
+
pkg: selectedPackage
|
|
215
|
+
});
|
|
216
|
+
const manifest = selectedPackage.manifest;
|
|
217
|
+
if (manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "") throw new Error(`consumerChecks requires a named published package: ${manifestPath}`);
|
|
218
|
+
const closure = consumerPackageClosure({
|
|
219
|
+
selected: selectedPackage,
|
|
220
|
+
packages: workspacePackages,
|
|
221
|
+
files
|
|
222
|
+
});
|
|
223
|
+
for (const member of closure.values()) assertConsumerNodeSupport({
|
|
224
|
+
manifest: member.manifest,
|
|
225
|
+
node: policy.consumerNode,
|
|
226
|
+
label: path.posix.join(member.directory, "package.json")
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
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));
|
|
749
232
|
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
return
|
|
753
|
-
ecosystem: "docker",
|
|
754
|
-
images
|
|
755
|
-
};
|
|
233
|
+
}
|
|
234
|
+
for (const declaration of declarations) if (!exercised.has(declaration)) add(`${declaration.workflow}:${declaration.job} consumerChecks declaration has no matching reusable invocation`);
|
|
235
|
+
return diagnostics;
|
|
756
236
|
};
|
|
757
237
|
//#endregion
|
|
758
238
|
//#region src/toolchain-images.ts
|
|
@@ -777,69 +257,9 @@ const canonicalDockerRuntime = (image) => {
|
|
|
777
257
|
return (registry === void 0 ? image : image.slice(registry.length + 1)).replace(/^library\//i, "");
|
|
778
258
|
};
|
|
779
259
|
//#endregion
|
|
780
|
-
//#region src/toolchain-inputs.ts
|
|
781
|
-
const excludedInputPath = (file) => file === "" || file.split("/").some((part) => part === "node_modules" || part === "vendor");
|
|
782
|
-
const javascriptDependencyLockfiles = [
|
|
783
|
-
"bun.lock",
|
|
784
|
-
"package-lock.json",
|
|
785
|
-
"yarn.lock",
|
|
786
|
-
"pnpm-lock.yaml"
|
|
787
|
-
];
|
|
788
|
-
/** Match project, environment, local, and fragment filenames recognized by mise. */
|
|
789
|
-
const isMiseConfigPath = (file) => !excludedInputPath(file) && (/(?:^|\/)\.?mise(?:\.[^./][^/]*)?\.toml$/.test(file) || /(?:^|\/)(?:\.?mise|\.config\/mise)\/config(?:\.[^./][^/]*)?\.toml$/.test(file) || /(?:^|\/)(?:\.?mise|\.config\/mise)\/conf\.d\/[^./][^/]*\.toml$/.test(file));
|
|
790
|
-
/** GitHub action metadata may live at the repository root or in any directory. */
|
|
791
|
-
const githubAutomationFileKind = (file) => {
|
|
792
|
-
if (file === "") return void 0;
|
|
793
|
-
if (/^\.github\/workflows\/[^/]+\.ya?ml$/.test(file)) return "workflow";
|
|
794
|
-
if (/(?:^|\/)action\.ya?ml$/.test(file)) return "action";
|
|
795
|
-
};
|
|
796
|
-
/** Dependabot updates workflows and repository-root action metadata. */
|
|
797
|
-
const isDependabotGithubActionsPath = (file) => {
|
|
798
|
-
const kind = githubAutomationFileKind(file);
|
|
799
|
-
return kind === "workflow" || kind === "action" && !file.includes("/");
|
|
800
|
-
};
|
|
801
|
-
/** Use one filename discriminator for reader selection and Python ecosystem decisions. */
|
|
802
|
-
const pythonDependencyManifestKind = (file) => {
|
|
803
|
-
if (excludedInputPath(file)) return void 0;
|
|
804
|
-
const name = file.split("/").at(-1);
|
|
805
|
-
if (name === "pyproject.toml") return "project";
|
|
806
|
-
if (name === "Pipfile") return "pipfile";
|
|
807
|
-
if (name === "Pipfile.lock") return "pipfile-lock";
|
|
808
|
-
if (name === "setup.py" || name === "setup.cfg") return "setup";
|
|
809
|
-
if (name !== void 0 && /^requirements[^/]*\.(?:txt|in)$/.test(name)) return "requirements";
|
|
810
|
-
};
|
|
811
|
-
const isPythonDependencyManifest = (file) => pythonDependencyManifestKind(file) !== void 0;
|
|
812
|
-
/** Docker definition names use dot-delimited variants, excluding source and documentation. */
|
|
813
|
-
const isDockerDefinitionPath = (file) => {
|
|
814
|
-
if (excludedInputPath(file)) return false;
|
|
815
|
-
const name = file.split("/").at(-1) ?? "";
|
|
816
|
-
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;
|
|
817
|
-
return /^(?:[^./]+\.)*(?:dockerfile|containerfile)(?:\.[^./]+)*$/i.test(name);
|
|
818
|
-
};
|
|
819
|
-
/** Lockfiles establish ecosystem presence without reading their dependency graphs. */
|
|
820
|
-
const toolchainInputKind = (file) => {
|
|
821
|
-
if (file === ".agents/cloud-setup.sh") return "config";
|
|
822
|
-
if (githubAutomationFileKind(file) !== void 0) return "config";
|
|
823
|
-
if (excludedInputPath(file)) return void 0;
|
|
824
|
-
if (pythonDependencyManifestKind(file) === "pipfile-lock" || javascriptDependencyLockfiles.some((name) => file.split("/").at(-1) === name) || /(?:^|\/)uv\.lock$/.test(file)) return "presence";
|
|
825
|
-
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";
|
|
826
|
-
};
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/toolchain-workspaces.ts
|
|
829
|
-
/** Match manifest directories with the same positive and negative workspace patterns. */
|
|
830
|
-
const workspaceContains = ({ directory, file, patterns, rootMembership }) => {
|
|
831
|
-
const relative = path.posix.relative(directory, path.posix.dirname(file));
|
|
832
|
-
if (relative === "" && rootMembership === "implicit") return true;
|
|
833
|
-
if (relative === ".." || relative.startsWith("../")) return false;
|
|
834
|
-
if (!Array.isArray(patterns)) return false;
|
|
835
|
-
const normalize = (pattern) => path.posix.normalize(pattern);
|
|
836
|
-
const selected = patterns.filter((value) => typeof value === "string");
|
|
837
|
-
return picomatch(selected.filter((pattern) => !pattern.startsWith("!")).map(normalize))(relative) && !picomatch(selected.filter((pattern) => pattern.startsWith("!")).map((pattern) => normalize(pattern.slice(1))))(relative);
|
|
838
|
-
};
|
|
839
|
-
//#endregion
|
|
840
260
|
//#region src/toolchain-dependabot.ts
|
|
841
261
|
const dependabotRules = ["dependabot-policy"];
|
|
842
|
-
const record$
|
|
262
|
+
const record$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
843
263
|
const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
844
264
|
const sameStrings = (value, expected) => {
|
|
845
265
|
if (!strings(value) || value.length !== expected.length) return false;
|
|
@@ -861,8 +281,8 @@ const workspaceTable = (parsed, ecosystem) => {
|
|
|
861
281
|
case "cargo": return parsed["workspace"];
|
|
862
282
|
case "uv": {
|
|
863
283
|
const tool = parsed["tool"];
|
|
864
|
-
const uv = record$
|
|
865
|
-
return record$
|
|
284
|
+
const uv = record$5(tool) ? tool["uv"] : void 0;
|
|
285
|
+
return record$5(uv) ? uv["workspace"] : void 0;
|
|
866
286
|
}
|
|
867
287
|
default: throw new Error("unknown workspace ecosystem", { cause: ecosystem });
|
|
868
288
|
}
|
|
@@ -882,7 +302,7 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
882
302
|
const document = parseDocument(text);
|
|
883
303
|
if (document.errors.length > 0) throw new Error(`${workspaceFile}:1: invalid pnpm workspace YAML`);
|
|
884
304
|
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
885
|
-
if (!record$
|
|
305
|
+
if (!record$5(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
|
|
886
306
|
const patterns = parsed["packages"] ?? [];
|
|
887
307
|
if (!strings(patterns)) throw new Error(`${workspaceFile}:1: pnpm packages must be a string array`);
|
|
888
308
|
workspaces.set(directory, patterns.map((pattern) => pattern.startsWith("!") ? `!${path.posix.normalize(pattern.slice(1))}` : path.posix.normalize(pattern)));
|
|
@@ -893,14 +313,14 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
893
313
|
}
|
|
894
314
|
}
|
|
895
315
|
try {
|
|
896
|
-
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : parse(files[file] ?? "");
|
|
897
|
-
if (!record$
|
|
316
|
+
const parsed = ecosystem === "npm" ? JSON.parse(files[file] ?? "") : parse$2(files[file] ?? "");
|
|
317
|
+
if (!record$5(parsed)) continue;
|
|
898
318
|
if (ecosystem === "npm" && typeof parsed["packageManager"] === "string" && parsed["packageManager"].startsWith("pnpm@")) continue;
|
|
899
319
|
const value = workspaceTable(parsed, ecosystem);
|
|
900
|
-
const patterns = record$
|
|
901
|
-
const members = ecosystem === "cargo" && record$
|
|
320
|
+
const patterns = record$5(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
|
|
321
|
+
const members = ecosystem === "cargo" && record$5(value) && patterns === void 0 ? [] : patterns;
|
|
902
322
|
if (strings(members)) {
|
|
903
|
-
const excluded = ecosystem !== "npm" && record$
|
|
323
|
+
const excluded = ecosystem !== "npm" && record$5(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
|
|
904
324
|
workspaces.set(path.posix.dirname(file), [...members, ...excluded]);
|
|
905
325
|
}
|
|
906
326
|
} catch {}
|
|
@@ -949,13 +369,13 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
949
369
|
if (includes.some((pattern) => picomatch.isMatch(relative, pattern)) && !excludes.some((pattern) => picomatch.isMatch(relative, pattern.slice(1)))) memberDirectories.add(directory);
|
|
950
370
|
}
|
|
951
371
|
for (const directory of memberDirectories) try {
|
|
952
|
-
const parsed = parse(files[path.posix.join(directory, "Cargo.toml")] ?? "");
|
|
953
|
-
const workspace = parse(files[path.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
954
|
-
const inherited = record$
|
|
372
|
+
const parsed = parse$2(files[path.posix.join(directory, "Cargo.toml")] ?? "");
|
|
373
|
+
const workspace = parse$2(files[path.posix.join(root, "Cargo.toml")] ?? "")["workspace"];
|
|
374
|
+
const inherited = record$5(workspace) ? workspace["dependencies"] : void 0;
|
|
955
375
|
const dependencyOwners = [parsed];
|
|
956
376
|
const targets = parsed["target"];
|
|
957
|
-
if (record$
|
|
958
|
-
for (const target of Object.values(targets)) if (record$
|
|
377
|
+
if (record$5(targets)) {
|
|
378
|
+
for (const target of Object.values(targets)) if (record$5(target)) dependencyOwners.push(target);
|
|
959
379
|
}
|
|
960
380
|
for (const owner of dependencyOwners) for (const key of [
|
|
961
381
|
"dependencies",
|
|
@@ -963,12 +383,12 @@ const workspaceRoots = (files, ecosystem) => {
|
|
|
963
383
|
"build-dependencies"
|
|
964
384
|
]) {
|
|
965
385
|
const dependencies = owner[key];
|
|
966
|
-
if (!record$
|
|
386
|
+
if (!record$5(dependencies)) continue;
|
|
967
387
|
for (const [name, dependency] of Object.entries(dependencies)) {
|
|
968
|
-
if (!record$
|
|
388
|
+
if (!record$5(dependency)) continue;
|
|
969
389
|
const isInherited = dependency["workspace"] === true;
|
|
970
|
-
const resolved = isInherited && record$
|
|
971
|
-
if (!record$
|
|
390
|
+
const resolved = isInherited && record$5(inherited) ? inherited[name] : dependency;
|
|
391
|
+
if (!record$5(resolved) || typeof resolved["path"] !== "string") continue;
|
|
972
392
|
if (path.posix.isAbsolute(resolved["path"]) || /\\|\$|^[A-Za-z]:/.test(resolved["path"])) continue;
|
|
973
393
|
const target = path.posix.normalize(path.posix.join(isInherited ? root : directory, resolved["path"]));
|
|
974
394
|
const relative = path.posix.relative(root, target);
|
|
@@ -996,7 +416,7 @@ const javascriptEcosystem = (directory, files) => {
|
|
|
996
416
|
if (files[path.posix.join(root, "bun.lock")] !== void 0) return "bun";
|
|
997
417
|
try {
|
|
998
418
|
const manifest = JSON.parse(files[path.posix.join(root, "package.json")] ?? "{}");
|
|
999
|
-
if (record$
|
|
419
|
+
if (record$5(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
|
|
1000
420
|
} catch {}
|
|
1001
421
|
return "npm";
|
|
1002
422
|
};
|
|
@@ -1007,8 +427,8 @@ const pythonEcosystem = (file, files) => {
|
|
|
1007
427
|
if (files[path.posix.join(root, "uv.lock")] !== void 0 || files[path.posix.join(root, "uv.toml")] !== void 0) return "uv";
|
|
1008
428
|
const pyproject = files[path.posix.join(root, "pyproject.toml")];
|
|
1009
429
|
if (pyproject !== void 0) try {
|
|
1010
|
-
const tool = parse(pyproject)["tool"];
|
|
1011
|
-
if (record$
|
|
430
|
+
const tool = parse$2(pyproject)["tool"];
|
|
431
|
+
if (record$5(tool) && record$5(tool["uv"])) return "uv";
|
|
1012
432
|
} catch {}
|
|
1013
433
|
return "pip";
|
|
1014
434
|
};
|
|
@@ -1079,7 +499,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1079
499
|
add("cannot resolve Dependabot YAML aliases");
|
|
1080
500
|
return diagnostics;
|
|
1081
501
|
}
|
|
1082
|
-
if (!record$
|
|
502
|
+
if (!record$5(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
|
|
1083
503
|
add("Dependabot must declare version: 2 and an updates array");
|
|
1084
504
|
return diagnostics;
|
|
1085
505
|
}
|
|
@@ -1092,7 +512,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1092
512
|
});
|
|
1093
513
|
if (legacyDocument.errors.length === 0) legacy = legacyDocument.toJS({ maxAliasCount: 100 });
|
|
1094
514
|
} catch {}
|
|
1095
|
-
const legacyUpdates = record$
|
|
515
|
+
const legacyUpdates = record$5(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
|
|
1096
516
|
const rootClaims = /* @__PURE__ */ new Map();
|
|
1097
517
|
const directoryClaims = /* @__PURE__ */ new Map();
|
|
1098
518
|
for (const [index, update] of parsed["updates"].entries()) {
|
|
@@ -1106,7 +526,7 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1106
526
|
return offset === void 0 ? 1 : lineCounter.linePos(offset).line;
|
|
1107
527
|
};
|
|
1108
528
|
const report = (key, message) => add(message, lineOf(key));
|
|
1109
|
-
if (!record$
|
|
529
|
+
if (!record$5(update) || typeof update["package-ecosystem"] !== "string") {
|
|
1110
530
|
report("package-ecosystem", "each Dependabot update must declare a package ecosystem");
|
|
1111
531
|
continue;
|
|
1112
532
|
}
|
|
@@ -1140,21 +560,21 @@ const checkDependabot = ({ files, policy }) => {
|
|
|
1140
560
|
covered.set(ecosystem, matches);
|
|
1141
561
|
const schedule = update["schedule"];
|
|
1142
562
|
const legacyUpdate = legacyUpdates.at(index);
|
|
1143
|
-
const legacySchedule = record$
|
|
1144
|
-
if (record$
|
|
1145
|
-
if (!record$
|
|
563
|
+
const legacySchedule = record$5(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
|
|
564
|
+
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`);
|
|
565
|
+
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`);
|
|
1146
566
|
const cooldown = update["cooldown"];
|
|
1147
|
-
if (!record$
|
|
567
|
+
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}`);
|
|
1148
568
|
const groups = update["groups"];
|
|
1149
|
-
if (!record$
|
|
569
|
+
if (!record$5(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
|
|
1150
570
|
const group = groups[name];
|
|
1151
|
-
return !record$
|
|
571
|
+
return !record$5(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
|
|
1152
572
|
})) report("groups", `${ecosystem}: groups must match the shared policy`);
|
|
1153
573
|
const ignored = ignoredFor(ecosystem, policy);
|
|
1154
574
|
const ignore = update["ignore"];
|
|
1155
575
|
if (ignore !== void 0 && (!Array.isArray(ignore) || ignore.length === 0)) report("ignore", `${ecosystem}: omit ignore or provide a nonempty array`);
|
|
1156
|
-
if (Array.isArray(ignore) && ignore.some((entry) => !record$
|
|
1157
|
-
for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$
|
|
576
|
+
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`);
|
|
577
|
+
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}`);
|
|
1158
578
|
}
|
|
1159
579
|
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}`);
|
|
1160
580
|
return diagnostics;
|
|
@@ -1166,7 +586,7 @@ const locatorValidators = {
|
|
|
1166
586
|
"bun-source": /^(?:jobs\.[\w-]+|runs)\.steps\.[\w-]+$/
|
|
1167
587
|
};
|
|
1168
588
|
const knownKind = (value) => typeof value === "string" && Object.hasOwn(locatorValidators, value);
|
|
1169
|
-
const record$
|
|
589
|
+
const record$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1170
590
|
/** One reviewed declaration identifies one unresolved selector, never a rule. */
|
|
1171
591
|
const parseDynamicSelectors = (input) => {
|
|
1172
592
|
if (input === void 0) return [];
|
|
@@ -1174,7 +594,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
1174
594
|
const result = [];
|
|
1175
595
|
const keys = /* @__PURE__ */ new Set();
|
|
1176
596
|
for (const entry of input) {
|
|
1177
|
-
if (!record$
|
|
597
|
+
if (!record$4(entry) || Object.keys(entry).some((key) => ![
|
|
1178
598
|
"path",
|
|
1179
599
|
"kind",
|
|
1180
600
|
"reason",
|
|
@@ -1210,7 +630,7 @@ const parseDynamicSelectors = (input) => {
|
|
|
1210
630
|
};
|
|
1211
631
|
//#endregion
|
|
1212
632
|
//#region src/toolchain-engine-floors.ts
|
|
1213
|
-
const record$
|
|
633
|
+
const record$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1214
634
|
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 !== "..");
|
|
1215
635
|
const parseEngineFloors = (input) => {
|
|
1216
636
|
if (input === void 0) return [];
|
|
@@ -1218,7 +638,7 @@ const parseEngineFloors = (input) => {
|
|
|
1218
638
|
const result = [];
|
|
1219
639
|
const jobs = /* @__PURE__ */ new Set();
|
|
1220
640
|
for (const entry of input) {
|
|
1221
|
-
if (!record$
|
|
641
|
+
if (!record$3(entry) || Object.keys(entry).some((key) => ![
|
|
1222
642
|
"package",
|
|
1223
643
|
"workflow",
|
|
1224
644
|
"job"
|
|
@@ -1239,16 +659,16 @@ const resolveEngineFloor = (entry, files) => {
|
|
|
1239
659
|
const workflowText = files[entry.workflow];
|
|
1240
660
|
if (manifestText === void 0 || workflowText === void 0) throw new Error("engine floor package and workflow must be tracked");
|
|
1241
661
|
const manifest = JSON.parse(manifestText);
|
|
1242
|
-
if (!record$
|
|
662
|
+
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");
|
|
1243
663
|
const range = manifest["engines"]["node"];
|
|
1244
664
|
const minimum = validRange(range) === null ? null : minVersion(range);
|
|
1245
665
|
if (minimum === null || minimum.prerelease.length !== 0) throw new Error("engine floor engines.node must declare a stable minimum");
|
|
1246
666
|
const document = parseDocument(workflowText, { merge: true });
|
|
1247
667
|
if (document.errors.length !== 0) throw new Error("engine floor workflow must be valid YAML");
|
|
1248
668
|
const workflow = document.toJS({ maxAliasCount: 100 });
|
|
1249
|
-
if (!record$
|
|
669
|
+
if (!record$3(workflow) || !record$3(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
|
|
1250
670
|
const job = workflow["jobs"][entry.job];
|
|
1251
|
-
if (!record$
|
|
671
|
+
if (!record$3(job)) throw new Error("engine floor job must exist in its tracked workflow");
|
|
1252
672
|
if (!Array.isArray(job["steps"]) || Object.hasOwn(job, "uses")) throw new Error("engine floor job must declare steps and cannot reuse a workflow");
|
|
1253
673
|
return {
|
|
1254
674
|
...entry,
|
|
@@ -1268,7 +688,7 @@ const packageRules = [
|
|
|
1268
688
|
"typescript-layout",
|
|
1269
689
|
"node-engine"
|
|
1270
690
|
];
|
|
1271
|
-
const record$
|
|
691
|
+
const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1272
692
|
const shellSubstitutions = (command) => {
|
|
1273
693
|
const frames = [{
|
|
1274
694
|
kind: "root",
|
|
@@ -1333,7 +753,7 @@ const compilerCommandSegments = (command) => {
|
|
|
1333
753
|
const segments = [[]];
|
|
1334
754
|
for (const source of [command, ...shellSubstitutions(command)]) {
|
|
1335
755
|
segments.push([]);
|
|
1336
|
-
const shellWords = source.match(/(?:[^\s"'\\;&|()]|\\[
|
|
756
|
+
const shellWords = source.match(/(?:[^\s"'\\;&|()]|\\[\s\S]|"(?:[^"\\]|\\[\s\S])*"|'[^']*')+|&&|\|\||[();&|\n]/g);
|
|
1337
757
|
for (const word of shellWords ?? []) if (/^(?:&&|\|\||[;&|\n])$/.test(word)) segments.push([]);
|
|
1338
758
|
else segments.at(-1)?.push(word);
|
|
1339
759
|
}
|
|
@@ -1420,29 +840,29 @@ const launcherValueOptions = new Map(Object.entries({
|
|
|
1420
840
|
"--input-type"
|
|
1421
841
|
]
|
|
1422
842
|
}).map(([launcher, options]) => [launcher, new Set(options)]));
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
continue;
|
|
1432
|
-
}
|
|
1433
|
-
if (quote === void 0 && (character === "'" || character === "\"")) {
|
|
1434
|
-
quote = character;
|
|
1435
|
-
continue;
|
|
1436
|
-
}
|
|
1437
|
-
const next = word.at(index + 1);
|
|
1438
|
-
if (character === "\\" && next !== void 0 && quote !== "'" && (quote === void 0 || /[$`"\\\n]/.test(next))) {
|
|
1439
|
-
result += next;
|
|
1440
|
-
index += 1;
|
|
1441
|
-
} else result += character;
|
|
843
|
+
const decodeShellWord = (word) => {
|
|
844
|
+
let result = "";
|
|
845
|
+
let quote;
|
|
846
|
+
for (let index = 0; index < word.length; index += 1) {
|
|
847
|
+
const character = word.at(index);
|
|
848
|
+
if (character === quote) {
|
|
849
|
+
quote = void 0;
|
|
850
|
+
continue;
|
|
1442
851
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
852
|
+
if (quote === void 0 && (character === "'" || character === "\"")) {
|
|
853
|
+
quote = character;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
const next = word.at(index + 1);
|
|
857
|
+
if (character === "\\" && next !== void 0 && quote !== "'" && (quote === void 0 || /[$`"\\\n]/.test(next))) {
|
|
858
|
+
if (next !== "\n") result += next;
|
|
859
|
+
index += 1;
|
|
860
|
+
} else result += character;
|
|
861
|
+
}
|
|
862
|
+
return result;
|
|
863
|
+
};
|
|
864
|
+
const invokesCompiler = (words) => {
|
|
865
|
+
const tokens = words.map(decodeShellWord);
|
|
1446
866
|
const compiler = (word) => /(?:^|\/)(?:tsc|tsgo)(?:\.js)?$/.test(word);
|
|
1447
867
|
let start = 0;
|
|
1448
868
|
const skipOptions = (index, launcher) => {
|
|
@@ -1499,10 +919,10 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1499
919
|
const document = pnpm ? parseDocument(text, { lineCounter }) : void 0;
|
|
1500
920
|
if (document !== void 0 && document.errors.length > 0) throw new Error("invalid YAML");
|
|
1501
921
|
const json = document === void 0 ? JSON.parse(text) : document.toJS();
|
|
1502
|
-
if (!record$
|
|
922
|
+
if (!record$2(json)) throw new Error("manifest must be an object");
|
|
1503
923
|
if (pnpm) {
|
|
1504
|
-
if (json["catalog"] !== void 0 && !record$
|
|
1505
|
-
if (json["catalogs"] !== void 0 && (!record$
|
|
924
|
+
if (json["catalog"] !== void 0 && !record$2(json["catalog"])) throw new Error("catalog must be a mapping");
|
|
925
|
+
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");
|
|
1506
926
|
if (json["packages"] !== void 0 && (!Array.isArray(json["packages"]) || !json["packages"].every((pattern) => typeof pattern === "string"))) throw new Error("workspace packages must be patterns");
|
|
1507
927
|
}
|
|
1508
928
|
manifests.set(file, json);
|
|
@@ -1516,13 +936,13 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1516
936
|
catalog: json["overrides"],
|
|
1517
937
|
prefix: ["overrides"]
|
|
1518
938
|
},
|
|
1519
|
-
...record$
|
|
939
|
+
...record$2(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
|
|
1520
940
|
catalog,
|
|
1521
941
|
prefix: ["catalogs", name]
|
|
1522
942
|
})) : []
|
|
1523
943
|
];
|
|
1524
944
|
for (const { catalog, prefix } of catalogEntries) {
|
|
1525
|
-
if (!record$
|
|
945
|
+
if (!record$2(catalog)) continue;
|
|
1526
946
|
for (const [name, value] of Object.entries(catalog)) {
|
|
1527
947
|
const node = document.getIn([...prefix, name], true);
|
|
1528
948
|
if (!isNode(node) || node.range === void 0 || node.range === null) continue;
|
|
@@ -1541,7 +961,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1541
961
|
});
|
|
1542
962
|
}
|
|
1543
963
|
}
|
|
1544
|
-
const catalogSources = (json) => [json, ...record$
|
|
964
|
+
const catalogSources = (json) => [json, ...record$2(json["workspaces"]) ? [json["workspaces"]] : []];
|
|
1545
965
|
const pnpmWorkspaceFor = (file) => {
|
|
1546
966
|
let directory = path.posix.dirname(file);
|
|
1547
967
|
while (true) {
|
|
@@ -1564,8 +984,8 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1564
984
|
patterns: workspace["packages"],
|
|
1565
985
|
rootMembership: "implicit"
|
|
1566
986
|
})) return void 0;
|
|
1567
|
-
if (name === "" || name === "default") return workspace["catalog"] ?? (record$
|
|
1568
|
-
return record$
|
|
987
|
+
if (name === "" || name === "default") return workspace["catalog"] ?? (record$2(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
|
|
988
|
+
return record$2(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
|
|
1569
989
|
}
|
|
1570
990
|
let directory = path.posix.dirname(file);
|
|
1571
991
|
while (true) {
|
|
@@ -1573,7 +993,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1573
993
|
if (manifest !== void 0) {
|
|
1574
994
|
for (const source of catalogSources(manifest)) {
|
|
1575
995
|
if (name === "" && source["catalog"] !== void 0) return source["catalog"];
|
|
1576
|
-
if (record$
|
|
996
|
+
if (record$2(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
|
|
1577
997
|
}
|
|
1578
998
|
if (manifest["workspaces"] !== void 0) return void 0;
|
|
1579
999
|
}
|
|
@@ -1587,7 +1007,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1587
1007
|
if (visited.has(value)) return void 0;
|
|
1588
1008
|
visited.add(value);
|
|
1589
1009
|
const catalog = catalogFor(file, value.slice(8));
|
|
1590
|
-
value = record$
|
|
1010
|
+
value = record$2(catalog) ? catalog[name] : void 0;
|
|
1591
1011
|
}
|
|
1592
1012
|
return value;
|
|
1593
1013
|
};
|
|
@@ -1603,7 +1023,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1603
1023
|
const workspaces = manifests.get(path.posix.join(directory, "package.json"))?.["workspaces"];
|
|
1604
1024
|
if (workspaces !== void 0) return {
|
|
1605
1025
|
directory,
|
|
1606
|
-
patterns: record$
|
|
1026
|
+
patterns: record$2(workspaces) ? workspaces["packages"] : workspaces,
|
|
1607
1027
|
source: "json"
|
|
1608
1028
|
};
|
|
1609
1029
|
if (directory === ".") return void 0;
|
|
@@ -1659,7 +1079,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1659
1079
|
"peerDependencies"
|
|
1660
1080
|
]) {
|
|
1661
1081
|
const dependencies = json[section];
|
|
1662
|
-
if (!record$
|
|
1082
|
+
if (!record$2(dependencies)) continue;
|
|
1663
1083
|
for (const [name, value] of Object.entries(dependencies)) {
|
|
1664
1084
|
if (!tsSpecifiers.has(name)) continue;
|
|
1665
1085
|
const resolved = resolve(file, name, value);
|
|
@@ -1721,7 +1141,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1721
1141
|
message: `packageManager Bun version must be ${policy.bun}, found ${JSON.stringify(manager)}`
|
|
1722
1142
|
});
|
|
1723
1143
|
const checkEntries = (entries, location) => {
|
|
1724
|
-
if (!record$
|
|
1144
|
+
if (!record$2(entries)) return;
|
|
1725
1145
|
for (const [name, raw] of Object.entries(entries)) {
|
|
1726
1146
|
const value = resolve(file, name, raw);
|
|
1727
1147
|
const tsAllowed = tsSpecifiers.get(name);
|
|
@@ -1758,12 +1178,12 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1758
1178
|
"devDependencies",
|
|
1759
1179
|
"optionalDependencies"
|
|
1760
1180
|
]) checkEntries(json[key]);
|
|
1761
|
-
if (record$
|
|
1181
|
+
if (record$2(json["peerDependencies"])) {
|
|
1762
1182
|
for (const [name, value] of Object.entries(json["peerDependencies"])) if (tsSpecifiers.has(name) && !peerRange(resolve(file, name, value))) checkEntries({ [name]: value });
|
|
1763
1183
|
}
|
|
1764
1184
|
for (const source of catalogSources(json)) {
|
|
1765
1185
|
checkEntries(source["catalog"]);
|
|
1766
|
-
if (!record$
|
|
1186
|
+
if (!record$2(source["catalogs"])) continue;
|
|
1767
1187
|
for (const catalog of Object.values(source["catalogs"])) checkEntries(catalog);
|
|
1768
1188
|
}
|
|
1769
1189
|
const ownedNames = /* @__PURE__ */ new Set([...pins.keys(), ...tsSpecifiers.keys()]);
|
|
@@ -1775,7 +1195,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1775
1195
|
"optionalDependencies"
|
|
1776
1196
|
]) {
|
|
1777
1197
|
const entries = resolutionRoot[section];
|
|
1778
|
-
if (!record$
|
|
1198
|
+
if (!record$2(entries)) continue;
|
|
1779
1199
|
for (const [name, value] of Object.entries(entries)) resolutionDependencies[name] = resolve(file, name, value);
|
|
1780
1200
|
}
|
|
1781
1201
|
const affectedSpecifiers = /* @__PURE__ */ new Map();
|
|
@@ -1795,7 +1215,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1795
1215
|
"optionalDependencies"
|
|
1796
1216
|
]) {
|
|
1797
1217
|
const entries = member[section];
|
|
1798
|
-
if (!record$
|
|
1218
|
+
if (!record$2(entries)) continue;
|
|
1799
1219
|
for (const [name, value] of Object.entries(entries)) {
|
|
1800
1220
|
if (!tsSpecifiers.has(name)) continue;
|
|
1801
1221
|
const values = affectedSpecifiers.get(name) ?? /* @__PURE__ */ new Set();
|
|
@@ -1805,7 +1225,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1805
1225
|
}
|
|
1806
1226
|
}
|
|
1807
1227
|
const checkResolutionMap = (entries, parent) => {
|
|
1808
|
-
if (!record$
|
|
1228
|
+
if (!record$2(entries)) return;
|
|
1809
1229
|
for (const [selector, replacement] of Object.entries(entries)) {
|
|
1810
1230
|
const target = selector === "." ? parent : selector;
|
|
1811
1231
|
if (target === void 0) continue;
|
|
@@ -1828,7 +1248,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1828
1248
|
});
|
|
1829
1249
|
continue;
|
|
1830
1250
|
}
|
|
1831
|
-
if (record$
|
|
1251
|
+
if (record$2(replacement)) {
|
|
1832
1252
|
checkResolutionMap(replacement, target);
|
|
1833
1253
|
continue;
|
|
1834
1254
|
}
|
|
@@ -1862,7 +1282,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1862
1282
|
};
|
|
1863
1283
|
checkResolutionMap(json["overrides"]);
|
|
1864
1284
|
checkResolutionMap(json["resolutions"]);
|
|
1865
|
-
if (record$
|
|
1285
|
+
if (record$2(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
|
|
1866
1286
|
const localEntries = toolchainEntries(file, json);
|
|
1867
1287
|
const usesCompiler = policy.typescriptInstallLayouts.some((layout) => localEntries.some(({ name }) => name === layout.compilerPackage));
|
|
1868
1288
|
let selectedLayout = layoutFor(specifierMap(localEntries));
|
|
@@ -1886,7 +1306,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1886
1306
|
key: "dependencies",
|
|
1887
1307
|
message: "TypeScript compiler and compatibility packages must match one complete declared install layout"
|
|
1888
1308
|
});
|
|
1889
|
-
const command = record$
|
|
1309
|
+
const command = record$2(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
|
|
1890
1310
|
if (selectedLayout !== void 0 && typeof command === "string") {
|
|
1891
1311
|
const normalizedExpected = selectedLayout.typecheckCommand.replace(/\s+/g, " ").trim();
|
|
1892
1312
|
for (const words of compilerCommandSegments(command)) {
|
|
@@ -1904,7 +1324,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1904
1324
|
}
|
|
1905
1325
|
}
|
|
1906
1326
|
}
|
|
1907
|
-
if (record$
|
|
1327
|
+
if (record$2(json["peerDependencies"])) {
|
|
1908
1328
|
const peerLayout = selectedLayout ?? workspaceLayoutFor(file)?.layout;
|
|
1909
1329
|
for (const [name, raw] of Object.entries(json["peerDependencies"])) {
|
|
1910
1330
|
if (!tsSpecifiers.has(name)) continue;
|
|
@@ -1928,7 +1348,7 @@ const checkPackageFiles = ({ files, policy }) => {
|
|
|
1928
1348
|
});
|
|
1929
1349
|
}
|
|
1930
1350
|
}
|
|
1931
|
-
if (record$
|
|
1351
|
+
if (record$2(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
|
|
1932
1352
|
file,
|
|
1933
1353
|
rule: "node-engine",
|
|
1934
1354
|
key: "node",
|
|
@@ -1950,7 +1370,7 @@ const runtimeRules = [
|
|
|
1950
1370
|
"runtime-workflow",
|
|
1951
1371
|
"action-pins"
|
|
1952
1372
|
];
|
|
1953
|
-
const record$
|
|
1373
|
+
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1954
1374
|
const pythonSelectorMatches = (value, expected) => value === expected || typeof value === "string" && /^\d+\.\d+$/.test(expected) && value.startsWith(`${expected}.`) && /^\d+\.\d+\.\d+$/.test(value);
|
|
1955
1375
|
const compareRelease = (left, right) => {
|
|
1956
1376
|
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
@@ -2055,7 +1475,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2055
1475
|
};
|
|
2056
1476
|
const toml = (rule) => {
|
|
2057
1477
|
try {
|
|
2058
|
-
return parse(text);
|
|
1478
|
+
return parse$2(text);
|
|
2059
1479
|
} catch {
|
|
2060
1480
|
add({
|
|
2061
1481
|
rule,
|
|
@@ -2114,11 +1534,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2114
1534
|
const parsed = toml("rust-version");
|
|
2115
1535
|
if (parsed !== void 0) {
|
|
2116
1536
|
const toolchain = parsed["toolchain"];
|
|
2117
|
-
const components = record$
|
|
1537
|
+
const components = record$1(toolchain) ? toolchain["components"] : void 0;
|
|
2118
1538
|
const expected = Array.isArray(components) && components.includes("rustc-dev") ? policy.rustCompilerDevelopment : policy.rust;
|
|
2119
1539
|
pin({
|
|
2120
1540
|
rule: "rust-version",
|
|
2121
|
-
value: record$
|
|
1541
|
+
value: record$1(toolchain) ? toolchain["channel"] : void 0,
|
|
2122
1542
|
expected,
|
|
2123
1543
|
label: "Rust channel",
|
|
2124
1544
|
line: lineOf("channel")
|
|
@@ -2139,21 +1559,21 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2139
1559
|
if (candidate === file) return parsed;
|
|
2140
1560
|
if (!trackedFiles.has(candidate)) return void 0;
|
|
2141
1561
|
try {
|
|
2142
|
-
return parse(readFile(candidate) ?? "");
|
|
1562
|
+
return parse$2(readFile(candidate) ?? "");
|
|
2143
1563
|
} catch {
|
|
2144
1564
|
return;
|
|
2145
1565
|
}
|
|
2146
1566
|
};
|
|
2147
1567
|
if (parsed !== void 0) {
|
|
2148
1568
|
const workspace = parsed["workspace"];
|
|
2149
|
-
const defaults = record$
|
|
2150
|
-
if (record$
|
|
1569
|
+
const defaults = record$1(workspace) ? workspace["package"] : void 0;
|
|
1570
|
+
if (record$1(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
|
|
2151
1571
|
const pkg = parsed["package"];
|
|
2152
|
-
const version = record$
|
|
2153
|
-
if (version !== void 0) if (!record$
|
|
1572
|
+
const version = record$1(pkg) ? pkg["rust-version"] : void 0;
|
|
1573
|
+
if (version !== void 0) if (!record$1(version)) rustFloor(version);
|
|
2154
1574
|
else {
|
|
2155
1575
|
let inherited;
|
|
2156
|
-
if (version["workspace"] === true && record$
|
|
1576
|
+
if (version["workspace"] === true && record$1(pkg)) {
|
|
2157
1577
|
const explicit = pkg["workspace"];
|
|
2158
1578
|
let directory = path.posix.dirname(file);
|
|
2159
1579
|
const explicitPath = typeof explicit === "string" && !explicit.includes("\\") && !explicit.includes(":") && !explicit.includes("${{") && !path.posix.isAbsolute(explicit) ? path.posix.normalize(path.posix.join(directory, explicit)) : void 0;
|
|
@@ -2161,9 +1581,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2161
1581
|
if (explicitPath !== void 0) directory = explicitPath;
|
|
2162
1582
|
while (true) {
|
|
2163
1583
|
const table = readManifest(path.posix.join(directory, "Cargo.toml"))?.["workspace"];
|
|
2164
|
-
if (record$
|
|
1584
|
+
if (record$1(table)) {
|
|
2165
1585
|
const fields = table["package"];
|
|
2166
|
-
inherited = record$
|
|
1586
|
+
inherited = record$1(fields) ? fields["rust-version"] : void 0;
|
|
2167
1587
|
break;
|
|
2168
1588
|
}
|
|
2169
1589
|
if (explicit !== void 0 || directory === ".") break;
|
|
@@ -2177,7 +1597,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2177
1597
|
}
|
|
2178
1598
|
if (name === "Pipfile") {
|
|
2179
1599
|
const requires = toml("python-version")?.["requires"];
|
|
2180
|
-
if (record$
|
|
1600
|
+
if (record$1(requires)) {
|
|
2181
1601
|
const minor = requires["python_version"];
|
|
2182
1602
|
if (minor !== void 0 && minor !== policy.python.split(".").slice(0, 2).join(".")) add({
|
|
2183
1603
|
rule: "python-version",
|
|
@@ -2196,7 +1616,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2196
1616
|
const parsed = toml("python-version");
|
|
2197
1617
|
if (parsed !== void 0) {
|
|
2198
1618
|
const project = parsed["project"];
|
|
2199
|
-
if (record$
|
|
1619
|
+
if (record$1(project) && project["requires-python"] !== void 0) {
|
|
2200
1620
|
if (!pythonRequirementMatches(project["requires-python"], policy.python)) add({
|
|
2201
1621
|
rule: "python-version",
|
|
2202
1622
|
line: lineOf("requires-python"),
|
|
@@ -2204,9 +1624,9 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2204
1624
|
});
|
|
2205
1625
|
}
|
|
2206
1626
|
const tool = parsed["tool"];
|
|
2207
|
-
const configuredUv = record$
|
|
1627
|
+
const configuredUv = record$1(tool) ? tool["uv"] : void 0;
|
|
2208
1628
|
const uv = name === "uv.toml" ? parsed : configuredUv;
|
|
2209
|
-
if (record$
|
|
1629
|
+
if (record$1(uv) && uv["python"] !== void 0) pin({
|
|
2210
1630
|
rule: "python-version",
|
|
2211
1631
|
value: uv["python"],
|
|
2212
1632
|
expected: policy.python,
|
|
@@ -2217,11 +1637,11 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2217
1637
|
}
|
|
2218
1638
|
if (isMiseConfigPath(file)) {
|
|
2219
1639
|
const tools = toml("runtime-manager")?.["tools"];
|
|
2220
|
-
if (record$
|
|
1640
|
+
if (record$1(tools)) for (const [tool, expected] of Object.entries(versions)) {
|
|
2221
1641
|
const configured = tools[tool];
|
|
2222
1642
|
if (configured !== void 0) pin({
|
|
2223
1643
|
rule: "runtime-manager",
|
|
2224
|
-
value: record$
|
|
1644
|
+
value: record$1(configured) ? configured["version"] : configured,
|
|
2225
1645
|
expected,
|
|
2226
1646
|
selector: nodeTools.has(tool) ? "node" : void 0,
|
|
2227
1647
|
label: `mise ${tool}`,
|
|
@@ -2506,7 +1926,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2506
1926
|
return diagnostics;
|
|
2507
1927
|
}
|
|
2508
1928
|
const nodeLine = (node) => {
|
|
2509
|
-
if (!record$
|
|
1929
|
+
if (!record$1(node) || !Array.isArray(node["range"])) return 1;
|
|
2510
1930
|
const offset = node["range"].at(0);
|
|
2511
1931
|
return typeof offset === "number" ? text.slice(0, offset).split("\n").length : 1;
|
|
2512
1932
|
};
|
|
@@ -2620,7 +2040,7 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
2620
2040
|
const manifest = JSON.parse(content ?? "");
|
|
2621
2041
|
pin({
|
|
2622
2042
|
rule,
|
|
2623
|
-
value: record$
|
|
2043
|
+
value: record$1(manifest) ? manifest["packageManager"] : void 0,
|
|
2624
2044
|
expected: `bun@${policy.bun}`,
|
|
2625
2045
|
label: "setup-bun version file packageManager",
|
|
2626
2046
|
line
|
|
@@ -3099,192 +2519,6 @@ const checkRuntimeFile = ({ file, text, policy, trackedFiles, readFile, reposito
|
|
|
3099
2519
|
return diagnostics;
|
|
3100
2520
|
};
|
|
3101
2521
|
//#endregion
|
|
3102
|
-
//#region src/toolchain-schema.ts
|
|
3103
|
-
const packagePinKeys = [
|
|
3104
|
-
"oxlint",
|
|
3105
|
-
"oxlint-tsgolint",
|
|
3106
|
-
"@oxlint/plugins",
|
|
3107
|
-
"@stll/oxlint-plugin",
|
|
3108
|
-
"oxfmt",
|
|
3109
|
-
"lefthook"
|
|
3110
|
-
];
|
|
3111
|
-
const valkeyArchitectures = ["amd64", "arm64"];
|
|
3112
|
-
const valkeyArtifactPackages = {
|
|
3113
|
-
server: "valkey-server",
|
|
3114
|
-
tools: "valkey-tools"
|
|
3115
|
-
};
|
|
3116
|
-
const record$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3117
|
-
const artifactRecord = (value, keys) => {
|
|
3118
|
-
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");
|
|
3119
|
-
return value;
|
|
3120
|
-
};
|
|
3121
|
-
const parseValkeyArtifacts = (input, version) => {
|
|
3122
|
-
const architectures = artifactRecord(input, valkeyArchitectures);
|
|
3123
|
-
const line = version.split(".").slice(0, 2).join(".");
|
|
3124
|
-
const parseArchitecture = (architecture) => {
|
|
3125
|
-
const packages = artifactRecord(architectures[architecture], Object.keys(valkeyArtifactPackages));
|
|
3126
|
-
const parseArtifact = (kind) => {
|
|
3127
|
-
const artifact = artifactRecord(packages[kind], ["url", "sha256"]);
|
|
3128
|
-
const url = artifact["url"];
|
|
3129
|
-
const sha256 = artifact["sha256"];
|
|
3130
|
-
const expected = `https://download.valkey.io/packaging/valkey-${line}/deb/ubuntu2404/${architecture}/${valkeyArtifactPackages[kind]}_${version}-1.noble_${architecture}.deb`;
|
|
3131
|
-
if (typeof url !== "string" || url !== expected) throw new Error("valkeyArtifacts URL must select the exact official versioned Ubuntu artifact");
|
|
3132
|
-
if (typeof sha256 !== "string" || !/^[a-f0-9]{64}$/.test(sha256)) throw new Error("valkeyArtifacts requires a lowercase SHA256 pin");
|
|
3133
|
-
return {
|
|
3134
|
-
url,
|
|
3135
|
-
sha256
|
|
3136
|
-
};
|
|
3137
|
-
};
|
|
3138
|
-
return {
|
|
3139
|
-
server: parseArtifact("server"),
|
|
3140
|
-
tools: parseArtifact("tools")
|
|
3141
|
-
};
|
|
3142
|
-
};
|
|
3143
|
-
return {
|
|
3144
|
-
amd64: parseArchitecture("amd64"),
|
|
3145
|
-
arm64: parseArchitecture("arm64")
|
|
3146
|
-
};
|
|
3147
|
-
};
|
|
3148
|
-
/** Reject unsupported or incomplete installed policies before inspecting a consumer. */
|
|
3149
|
-
const parseToolchainPolicy = (input) => {
|
|
3150
|
-
if (!record$1(input) || input["schemaVersion"] !== 1) throw new Error("toolchain.json must use schemaVersion 1");
|
|
3151
|
-
const string = (object, key) => {
|
|
3152
|
-
const value = object[key];
|
|
3153
|
-
if (typeof value !== "string" || value.trim() === "") throw new Error(`toolchain.json requires a nonempty ${key}`);
|
|
3154
|
-
return value;
|
|
3155
|
-
};
|
|
3156
|
-
const packages = {};
|
|
3157
|
-
for (const key of packagePinKeys) packages[key] = string(input, key);
|
|
3158
|
-
const bun = string(input, "bun");
|
|
3159
|
-
const typescript = string(input, "typescript");
|
|
3160
|
-
const node = string(input, "node");
|
|
3161
|
-
const python = string(input, "python");
|
|
3162
|
-
const postgres = string(input, "postgres");
|
|
3163
|
-
const valkey = string(input, "valkey");
|
|
3164
|
-
const rust = string(input, "rust");
|
|
3165
|
-
const rustCompilerDevelopment = string(input, "rustCompilerDevelopment");
|
|
3166
|
-
for (const [name, value] of Object.entries({
|
|
3167
|
-
...packages,
|
|
3168
|
-
bun,
|
|
3169
|
-
typescript
|
|
3170
|
-
})) if (valid(value) !== value) throw new Error(`toolchain.json ${name} must be an exact release`);
|
|
3171
|
-
if (!/^[1-9]\d*$/.test(postgres) || !Number.isSafeInteger(Number(postgres))) throw new Error("PostgreSQL must be a canonical positive major");
|
|
3172
|
-
if (valid(valkey) !== valkey || !/^\d+\.\d+\.\d+$/.test(valkey)) throw new Error("Valkey must be an exact stable release");
|
|
3173
|
-
const valkeyArtifacts = parseValkeyArtifacts(input["valkeyArtifacts"], valkey);
|
|
3174
|
-
if (!nodePolicyValid(node)) throw new Error("Node must be a canonical major N.x series");
|
|
3175
|
-
if (!/^\d+\.\d+(?:\.\d+)?$/.test(python)) throw new Error("Python must be an exact minor or patch release");
|
|
3176
|
-
if (!/^\d+\.\d+\.\d+$/.test(rust)) throw new Error("Rust must be an exact stable release");
|
|
3177
|
-
if (!/^nightly-\d{4}-\d{2}-\d{2}$/.test(rustCompilerDevelopment)) throw new Error("Rust compiler development must be a dated nightly");
|
|
3178
|
-
const compatibility = input["typescript6Compatibility"];
|
|
3179
|
-
if (!record$1(compatibility)) throw new Error("missing TypeScript compatibility policy");
|
|
3180
|
-
const version = string(compatibility, "version");
|
|
3181
|
-
const packageAlias = string(compatibility, "packageAlias");
|
|
3182
|
-
if (valid(version) !== version) throw new Error("TypeScript compatibility must be an exact release");
|
|
3183
|
-
const layouts = input["typescriptInstallLayouts"];
|
|
3184
|
-
if (!Array.isArray(layouts) || layouts.length === 0) throw new Error("missing TypeScript install layouts");
|
|
3185
|
-
const typescriptInstallLayouts = layouts.map((layout) => {
|
|
3186
|
-
if (!record$1(layout)) throw new Error("invalid TypeScript install layout");
|
|
3187
|
-
const compilerPackage = string(layout, "compilerPackage");
|
|
3188
|
-
const compilerSpecifier = string(layout, "compilerSpecifier");
|
|
3189
|
-
const typecheckCommand = string(layout, "typecheckCommand");
|
|
3190
|
-
switch (layout["type"]) {
|
|
3191
|
-
case "direct":
|
|
3192
|
-
if (compilerSpecifier !== typescript || compilerPackage !== "typescript") throw new Error("direct TypeScript layout differs from shared version");
|
|
3193
|
-
return {
|
|
3194
|
-
type: "direct",
|
|
3195
|
-
compilerPackage,
|
|
3196
|
-
compilerSpecifier,
|
|
3197
|
-
typecheckCommand
|
|
3198
|
-
};
|
|
3199
|
-
case "split-compatibility": {
|
|
3200
|
-
const compatibilityPackage = string(layout, "compatibilityPackage");
|
|
3201
|
-
const compatibilitySpecifier = string(layout, "compatibilitySpecifier");
|
|
3202
|
-
if (compilerSpecifier !== `npm:typescript@${typescript}` || compatibilitySpecifier !== version) throw new Error("split TypeScript layout differs from shared versions");
|
|
3203
|
-
return {
|
|
3204
|
-
type: "split-compatibility",
|
|
3205
|
-
compilerPackage,
|
|
3206
|
-
compilerSpecifier,
|
|
3207
|
-
compatibilityPackage,
|
|
3208
|
-
compatibilitySpecifier,
|
|
3209
|
-
typecheckCommand
|
|
3210
|
-
};
|
|
3211
|
-
}
|
|
3212
|
-
default: throw new Error("unsupported TypeScript install layout");
|
|
3213
|
-
}
|
|
3214
|
-
});
|
|
3215
|
-
const actionsInput = input["actions"];
|
|
3216
|
-
if (!record$1(actionsInput) || Object.keys(actionsInput).length === 0) throw new Error("missing approved actions");
|
|
3217
|
-
const actions = {};
|
|
3218
|
-
for (const [name, action] of Object.entries(actionsInput)) {
|
|
3219
|
-
if (!record$1(action)) throw new Error(`invalid action policy: ${name}`);
|
|
3220
|
-
const sha = string(action, "sha");
|
|
3221
|
-
const actionVersion = string(action, "version");
|
|
3222
|
-
if (!/^[a-f0-9]{40}$/.test(sha) || !/^v\d+(?:\.\d+)*$/.test(actionVersion)) throw new Error(`invalid action SHA/version: ${name}`);
|
|
3223
|
-
actions[name] = {
|
|
3224
|
-
sha,
|
|
3225
|
-
version: actionVersion
|
|
3226
|
-
};
|
|
3227
|
-
}
|
|
3228
|
-
const dependabotInput = input["dependabot"];
|
|
3229
|
-
if (!record$1(dependabotInput) || !record$1(dependabotInput["schedule"]) || !record$1(dependabotInput["cooldown"]) || !record$1(dependabotInput["groups"])) throw new Error("missing Dependabot policy");
|
|
3230
|
-
const scheduleInput = dependabotInput["schedule"];
|
|
3231
|
-
const schedule = {
|
|
3232
|
-
interval: string(scheduleInput, "interval"),
|
|
3233
|
-
day: string(scheduleInput, "day"),
|
|
3234
|
-
time: string(scheduleInput, "time"),
|
|
3235
|
-
timezone: string(scheduleInput, "timezone")
|
|
3236
|
-
};
|
|
3237
|
-
const defaultDays = dependabotInput["cooldown"]["defaultDays"];
|
|
3238
|
-
if (typeof defaultDays !== "number" || !Number.isInteger(defaultDays) || defaultDays < 5) throw new Error("Dependabot cooldown must be at least five days");
|
|
3239
|
-
const strings = (value) => {
|
|
3240
|
-
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) throw new Error("Dependabot group values must be string arrays");
|
|
3241
|
-
return value;
|
|
3242
|
-
};
|
|
3243
|
-
const groups = {};
|
|
3244
|
-
for (const [name, group] of Object.entries(dependabotInput["groups"])) {
|
|
3245
|
-
if (!record$1(group)) throw new Error(`invalid Dependabot group: ${name}`);
|
|
3246
|
-
groups[name] = {
|
|
3247
|
-
patterns: strings(group["patterns"]),
|
|
3248
|
-
updateTypes: strings(group["updateTypes"])
|
|
3249
|
-
};
|
|
3250
|
-
}
|
|
3251
|
-
return {
|
|
3252
|
-
bun,
|
|
3253
|
-
typescript,
|
|
3254
|
-
node,
|
|
3255
|
-
postgres,
|
|
3256
|
-
valkey,
|
|
3257
|
-
valkeyArtifacts,
|
|
3258
|
-
python,
|
|
3259
|
-
rust,
|
|
3260
|
-
rustCompilerDevelopment,
|
|
3261
|
-
packages,
|
|
3262
|
-
actions,
|
|
3263
|
-
typescriptInstallLayouts,
|
|
3264
|
-
typescript6Compatibility: {
|
|
3265
|
-
version,
|
|
3266
|
-
packageAlias
|
|
3267
|
-
},
|
|
3268
|
-
dependabot: {
|
|
3269
|
-
schedule,
|
|
3270
|
-
cooldown: { defaultDays },
|
|
3271
|
-
groups,
|
|
3272
|
-
ignoredPackages: [.../* @__PURE__ */ new Set([
|
|
3273
|
-
...Object.keys(packages),
|
|
3274
|
-
...typescriptInstallLayouts.flatMap((layout) => layout.type === "direct" ? [layout.compilerPackage] : [layout.compilerPackage, layout.compatibilityPackage]),
|
|
3275
|
-
packageAlias,
|
|
3276
|
-
"bun-types"
|
|
3277
|
-
])],
|
|
3278
|
-
ignoredActions: Object.keys(actions),
|
|
3279
|
-
ignoredImages: [
|
|
3280
|
-
"node",
|
|
3281
|
-
"python",
|
|
3282
|
-
"oven/bun"
|
|
3283
|
-
]
|
|
3284
|
-
}
|
|
3285
|
-
};
|
|
3286
|
-
};
|
|
3287
|
-
//#endregion
|
|
3288
2522
|
//#region src/toolchain-guard.ts
|
|
3289
2523
|
const toolchainRules = [.../* @__PURE__ */ new Set([
|
|
3290
2524
|
...packageRules,
|
|
@@ -3296,7 +2530,7 @@ const record = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
3296
2530
|
const array = (value) => Array.isArray(value);
|
|
3297
2531
|
/** Repository-owned declarations are explicit; malformed decisions fail closed. */
|
|
3298
2532
|
const parseToolchainConfiguration = (input) => {
|
|
3299
|
-
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
|
|
2533
|
+
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");
|
|
3300
2534
|
const disabled = /* @__PURE__ */ new Set();
|
|
3301
2535
|
const optOuts = input["optOuts"] ?? [];
|
|
3302
2536
|
if (!array(optOuts)) throw new Error("optOuts must be an array");
|
|
@@ -3310,7 +2544,8 @@ const parseToolchainConfiguration = (input) => {
|
|
|
3310
2544
|
disabled,
|
|
3311
2545
|
cloud: parseCloudSetup(input["cloud"]),
|
|
3312
2546
|
engineFloors: parseEngineFloors(input["engineFloors"]),
|
|
3313
|
-
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"])
|
|
2547
|
+
dynamicSelectors: parseDynamicSelectors(input["dynamicSelectors"]),
|
|
2548
|
+
consumerChecks: parseConsumerChecks(input["consumerChecks"])
|
|
3314
2549
|
};
|
|
3315
2550
|
};
|
|
3316
2551
|
/** Read one tracked configuration snapshot for guards and policy generation. */
|
|
@@ -3373,14 +2608,11 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3373
2608
|
}).trim();
|
|
3374
2609
|
repository = /^(?:https?:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(origin)?.[1];
|
|
3375
2610
|
} catch {}
|
|
3376
|
-
let
|
|
3377
|
-
let cloud;
|
|
2611
|
+
let configuration = parseToolchainConfiguration({});
|
|
3378
2612
|
const engineFloors = [];
|
|
3379
2613
|
let dynamicSelectors = [];
|
|
3380
2614
|
if (files["stll-toolchain.json"] !== void 0) try {
|
|
3381
|
-
|
|
3382
|
-
disabled = configuration.disabled;
|
|
3383
|
-
cloud = configuration.cloud;
|
|
2615
|
+
configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
|
|
3384
2616
|
dynamicSelectors = configuration.dynamicSelectors;
|
|
3385
2617
|
for (const entry of configuration.engineFloors) engineFloors.push(resolveEngineFloor(entry, files));
|
|
3386
2618
|
} catch (error) {
|
|
@@ -3391,6 +2623,7 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3391
2623
|
message: error instanceof Error ? error.message : String(error)
|
|
3392
2624
|
});
|
|
3393
2625
|
}
|
|
2626
|
+
const cloud = configuration.cloud;
|
|
3394
2627
|
const script = files[cloudSetupPath];
|
|
3395
2628
|
if (cloud === void 0 && trackedFiles.has(".agents/cloud-setup.sh")) diagnostics.push({
|
|
3396
2629
|
rule: "cloud-setup-drift",
|
|
@@ -3422,6 +2655,11 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3422
2655
|
const exercisedFloors = /* @__PURE__ */ new Set();
|
|
3423
2656
|
const mismatchedFloors = /* @__PURE__ */ new Set();
|
|
3424
2657
|
const selectorMatches = /* @__PURE__ */ new Map();
|
|
2658
|
+
diagnostics.push(...checkConsumerChecks({
|
|
2659
|
+
declarations: configuration.consumerChecks,
|
|
2660
|
+
files,
|
|
2661
|
+
policy
|
|
2662
|
+
}));
|
|
3425
2663
|
diagnostics.push(...checkPackageFiles({
|
|
3426
2664
|
files,
|
|
3427
2665
|
policy
|
|
@@ -3464,9 +2702,9 @@ const checkToolchain = ({ root, policy, onDelegated }) => {
|
|
|
3464
2702
|
files,
|
|
3465
2703
|
policy: policy.dependabot
|
|
3466
2704
|
}));
|
|
3467
|
-
return diagnostics.filter((diagnostic) => !disabled.has(diagnostic.rule));
|
|
2705
|
+
return diagnostics.filter((diagnostic) => !configuration.disabled.has(diagnostic.rule));
|
|
3468
2706
|
};
|
|
3469
2707
|
//#endregion
|
|
3470
|
-
export {
|
|
2708
|
+
export { parseToolchainConfiguration as n, readToolchainInputs as r, checkToolchain as t };
|
|
3471
2709
|
|
|
3472
2710
|
//# sourceMappingURL=toolchain-guard.mjs.map
|