@stll/oxlint-config 0.12.2 → 0.15.0

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.
Files changed (88) hide show
  1. package/README.md +261 -1
  2. package/dist/cloud-setup-cli.cjs +6 -4
  3. package/dist/cloud-setup-cli.cjs.map +1 -1
  4. package/dist/cloud-setup-cli.mjs +3 -1
  5. package/dist/cloud-setup-cli.mjs.map +1 -1
  6. package/dist/compiler-packages.cjs +28 -0
  7. package/dist/compiler-packages.cjs.map +1 -0
  8. package/dist/compiler-packages.mjs +17 -0
  9. package/dist/compiler-packages.mjs.map +1 -0
  10. package/dist/consumer-compat-cli.cjs +1091 -0
  11. package/dist/consumer-compat-cli.cjs.map +1 -0
  12. package/dist/consumer-compat-cli.d.cts +1 -0
  13. package/dist/consumer-compat-cli.d.mts +1 -0
  14. package/dist/consumer-compat-cli.mjs +1089 -0
  15. package/dist/consumer-compat-cli.mjs.map +1 -0
  16. package/dist/index.cjs +50 -40
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +46 -41
  19. package/dist/index.d.mts +46 -41
  20. package/dist/index.mjs +50 -41
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/publish-build-target-nuxt-helper.cjs +71 -0
  23. package/dist/publish-build-target-nuxt-helper.cjs.map +1 -0
  24. package/dist/publish-build-target-nuxt-helper.d.cts +10 -0
  25. package/dist/publish-build-target-nuxt-helper.d.mts +10 -0
  26. package/dist/publish-build-target-nuxt-helper.mjs +68 -0
  27. package/dist/publish-build-target-nuxt-helper.mjs.map +1 -0
  28. package/dist/publish-build-target-vite-dts-helper.cjs +3 -0
  29. package/dist/publish-build-target-vite-dts-helper.d.cts +24 -0
  30. package/dist/publish-build-target-vite-dts-helper.d.mts +24 -0
  31. package/dist/publish-build-target-vite-dts-helper.mjs +2 -0
  32. package/dist/publish-build-target-vite-dts-helper2.cjs +372 -0
  33. package/dist/publish-build-target-vite-dts-helper2.cjs.map +1 -0
  34. package/dist/publish-build-target-vite-dts-helper2.mjs +359 -0
  35. package/dist/publish-build-target-vite-dts-helper2.mjs.map +1 -0
  36. package/dist/publish-contract-cli.cjs +983 -0
  37. package/dist/publish-contract-cli.cjs.map +1 -0
  38. package/dist/publish-contract-cli.d.cts +1 -0
  39. package/dist/publish-contract-cli.d.mts +1 -0
  40. package/dist/publish-contract-cli.mjs +981 -0
  41. package/dist/publish-contract-cli.mjs.map +1 -0
  42. package/dist/publish-contract.cjs +431 -0
  43. package/dist/publish-contract.cjs.map +1 -0
  44. package/dist/publish-contract.mjs +396 -0
  45. package/dist/publish-contract.mjs.map +1 -0
  46. package/dist/rolldown-runtime.cjs +28 -0
  47. package/dist/toolchain-changed-cli.cjs +34 -0
  48. package/dist/toolchain-changed-cli.cjs.map +1 -0
  49. package/dist/toolchain-changed-cli.d.cts +1 -0
  50. package/dist/toolchain-changed-cli.d.mts +1 -0
  51. package/dist/toolchain-changed-cli.mjs +34 -0
  52. package/dist/toolchain-changed-cli.mjs.map +1 -0
  53. package/dist/toolchain-changed.cjs +1470 -0
  54. package/dist/toolchain-changed.cjs.map +1 -0
  55. package/dist/toolchain-changed.mjs +1463 -0
  56. package/dist/toolchain-changed.mjs.map +1 -0
  57. package/dist/toolchain-check-cli.cjs +2 -1
  58. package/dist/toolchain-check-cli.cjs.map +1 -1
  59. package/dist/toolchain-check-cli.mjs +2 -1
  60. package/dist/toolchain-check-cli.mjs.map +1 -1
  61. package/dist/toolchain-guard.cjs +360 -1161
  62. package/dist/toolchain-guard.cjs.map +1 -1
  63. package/dist/toolchain-guard.mjs +338 -1100
  64. package/dist/toolchain-guard.mjs.map +1 -1
  65. package/dist/toolchain-inputs.cjs +926 -0
  66. package/dist/toolchain-inputs.cjs.map +1 -0
  67. package/dist/toolchain-inputs.mjs +816 -0
  68. package/dist/toolchain-inputs.mjs.map +1 -0
  69. package/dist/toolchain-schema.cjs +702 -0
  70. package/dist/toolchain-schema.cjs.map +1 -0
  71. package/dist/toolchain-schema.mjs +580 -0
  72. package/dist/toolchain-schema.mjs.map +1 -0
  73. package/dist/typecheck-parity-cli.cjs +38 -898
  74. package/dist/typecheck-parity-cli.cjs.map +1 -1
  75. package/dist/typecheck-parity-cli.mjs +39 -899
  76. package/dist/typecheck-parity-cli.mjs.map +1 -1
  77. package/dist/typecheck-parity.cjs +905 -0
  78. package/dist/typecheck-parity.cjs.map +1 -0
  79. package/dist/typecheck-parity.mjs +894 -0
  80. package/dist/typecheck-parity.mjs.map +1 -0
  81. package/dist/typecheck-probe-cli.cjs +176 -0
  82. package/dist/typecheck-probe-cli.cjs.map +1 -0
  83. package/dist/typecheck-probe-cli.d.cts +1 -0
  84. package/dist/typecheck-probe-cli.d.mts +1 -0
  85. package/dist/typecheck-probe-cli.mjs +174 -0
  86. package/dist/typecheck-probe-cli.mjs.map +1 -0
  87. package/package.json +43 -5
  88. package/toolchain.json +9 -1
