@spunto/build 0.14.0 → 0.16.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.
- package/package.json +1 -1
- package/src/script/setup-script.ts +12 -84
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spunto/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Spunto's shared Build engine — the devcontainer image protocol and VS Code extension registry clients, with no database, no HTTP framework and no UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1480,12 +1480,12 @@ export function buildStartScript(params: StartScriptParams): { script: string; h
|
|
|
1480
1480
|
// The functions below power the *new* model where the agent (not the container) drives setup:
|
|
1481
1481
|
// `buildContainerScript` is the passive CMD (serve code-server + stay alive, no setup, no status
|
|
1482
1482
|
// machinery), and `buildSetupPlan` returns the ordered phases the agent execs one by one, each
|
|
1483
|
-
// wrapped in
|
|
1483
|
+
// wrapped in its own try/catch. The legacy `buildWorkerScript`/`buildSetupScript`/`buildStartScript`
|
|
1484
1484
|
// above are left untouched — the runner picks the path by flag, defaulting to legacy. See RFC 0017.
|
|
1485
1485
|
|
|
1486
|
-
/** One setup phase the agent execs via `docker exec
|
|
1486
|
+
/** One setup phase the agent execs via `docker exec`. */
|
|
1487
1487
|
export type SetupPhase = {
|
|
1488
|
-
/** Timing
|
|
1488
|
+
/** Timing key and the phase's short identity. */
|
|
1489
1489
|
id: "credentials" | "shell" | "dotfiles" | "skills" | "clone" | "postCreate" | "postStart"
|
|
1490
1490
|
/** Human label for logs. */
|
|
1491
1491
|
label: string
|
|
@@ -1500,7 +1500,7 @@ export type SetupPhase = {
|
|
|
1500
1500
|
|
|
1501
1501
|
// The passive container CMD. Same "serve + stay alive" blocks as buildStartScript (SSH, feature
|
|
1502
1502
|
// entrypoints, env persistence, extensions, code-server settings + restart loop) — MINUS the setup
|
|
1503
|
-
// status machinery, the lifecycle commands and any
|
|
1503
|
+
// status machinery, the lifecycle commands and any timing emission (the agent owns all of that
|
|
1504
1504
|
// now). Ends by signalling readiness (`.mp-container-up`) then `wait`. Blocks are duplicated from
|
|
1505
1505
|
// buildStartScript on purpose: the legacy path stays byte-identical (prod default) until it's
|
|
1506
1506
|
// deleted once WORKER_SETUP_ORCHESTRATOR=agent becomes the default. See RFC 0017 §2.1.
|
|
@@ -1714,9 +1714,6 @@ export function buildWorkerScript(params: {
|
|
|
1714
1714
|
" local now; now=\"$(_mp_now)\"",
|
|
1715
1715
|
" if [ -s \"$_MP_TFILE\" ]; then printf ',' >> \"$_MP_TFILE\"; fi",
|
|
1716
1716
|
" printf '\"%s\":%s' \"$1\" \"$now\" >> \"$_MP_TFILE\" || true",
|
|
1717
|
-
// Native OTel: emit this phase transition as a real OTLP span through the node
|
|
1718
|
-
// collector (|| true + suspends set -e inside the fn — telemetry never breaks setup).
|
|
1719
|
-
" _mp_span_on_stamp \"$1\" \"$now\" || true",
|
|
1720
1717
|
"}",
|
|
1721
1718
|
// mp-status.json lives in the home dir (persistent volume), not /tmp — when a
|
|
1722
1719
|
// DinD feature starts dockerd in the background, /tmp's mount visibility can
|
|
@@ -1733,70 +1730,14 @@ export function buildWorkerScript(params: {
|
|
|
1733
1730
|
" printf '%s' \"$j\" > " + workerStatusPath("/home/vscode") + " || true",
|
|
1734
1731
|
"}",
|
|
1735
1732
|
"",
|
|
1736
|
-
// ── Native OTel setup spans (emitted by this script, not backdated in the API) ──
|
|
1737
|
-
// Each phase is emitted as a real OTLP span to the node agent's collector (the
|
|
1738
|
-
// auto-injected OTEL_EXPORTER_OTLP_ENDPOINT); the agent forwards to /otlp where org.id
|
|
1739
|
-
// /node.id are stamped server-side. MP_SETUP_TRACEPARENT (the job.worker.spawn span,
|
|
1740
|
-
// injected by the runner) gives the traceId + parent span, so the whole `worker.setup`
|
|
1741
|
-
// subtree nests under the spawn trace. No trace context (or no curl) → silently skipped.
|
|
1742
|
-
"MP_TRACE_ID=''; MP_JOB_SPAN=''",
|
|
1743
|
-
"if [ -n \"${MP_SETUP_TRACEPARENT:-}\" ]; then",
|
|
1744
|
-
" MP_TRACE_ID=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f2)",
|
|
1745
|
-
" MP_JOB_SPAN=$(printf '%s' \"$MP_SETUP_TRACEPARENT\" | cut -d- -f3)",
|
|
1746
|
-
"fi",
|
|
1747
|
-
"MP_SETUP_SPAN=''; MP_SETUP_START=''; MP_PREV_PHASE=''; MP_PREV_TS=''",
|
|
1748
1733
|
// Failure-trap state. _MP_DONE flips to 1 once setup+start reach `ready` (see buildStartScript),
|
|
1749
1734
|
// disarming _mp_fail so a normal container stop / code-server hiccup after ready never looks like
|
|
1750
1735
|
// a setup failure. _MP_LAST holds the most recent status JSON (set by _mp) for the error re-emit.
|
|
1751
1736
|
"_MP_DONE=0; _MP_LAST=''",
|
|
1752
|
-
|
|
1753
|
-
"_mp_phase_span_name() {",
|
|
1754
|
-
" case \"$1\" in",
|
|
1755
|
-
" initializing) echo 'setup.initialize' ;;",
|
|
1756
|
-
" credentials) echo 'setup.credentials' ;;",
|
|
1757
|
-
" shell) echo 'setup.shell' ;;",
|
|
1758
|
-
" cloning) echo 'setup.clone' ;;",
|
|
1759
|
-
" postCreate) echo 'setup.postCreate' ;;",
|
|
1760
|
-
" postStart) echo 'setup.postStart' ;;",
|
|
1761
|
-
" *) echo \"setup.$1\" ;;",
|
|
1762
|
-
" esac",
|
|
1763
|
-
"}",
|
|
1764
|
-
// Emit one OTLP/HTTP JSON span, backgrounded + short timeout so it never adds latency
|
|
1765
|
-
// to (or blocks) the setup path. Args: name start_ms end_ms span_id parent_span_id phase.
|
|
1766
|
-
"_mp_span_emit() {",
|
|
1767
|
-
" if [ -z \"$MP_TRACE_ID\" ] || [ -z \"${OTEL_EXPORTER_OTLP_ENDPOINT:-}\" ]; then return 0; fi",
|
|
1768
|
-
" if ! command -v curl >/dev/null 2>&1; then return 0; fi",
|
|
1769
|
-
// Optional $7 = error message and $8 = exit code turn this into a native ERROR span
|
|
1770
|
-
// (OTel status code 2 + a setup.exit_code attribute) — that's what makes a failed
|
|
1771
|
-
// clone/postCreate/postStart show up red in the waterfall instead of as a hole.
|
|
1772
|
-
" local name=\"$1\" s_ns=$(( $2 * 1000000 )) e_ns=$(( $3 * 1000000 )) sid=\"$4\" pid=\"$5\" phase=\"$6\" errmsg=\"${7:-}\" ecode=\"${8:-}\" dur=$(( $3 - $2 )) attrs='' status=''",
|
|
1773
|
-
" if [ -n \"$phase\" ]; then attrs=\"{\\\"key\\\":\\\"setup.phase\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"$phase\\\"}},{\\\"key\\\":\\\"setup.duration_ms\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$dur\\\"}}\"; fi",
|
|
1774
|
-
" if [ -n \"$ecode\" ]; then [ -n \"$attrs\" ] && attrs=\"$attrs,\"; attrs=\"${attrs}{\\\"key\\\":\\\"setup.exit_code\\\",\\\"value\\\":{\\\"intValue\\\":\\\"$ecode\\\"}}\"; fi",
|
|
1775
|
-
" if [ -n \"$errmsg\" ]; then status=\",\\\"status\\\":{\\\"code\\\":2,\\\"message\\\":\\\"$errmsg\\\"}\"; fi",
|
|
1776
|
-
" local body=\"{\\\"resourceSpans\\\":[{\\\"resource\\\":{\\\"attributes\\\":[{\\\"key\\\":\\\"service.name\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"spunto-worker\\\"}},{\\\"key\\\":\\\"spunto.worker.slug\\\",\\\"value\\\":{\\\"stringValue\\\":\\\"${WORKER_SLUG:-}\\\"}}]},\\\"scopeSpans\\\":[{\\\"scope\\\":{\\\"name\\\":\\\"spunto.setup\\\"},\\\"spans\\\":[{\\\"traceId\\\":\\\"$MP_TRACE_ID\\\",\\\"spanId\\\":\\\"$sid\\\",\\\"parentSpanId\\\":\\\"$pid\\\",\\\"name\\\":\\\"$name\\\",\\\"kind\\\":1,\\\"startTimeUnixNano\\\":\\\"$s_ns\\\",\\\"endTimeUnixNano\\\":\\\"$e_ns\\\",\\\"attributes\\\":[$attrs]$status}]}]}]}\"",
|
|
1777
|
-
" ( curl -s -m 3 -o /dev/null -X POST \"$OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces\" -H 'Content-Type: application/json' -d \"$body\" || true ) &",
|
|
1778
|
-
"}",
|
|
1779
|
-
// Called on each new phase stamp. The first stamp opens the `worker.setup` window; every
|
|
1780
|
-
// later stamp closes the previous phase as a child span; `ready` also emits the parent.
|
|
1781
|
-
"_mp_span_on_stamp() {",
|
|
1782
|
-
" if [ -z \"$MP_TRACE_ID\" ]; then return 0; fi",
|
|
1783
|
-
" local key=\"$1\" now=\"$2\"",
|
|
1784
|
-
" if [ -z \"$MP_SETUP_SPAN\" ]; then",
|
|
1785
|
-
" MP_SETUP_SPAN=$(_mp_rand_id); MP_SETUP_START=\"$now\"; MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"; return 0",
|
|
1786
|
-
" fi",
|
|
1787
|
-
" _mp_span_emit \"$(_mp_phase_span_name \"$MP_PREV_PHASE\")\" \"$MP_PREV_TS\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$MP_PREV_PHASE\"",
|
|
1788
|
-
" if [ \"$key\" = ready ]; then",
|
|
1789
|
-
" _mp_span_emit 'worker.setup' \"$MP_SETUP_START\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" ''",
|
|
1790
|
-
" else",
|
|
1791
|
-
" MP_PREV_PHASE=\"$key\"; MP_PREV_TS=\"$now\"",
|
|
1792
|
-
" fi",
|
|
1793
|
-
"}",
|
|
1794
|
-
// ── Native error spans on setup failure (the direct goal of this refactor) ──
|
|
1737
|
+
// ── Setup failure trap ──
|
|
1795
1738
|
// set -e makes any uncaught failure abort the CMD; before PR #115 that just left the worker
|
|
1796
|
-
// stuck at its last status
|
|
1797
|
-
//
|
|
1798
|
-
// failing clone/postCreate/postStart shows up red under worker.setup, and (2) a phase=error
|
|
1799
|
-
// status write so the API poller flips the worker to `error` with a message. It fires only on a
|
|
1739
|
+
// stuck at its last status. This EXIT trap turns that same failure into a phase=error status
|
|
1740
|
+
// write so the API poller flips the worker to `error` with a message. It fires only on a
|
|
1800
1741
|
// non-zero exit and only before `ready` (_MP_DONE guards the normal path); the short sleep lets
|
|
1801
1742
|
// the 1.5s poll read the error status before the container exits.
|
|
1802
1743
|
"_mp_fail() {",
|
|
@@ -1806,26 +1747,13 @@ export function buildWorkerScript(params: {
|
|
|
1806
1747
|
" [ \"$_MP_DONE\" = 1 ] && return 0",
|
|
1807
1748
|
" [ \"$code\" = 0 ] && return 0",
|
|
1808
1749
|
" _MP_DONE=1",
|
|
1809
|
-
" local phase
|
|
1810
|
-
" phase
|
|
1811
|
-
" [ -z \"$phase\" ] && phase=$(printf '%s' \"$_MP_LAST\" | sed -n 's/.*\"phase\":\"\\([^\"]*\\)\".*/\\1/p')",
|
|
1750
|
+
" local phase msg j",
|
|
1751
|
+
" phase=$(printf '%s' \"$_MP_LAST\" | sed -n 's/.*\"phase\":\"\\([^\"]*\\)\".*/\\1/p')",
|
|
1812
1752
|
" [ -z \"$phase\" ] && phase=initializing",
|
|
1813
|
-
" now=\"$(_mp_now)\"",
|
|
1814
1753
|
// A phase that knows *why* it failed (e.g. a clone on a branch that doesn't exist) sets
|
|
1815
1754
|
// MP_FAIL_MSG before exiting, so the UI shows that instead of the generic phase message.
|
|
1816
1755
|
" msg=\"${MP_FAIL_MSG:-setup phase $phase failed (exit $code)}\"",
|
|
1817
1756
|
" echo \"[setup] ✗ $msg\" >&2",
|
|
1818
|
-
// Best-effort error spans: close the failing phase (child of worker.setup) then worker.setup
|
|
1819
|
-
// itself in ERROR. On a restart (postStart failure) the setup window was never opened, so emit
|
|
1820
|
-
// a single worker.setup error span under the job span instead.
|
|
1821
|
-
" if [ -n \"$MP_TRACE_ID\" ]; then",
|
|
1822
|
-
" if [ -n \"$MP_SETUP_SPAN\" ]; then",
|
|
1823
|
-
" _mp_span_emit \"$(_mp_phase_span_name \"$phase\")\" \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_SETUP_SPAN\" \"$phase\" \"$msg\" \"$code\"",
|
|
1824
|
-
" _mp_span_emit 'worker.setup' \"${MP_SETUP_START:-$now}\" \"$now\" \"$MP_SETUP_SPAN\" \"$MP_JOB_SPAN\" '' \"$msg\" \"$code\"",
|
|
1825
|
-
" else",
|
|
1826
|
-
" _mp_span_emit 'worker.setup' \"${MP_PREV_TS:-$now}\" \"$now\" \"$(_mp_rand_id)\" \"$MP_JOB_SPAN\" \"$phase\" \"$msg\" \"$code\"",
|
|
1827
|
-
" fi",
|
|
1828
|
-
" fi",
|
|
1829
1757
|
// Flip .mp-status.json to phase=error (preserving the rest) so monitorSetup surfaces it.
|
|
1830
1758
|
" j=\"$_MP_LAST\"",
|
|
1831
1759
|
" if [ -n \"$j\" ]; then",
|
|
@@ -1855,10 +1783,10 @@ export function buildWorkerScript(params: {
|
|
|
1855
1783
|
|
|
1856
1784
|
// ─── buildSetupPlan (RFC 0017) ─────────────────────────────────────────────────
|
|
1857
1785
|
//
|
|
1858
|
-
// The ordered phases the agent execs one by one (each in its own `docker exec`
|
|
1786
|
+
// The ordered phases the agent execs one by one (each in its own `docker exec`) when
|
|
1859
1787
|
// WORKER_SETUP_ORCHESTRATOR=agent. Reuses the exact bash blocks from buildSetupScript/buildStartScript
|
|
1860
|
-
// (clone strategies, oh-my-zsh, dotfiles, lifecycle) MINUS the `_mp`/`_mp_stamp
|
|
1861
|
-
// agent owns status
|
|
1788
|
+
// (clone strategies, oh-my-zsh, dotfiles, lifecycle) MINUS the `_mp`/`_mp_stamp` machinery — the
|
|
1789
|
+
// agent owns status now. Each phase carries the SetupStatus to persist at its start/end
|
|
1862
1790
|
// (mirrors the legacy `mkStatus(...)` writes) so the DB/UI progression is identical. See RFC 0017 §2.2.
|
|
1863
1791
|
|
|
1864
1792
|
export function buildSetupPlan(params: SetupScriptParams): {
|