Skip to content

Commit 17ae3e6

Browse files
authored
build(scripts): convert the last 14 standalone .mjs scripts to real .ts (#7751)
* build(scripts): convert every remaining .mjs/.d.mts pair to real .ts Same motivation as the prior commit, extended to scripts/: a hand-maintained .d.mts alongside a .mjs is exactly the duplicated-declaration problem the TypeScript migration was supposed to eliminate, and it can silently drift from the real implementation with no compiler ever checking it. Converts all 39 remaining scripts/*.mjs files that had one -- the mcp-release/ orb-release family, every docs/settings/schema drift checker, and the rest of the standalone generators -- to real .ts, inferring each function's types from its actual behavior rather than trusting the old declaration, per the pattern proven out on the miner/mcp packages. Nothing in scripts/ was ever part of Codecov's coverage surface, so this adds no coverage obligation; it's a straight type-safety and drift-elimination win. Real drift the old .d.mts files had already accumulated, found while converting: - check-schema-drift.ts read a table's name via SQLiteTable.Symbol.Name, an @internal drizzle-orm symbol never in its public type exports (which is exactly why the old hand-written declaration typed it without complaint) -- switched to the public getTableName(). - ci-duration-report.ts's WorkflowRun type was missing the `event` field the code actually filters on. - orb-release-core.ts's IMAGE_RELEVANT_PREFIXES still named two sibling scripts by their old .mjs filenames, now renamed here too -- a commit touching either file under its real name would have silently stopped counting as image-relevant. Every consumer updated to match: .js-suffixed import specifiers (Vite/ esbuild/Wrangler already resolve these to the sibling .ts, same as the prior commit), test imports, and every real invocation site. A script whose own file stays .mjs but now imports something converted here (e.g. check-mcp-package.mjs importing forbidden-content.ts) needs tsx instead of plain node, since only tsx (not node --experimental-strip-types) resolves a same-directory .ts import transitively; a script with zero local imports uses --experimental-strip-types directly, cheaper than spawning tsx. Covers every affected npm script, the three release-watch GitHub workflows (which previously needed no npm install at all -- added ./.github/actions/setup-workspace to each), the Dockerfile, and deploy-selfhost-prebuilt.sh. That last category caught two live regressions already sitting on this branch from the prior commit, beyond the one this commit's own check-miner-deployment-docs.ts conversion fixes (that one's what's been failing this PR's own CI): packages/loopover-miner/scripts/ generate-env-reference.mjs (npm run miner:env-reference, part of test:ci) and the Dockerfile/deploy-selfhost-prebuilt.sh's validate-selfhost-sourcemap invocations were both silently broken the same way -- caught by grepping for every remaining literal .mjs reference to a converted filename repo-wide, not by any test, since the one existing test for the miner env-reference generator imports it through Vite (which already tolerates the mismatch) rather than spawning it as the real subprocess the npm script actually runs. * build(scripts): convert the last 14 standalone .mjs scripts to real .ts Closes out the scripts/ TypeScript migration this branch's base commit started: every remaining scripts/*.mjs file that had no paired .d.mts (so wasn't covered by the prior "convert every .mjs/.d.mts pair" commit) is now real .ts, type-checked against its actual behavior instead of running untyped. Covers the actionlint/build-selfhost/ check-migrations/compute-test-shards/rees-coverage/smoke-observability- {metrics,traces}/smoke-ui-browser/check-{mcp,miner}-package/ check-mcp-release-candidate/check-orb-{release,stable-release}-due/ check-changelog families. Every consumer updated to match: package.json scripts, GitHub workflows (orb-beta-release, orb-stable-release-pr, publish-miner, release-selfhost, selfhost, test-timing-refresh), the Dockerfile and .dockerignore, deploy-selfhost-prebuilt.sh, and comment-only references across src/queue/processors.ts, packages/loopover-engine's deny-hooks, the selfhost stub files, and the self-hosting docs pages. A script invoked directly as a subprocess (not a same-directory .ts import) keeps plain `node --experimental-strip-types`; one that imports another local .ts file (e.g. check-orb-release-due.ts importing orb-release-core.ts) needs tsx instead, since only tsx -- not node's native strip-types -- resolves a `.js`-suffixed specifier to a sibling .ts transitively. Real invocations verified for every converted script against this repo's own state (migrations, git tags, the packed npm tarballs, a live loopover.ai smoke check, actionlint/composite-action lint), not just typecheck -- including reproducing rees-coverage.ts's one pre-existing, already-tolerated (`|| true` in ci.yml) analyzer-metadata ENOENT quirk against the original .mjs to confirm it predates this conversion rather than being introduced by it. # Conflicts: # scripts/check-changelog.ts
1 parent 0ff0b8f commit 17ae3e6

67 files changed

Lines changed: 270 additions & 235 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ dist-ssr
1212
coverage
1313
.git
1414
.claude
15-
# The self-host bundle's only entry point is src/server.ts (scripts/build-selfhost.mjs) and `npm ci`
15+
# The self-host bundle's only entry point is src/server.ts (scripts/build-selfhost.ts) and `npm ci`
1616
# only ever sees the root package*.json (copied before the rest of the tree) — the loopover-ui
1717
# workspace app and the test suite are never read during the image build, so keep both out of the
1818
# build context entirely (measured: ~11MB of this repo's ~22MB tracked-file footprint).
@@ -26,7 +26,7 @@ auth.json
2626
**/auth.json
2727
# The review-enrichment service (REES) is a separate Railway service with its own Dockerfile — keep it out of
2828
# the engine image. EXCEPT analyzer-metadata.json: the main engine's own code (src/review/enrichment-analyzers-
29-
# taxonomy.ts) imports it directly, so excluding it wholesale breaks `scripts/build-selfhost.mjs`'s esbuild
29+
# taxonomy.ts) imports it directly, so excluding it wholesale breaks `scripts/build-selfhost.ts`'s esbuild
3030
# bundle (module resolution failure at build time, not a runtime gap -- caught by the "build + boot smoke test"
3131
# workflow, which is path-gated and doesn't run on every PR).
3232
review-enrichment

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,14 @@ jobs:
160160
# and the pack check only inspects the tarball. Root src/ cannot
161161
# affect it, so it is intentionally NOT a trigger here.
162162
- 'packages/loopover-mcp/**'
163-
- 'scripts/check-mcp-package.mjs'
163+
- 'scripts/check-mcp-package.ts'
164164
- 'package-lock.json'
165165
engine:
166166
- 'packages/loopover-engine/**'
167167
- 'package-lock.json'
168168
miner:
169169
- 'packages/loopover-miner/**'
170-
- 'scripts/check-miner-package.mjs'
170+
- 'scripts/check-miner-package.ts'
171171
- 'package-lock.json'
172172
# No dedicated build/pack script (unlike mcp/engine/miner above) -- discovery-index is a normal
173173
# vitest-covered workspace package (packages/discovery-index/src/**/*.ts is in vitest.config.ts's
@@ -217,7 +217,7 @@ jobs:
217217
- 'control-plane/**'
218218
- '.github/workflows/ci.yml'
219219
# Both miner-package test files are self-contained w.r.t. root src/**, the same trust boundary as
220-
# mcpCliHarness above: check-miner-package.test.ts only spawns scripts/check-miner-package.mjs as a
220+
# mcpCliHarness above: check-miner-package.test.ts only spawns scripts/check-miner-package.ts as a
221221
# real subprocess (node:child_process + vitest, nothing else), and miner-calibration-types.test.ts
222222
# only imports the scaffolded packages/loopover-miner/lib/calibration.ts -- neither ever loads
223223
# root src/ in-process. Mirrors mcpCliHarness's filter shape for the same reason (tooling/config
@@ -927,7 +927,7 @@ jobs:
927927
# across every push-to-main run). key never actually matches (no save ever uses this literal
928928
# string); it exists only so the step always falls through to the restore-keys prefix match,
929929
# picking whatever the most recent refresh wrote. A cache MISS here is always safe: see
930-
# scripts/compute-test-shards.mjs's fallback -- it splits evenly across shards when no timing
930+
# scripts/compute-test-shards.ts's fallback -- it splits evenly across shards when no timing
931931
# data is available for a file (or none at all), the same balance vitest's own --shard already
932932
# gives today, so this can only make shard balance better than today's baseline, never worse.
933933
- name: Restore test timing cache
@@ -1036,11 +1036,11 @@ jobs:
10361036
# shards, every run sampled. Deliberately not applied to the scoped-selection branch above:
10371037
# that file set isn't known until vitest resolves --changed itself, so a precomputed
10381038
# assignment can't cover it without duplicating vitest's own dependency-graph resolution here.
1039-
# compute-test-shards.mjs enforces its own hard invariant (the union of all 3 shards' files
1039+
# compute-test-shards.ts enforces its own hard invariant (the union of all 3 shards' files
10401040
# must exactly equal the discovered file set, no file missing or duplicated) and refuses to
10411041
# write output at all if that's ever violated, so a bug here fails this step loudly rather
10421042
# than silently dropping a test file from CI.
1043-
node scripts/compute-test-shards.mjs --shards=3 --timing=test-timing.json --output=shard-assignment.json
1043+
node --experimental-strip-types scripts/compute-test-shards.ts --shards=3 --timing=test-timing.json --output=shard-assignment.json
10441044
mapfile -t SHARD_FILES < <(node -e "console.log(JSON.parse(require('fs').readFileSync('shard-assignment.json','utf8'))['${{ matrix.shard }}'].join('\n'))")
10451045
npm run test:coverage -- --maxWorkers=4 "${SHARD_FILES[@]}" --reporter=default --reporter=blob --reporter=junit --outputFile.blob=blob-report/report-${{ matrix.shard }}.blob --outputFile.junit=reports/junit/vitest.xml "${EXCLUDE_ARGS[@]}"
10461046
fi

.github/workflows/orb-beta-release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Automated ORB (self-host container image, ghcr.io/jsonbored/loopover-selfhost) beta channel.
22
# Daily (or on demand via workflow_dispatch), checks whether any image-relevant commit has landed
3-
# since the last orb-v tag (scripts/check-orb-release-due.mjs / scripts/orb-release-core.ts) and,
3+
# since the last orb-v tag (scripts/check-orb-release-due.ts / scripts/orb-release-core.ts) and,
44
# if so, cuts the next `orb-vX.Y.Z-beta.N` tag and dispatches release-selfhost.yml to build + publish
55
# it -- fully unattended: that workflow's `environment:` routes an actual beta version to
66
# `release-beta` (no required reviewers), while a stable/rc version still requires the human-gated
@@ -40,11 +40,11 @@ jobs:
4040

4141
- name: Check whether an ORB beta is due
4242
id: report
43-
# check-orb-release-due.mjs imports orb-release-core.ts directly, so it needs tsx (not plain node) to
44-
# resolve that local .ts import.
43+
# check-orb-release-due.ts imports orb-release-core.ts directly via a `.js` specifier, so it needs tsx
44+
# (not plain node) to resolve that local .ts import.
4545
run: |
4646
set -euo pipefail
47-
npx tsx scripts/check-orb-release-due.mjs --json --output orb-release-due.json
47+
npx tsx scripts/check-orb-release-due.ts --json --output orb-release-due.json
4848
node <<'NODE'
4949
const fs = require("node:fs");
5050
const report = JSON.parse(fs.readFileSync("orb-release-due.json", "utf8"));

.github/workflows/orb-stable-release-pr.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# doesn't fit release-please's directory-component model the way packages/loopover-mcp and
55
# packages/loopover-engine do (see mcp-release-please.yml). Same UX contract as those, hand-rolled: on the
66
# same schedule (or on demand), (re)compute the next stable version from conventional commits since the last
7-
# STABLE orb-v tag (scripts/check-orb-stable-release-due.mjs / orb-release-core.ts's buildOrbStableReleaseReport)
7+
# STABLE orb-v tag (scripts/check-orb-stable-release-due.ts / orb-release-core.ts's buildOrbStableReleaseReport)
88
# and keep a standing `release-orb-stable` branch + PR in sync with that proposal. Nothing ships until a
99
# maintainer reviews and merges it -- see orb-stable-release-tag.yml for what happens then. Never touches the
1010
# daily fully-unattended beta channel (orb-beta-release.yml).
@@ -39,11 +39,11 @@ jobs:
3939

4040
- name: Check whether a stable ORB release is due
4141
id: report
42-
# check-orb-stable-release-due.mjs imports orb-release-core.ts directly, so it needs tsx (not plain
43-
# node) to resolve that local .ts import.
42+
# check-orb-stable-release-due.ts imports orb-release-core.ts directly via a `.js` specifier, so it
43+
# needs tsx (not plain node) to resolve that local .ts import.
4444
run: |
4545
set -euo pipefail
46-
npx tsx scripts/check-orb-stable-release-due.mjs --json --output orb-stable-release-due.json
46+
npx tsx scripts/check-orb-stable-release-due.ts --json --output orb-stable-release-due.json
4747
node <<'NODE'
4848
const fs = require("node:fs");
4949
const report = JSON.parse(fs.readFileSync("orb-stable-release-due.json", "utf8"));

.github/workflows/publish-miner.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797
run: npm run build --workspace @loopover/miner
9898

9999
# Reuses the exact allowlist/required-files/forbidden-content check test:ci already runs on
100-
# every PR (scripts/check-miner-package.mjs) -- a dry-run pack, so it doesn't produce the real
100+
# every PR (scripts/check-miner-package.ts) -- a dry-run pack, so it doesn't produce the real
101101
# tarball this job packs+uploads below.
102102
- name: Validate packed file list
103103
run: npm run test:miner-pack

.github/workflows/release-selfhost.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ jobs:
135135
run: npm run build --workspace @loopover/engine
136136

137137
- name: Build self-host bundle for release
138-
run: node scripts/build-selfhost.mjs --all
138+
run: node --experimental-strip-types scripts/build-selfhost.ts --all
139139

140140
- name: Validate release source map
141141
run: node --experimental-strip-types scripts/validate-selfhost-sourcemap.ts

.github/workflows/selfhost.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Self-host stack CI (#980/#982). Provides integration coverage the main CI can't:
22
# 1. Postgres integration test — needs a real PG service container
3-
# 2. Self-host bundle build validation (build-selfhost.mjs)
3+
# 2. Self-host bundle build validation (build-selfhost.ts)
44
# 3. Docker image build + container smoke test (/health, /ready, /metrics)
55
# Unit tests and typecheck are NOT duplicated here — the main CI validate job covers them.
66
name: self-host
@@ -11,7 +11,7 @@ on:
1111
paths:
1212
- "src/selfhost/**"
1313
- "src/server.ts"
14-
- "scripts/build-selfhost.mjs"
14+
- "scripts/build-selfhost.ts"
1515
- "scripts/validate-selfhost-sourcemap.ts"
1616
- "Dockerfile"
1717
- "docker-compose.yml"
@@ -27,7 +27,7 @@ on:
2727
paths:
2828
- "src/selfhost/**"
2929
- "src/server.ts"
30-
- "scripts/build-selfhost.mjs"
30+
- "scripts/build-selfhost.ts"
3131
- "scripts/validate-selfhost-sourcemap.ts"
3232
- "Dockerfile"
3333
- "docker-compose.yml"
@@ -97,7 +97,7 @@ jobs:
9797
run: PG_TEST_URL=postgres://postgres:devpw@localhost:5432/loopover npx vitest run test/integration/selfhost-pg.test.ts
9898

9999
- name: Build the self-host bundle
100-
run: node scripts/build-selfhost.mjs
100+
run: node --experimental-strip-types scripts/build-selfhost.ts
101101

102102
- name: Validate self-host source map
103103
run: node scripts/validate-selfhost-sourcemap.ts

.github/workflows/test-timing-refresh.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Test timing refresh
22

33
# Pulls per-test-file historical duration data from Codecov's Test Analytics API (see
44
# scripts/fetch-test-timing.ts) and caches it for validate-tests' duration-aware shard bin-packer
5-
# (scripts/compute-test-shards.mjs, ci.yml's "Test with coverage" step) to consume. Runs on a schedule
5+
# (scripts/compute-test-shards.ts, ci.yml's "Test with coverage" step) to consume. Runs on a schedule
66
# rather than per-PR: Codecov doesn't publish a numeric rate limit for this read endpoint, and this
77
# repo's PR volume (hundreds/day) makes "query fresh on every PR" a real risk of hitting one, for data
88
# that doesn't meaningfully change run-to-run anyway.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RUN npm ci --ignore-scripts
2222
RUN npm --workspace @loopover/engine run build
2323
# --all: bundle every dependency into one self-contained dist/server.mjs, so the runtime image needs no
2424
# node_modules (≈10× smaller). The bundle has zero `cloudflare:*` imports (stubbed at build), so no loader.
25-
RUN node scripts/build-selfhost.mjs --all
25+
RUN node --experimental-strip-types scripts/build-selfhost.ts --all
2626
RUN node --experimental-strip-types scripts/validate-selfhost-sourcemap.ts
2727

2828
# --- runtime base: slim, non-root -----------------------------------------------------------------------
@@ -69,7 +69,7 @@ ARG INSTALL_VISUAL_REVIEW=false
6969
COPY package*.json ./
7070
RUN if [ "$INSTALL_VISUAL_REVIEW" = "true" ]; then npm install puppeteer-core@22.13.1 --ignore-scripts; fi
7171
# sharp (#4370): esbuild marks it `external` in the --all bundle (a native per-platform binary can't be
72-
# bundled into dist/server.mjs, see scripts/build-selfhost.mjs), so it must be installed separately here,
72+
# bundled into dist/server.mjs, see scripts/build-selfhost.ts), so it must be installed separately here,
7373
# same reason as puppeteer-core above -- but unconditional (not behind an opt-in build-arg): it's a core
7474
# dependency of the vision-image-downscale path, not an optional external-sidecar feature. --ignore-scripts
7575
# is safe here: sharp's own platform binary ships as an npm `optionalDependencies` entry

apps/loopover-ui/content/docs/self-hosting-operations.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ scripts above pointed at an older target:
11361136
`deploy-selfhost-prebuilt.sh`.
11371137

11381138
<Callout variant="warn" title="Migrations are forward-only">
1139-
This repo has no down-migration convention — `scripts/check-migrations.mjs` only enforces a
1139+
This repo has no down-migration convention — `scripts/check-migrations.ts` only enforces a
11401140
contiguous, non-colliding numbering, not a reverse path. If a migration has already run forward
11411141
against the live database, rolling back the app code is **not safe in general**: older code can
11421142
break against a newer schema (a dropped/renamed column, a NOT NULL column it never writes, a

0 commit comments

Comments
 (0)