@@ -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, subset, valid, validRange } from "semver";
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
- //#region src/cloud-setup-install.ts
9
- const packageInstall = String.raw`
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/cloud-setup-schema.ts
262
- const cloudServices = ["postgres", "valkey"];
263
- const cloudInstallCommand = "bun install --frozen-lockfile";
264
- const cloudService = (value) => cloudServices.some((service) => value === service);
265
- /** Parse an explicit declaration without assuming services for undeclared repositories. */
266
- const parseCloudSetup = (input) => {
267
- if (input === void 0) return void 0;
268
- if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("cloud must be an object");
269
- for (const key of Object.keys(input)) if (key !== "services" && key !== "install" && key !== "envFile") throw new Error(`unsupported cloud key: ${key}`);
270
- const selected = "services" in input ? input.services : [];
271
- if (!Array.isArray(selected)) throw new Error("cloud.services must be an array when provided");
272
- if (!("install" in input) || input.install !== "bun install --frozen-lockfile") throw new Error(`cloud.install must be exactly ${cloudInstallCommand}`);
273
- const seen = /* @__PURE__ */ new Set();
274
- const services = Array.from(selected, (service) => {
275
- if (!cloudService(service)) throw new Error("cloud.services contains an unsupported service");
276
- if (seen.has(service)) throw new Error(`cloud.services contains duplicate service: ${service}`);
277
- seen.add(service);
278
- return service;
279
- });
280
- if (services.length === 0) {
281
- if ("envFile" in input) throw new Error("cloud.envFile is not allowed without services");
282
- return {
283
- services,
284
- install: cloudInstallCommand
285
- };
11
+ //#region src/toolchain-consumer-checks.ts
12
+ const consumerRunnerVersion = "0.15.0";
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
- if (!("envFile" in input) || typeof input.envFile !== "string") throw new Error("cloud.envFile must be a canonical repository-relative path");
288
- const envFile = input.envFile;
289
- if (envFile.startsWith("/") || /^[A-Za-z]:/.test(envFile) || envFile.includes("\\") || /\p{Cc}/u.test(envFile) || envFile.split("/").some((part) => part === "" || part === "." || part === "..")) throw new Error("cloud.envFile must be a canonical repository-relative path");
290
- return {
291
- services,
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
- //#endregion
297
- //#region src/cloud-setup-services.ts
298
- const common = String.raw`
299
- service_file() {
300
- local file="$1" owner="$2"
301
- [[ ! -L "$file" ]] || fail "Service state must not contain symbolic links"
302
- if [[ -e "$file" ]]; then
303
- [[ -f "$file" && "$(stat -c '%u:%a' "$file")" == "$owner:600" ]] || fail "Invalid service state file"
304
- fi
305
- }
306
- service_directory() {
307
- local directory="$1"
308
- [[ ! -L "$directory" ]] || fail "Service directory must not be a symbolic link"
309
- if [[ -e "$directory" ]]; then
310
- [[ -d "$directory" && "$(stat -c '%u:%a' "$directory")" == "$SERVICE_UID:700" ]] || fail "Invalid service directory"
311
- else
312
- root install -d -m 700 -o stll-cloud -g stll-cloud "$directory"
313
- fi
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
- //#endregion
463
- //#region src/cloud-setup.ts
464
- const cloudSetupPath = ".agents/cloud-setup.sh";
465
- const cloudEnvironmentMarker = "# Generated by stll-cloud-setup; disposable local test services.";
466
- const script = String.raw`#!/usr/bin/env bash
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
- //#endregion
647
- //#region src/toolchain-container-inputs.ts
648
- const record$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
649
- /** Filename class recognized by Dependabot's Docker Compose fetcher. */
650
- const isComposeDefinitionPath = (file) => /^\.?(?:docker-)?compose(?:-[\w]+)?(?:\.[\w-]+)?\.ya?ml$/i.test(file.split("/").at(-1) ?? "");
651
- /** Candidate YAML files are classified by their document shape after reading. */
652
- const isKubernetesDefinitionPath = (file) => /^[^.].*\.ya?ml$/i.test(file.split("/").at(-1) ?? "");
653
- const kubernetesPodSpecPaths = {
654
- Pod: ["spec"],
655
- Deployment: [
656
- "spec",
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
- ecosystem: "docker-compose",
709
- images
132
+ workflow: entry["workflow"],
133
+ job: entry["job"],
134
+ packages: entry["packages"],
135
+ toolingVersion: entry["toolingVersion"],
136
+ fixturePath: entry["fixturePath"]
710
137
  };
711
- }
712
- if (typeof document["apiVersion"] !== "string" || typeof document["kind"] !== "string") return void 0;
713
- const images = [];
714
- const visited = /* @__PURE__ */ new Set();
715
- const walkResource = (resource, prefix) => {
716
- if (visited.has(resource)) return;
717
- visited.add(resource);
718
- if (typeof resource["apiVersion"] !== "string" || typeof resource["kind"] !== "string") return;
719
- if (resource["kind"] === "List" && Array.isArray(resource["items"])) {
720
- for (const [index, item] of resource["items"].entries()) if (record$7(item)) walkResource(item, [
721
- ...prefix,
722
- "items",
723
- index
724
- ]);
725
- return;
726
- }
727
- const specPath = Object.entries(kubernetesPodSpecPaths).find(([kind]) => kind === resource["kind"])?.[1];
728
- if (specPath === void 0) return;
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
- walkResource(document, []);
752
- return images.length === 0 ? void 0 : {
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$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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$6(tool) ? tool["uv"] : void 0;
865
- return record$6(uv) ? uv["workspace"] : void 0;
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$6(parsed)) throw new Error(`${workspaceFile}:1: pnpm workspace must contain an object`);
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$6(parsed)) continue;
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$6(value) ? value[ecosystem === "npm" ? "packages" : "members"] : value;
901
- const members = ecosystem === "cargo" && record$6(value) && patterns === void 0 ? [] : patterns;
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$6(value) && strings(value["exclude"]) ? value["exclude"].map((pattern) => `!${pattern}`) : [];
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$6(workspace) ? workspace["dependencies"] : void 0;
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$6(targets)) {
958
- for (const target of Object.values(targets)) if (record$6(target)) dependencyOwners.push(target);
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$6(dependencies)) continue;
386
+ if (!record$5(dependencies)) continue;
967
387
  for (const [name, dependency] of Object.entries(dependencies)) {
968
- if (!record$6(dependency)) continue;
388
+ if (!record$5(dependency)) continue;
969
389
  const isInherited = dependency["workspace"] === true;
970
- const resolved = isInherited && record$6(inherited) ? inherited[name] : dependency;
971
- if (!record$6(resolved) || typeof resolved["path"] !== "string") continue;
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$6(manifest) && typeof manifest["packageManager"] === "string" && manifest["packageManager"].startsWith("bun@")) return "bun";
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$6(tool) && record$6(tool["uv"])) return "uv";
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$6(parsed) || parsed["version"] !== 2 || !Array.isArray(parsed["updates"])) {
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$6(legacy) && Array.isArray(legacy["updates"]) ? legacy["updates"] : [];
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$6(update) || typeof update["package-ecosystem"] !== "string") {
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$6(legacyUpdate) ? legacyUpdate["schedule"] : void 0;
1144
- if (record$6(schedule) && schedule["time"] === policy.schedule.time && (!record$6(legacySchedule) || legacySchedule["time"] !== policy.schedule.time)) report("schedule", `${ecosystem}: quote schedule time to preserve its string value in YAML 1.1`);
1145
- if (!record$6(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`);
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$6(cooldown) || Object.keys(cooldown).length !== 1 || cooldown["default-days"] !== policy.cooldown.defaultDays) report("cooldown", `${ecosystem}: cooldown must contain only default-days: ${policy.cooldown.defaultDays}`);
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$6(groups) || Object.keys(groups).length !== Object.keys(policy.groups).length || Object.entries(policy.groups).some(([name, expected]) => {
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$6(group) || Object.keys(group).length !== 2 || !sameStrings(group["patterns"], expected.patterns) || !sameStrings(group["update-types"], expected.updateTypes);
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$6(entry) || typeof entry["dependency-name"] !== "string" || !ignored.includes(entry["dependency-name"]))) report("ignore", `${ecosystem}: ignore only packages owned by the shared policy`);
1157
- for (const name of ignored) if (!Array.isArray(ignore) || !ignore.some((entry) => record$6(entry) && entry["dependency-name"] === name && Object.keys(entry).length === 1)) report("ignore", `${ecosystem}: ignore all updates for shared pin ${name}`);
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$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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$5(entry) || Object.keys(entry).some((key) => ![
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$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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$4(entry) || Object.keys(entry).some((key) => ![
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$4(manifest) || manifest["private"] === true || typeof manifest["name"] !== "string" || manifest["name"].trim() === "" || !record$4(manifest["engines"]) || typeof manifest["engines"]["node"] !== "string") throw new Error("engine floor package must be published and declare engines.node");
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$4(workflow) || !record$4(workflow["jobs"])) throw new Error("engine floor job must exist in its tracked workflow");
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$4(job)) throw new Error("engine floor job must exist in its tracked workflow");
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$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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"'\\;&|()]|\\[^\n]|"(?:[^"\\]|\\.)*"|'[^']*')+|&&|\|\||[();&|\n]/g);
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 invokesCompiler = (words) => {
1424
- const literal = (word) => {
1425
- let result = "";
1426
- let quote;
1427
- for (let index = 0; index < word.length; index += 1) {
1428
- const character = word.at(index);
1429
- if (character === quote) {
1430
- quote = void 0;
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
- return result;
1444
- };
1445
- const tokens = words.map(literal);
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$3(json)) throw new Error("manifest must be an object");
922
+ if (!record$2(json)) throw new Error("manifest must be an object");
1503
923
  if (pnpm) {
1504
- if (json["catalog"] !== void 0 && !record$3(json["catalog"])) throw new Error("catalog must be a mapping");
1505
- if (json["catalogs"] !== void 0 && (!record$3(json["catalogs"]) || Object.values(json["catalogs"]).some((catalog) => !record$3(catalog)))) throw new Error("named catalogs must be mappings");
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$3(json["catalogs"]) ? Object.entries(json["catalogs"]).map(([name, catalog]) => ({
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$3(catalog)) continue;
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$3(json["workspaces"]) ? [json["workspaces"]] : []];
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$3(workspace["catalogs"]) ? workspace["catalogs"]["default"] : void 0);
1568
- return record$3(workspace["catalogs"]) ? workspace["catalogs"][name] : void 0;
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$3(source["catalogs"]) && source["catalogs"][name] !== void 0) return source["catalogs"][name];
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$3(catalog) ? catalog[name] : void 0;
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$3(workspaces) ? workspaces["packages"] : workspaces,
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$3(dependencies)) continue;
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$3(entries)) return;
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$3(json["peerDependencies"])) {
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$3(source["catalogs"])) continue;
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$3(entries)) continue;
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$3(entries)) continue;
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$3(entries)) return;
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$3(replacement)) {
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$3(json["pnpm"])) checkResolutionMap(json["pnpm"]["overrides"]);
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$3(json["scripts"]) ? json["scripts"]["typecheck"] : void 0;
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$3(json["peerDependencies"])) {
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$3(json["engines"]) && json["engines"]["node"] !== void 0 && (typeof json["engines"]["node"] !== "string" || !nodeSupportRangeMatches(policy.node, json["engines"]["node"]))) add({
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$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
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$2(toolchain) ? toolchain["components"] : void 0;
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$2(toolchain) ? toolchain["channel"] : void 0,
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$2(workspace) ? workspace["package"] : void 0;
2150
- if (record$2(defaults) && defaults["rust-version"] !== void 0) rustFloor(defaults["rust-version"]);
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$2(pkg) ? pkg["rust-version"] : void 0;
2153
- if (version !== void 0) if (!record$2(version)) rustFloor(version);
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$2(pkg)) {
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$2(table)) {
1584
+ if (record$1(table)) {
2165
1585
  const fields = table["package"];
2166
- inherited = record$2(fields) ? fields["rust-version"] : void 0;
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$2(requires)) {
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$2(project) && project["requires-python"] !== void 0) {
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$2(tool) ? tool["uv"] : void 0;
1627
+ const configuredUv = record$1(tool) ? tool["uv"] : void 0;
2208
1628
  const uv = name === "uv.toml" ? parsed : configuredUv;
2209
- if (record$2(uv) && uv["python"] !== void 0) pin({
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$2(tools)) for (const [tool, expected] of Object.entries(versions)) {
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$2(configured) ? configured["version"] : configured,
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$2(node) || !Array.isArray(node["range"])) return 1;
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$2(manifest) ? manifest["packageManager"] : void 0,
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 dynamicSelectors declarations");
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 disabled = /* @__PURE__ */ new Set();
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
- const configuration = parseToolchainConfiguration(JSON.parse(files["stll-toolchain.json"]));
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 { cloudSetupPath as a, parseToolchainPolicy as i, parseToolchainConfiguration as n, generateCloudSetup as o, readToolchainInputs as r, checkToolchain as t };
2708
+ export { parseToolchainConfiguration as n, readToolchainInputs as r, checkToolchain as t };
3471
2709
 
3472
2710
  //# sourceMappingURL=toolchain-guard.mjs.map