Uh oh!
There was an error while loading. Please reload this page.
chore(ci): slow both dependabot channels from weekly to monthly #5366
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint | |
| # Until #2923 this workflow was `workflow_dispatch`-only, so ESLint had never | |
| # gated a PR. That mattered more than it looked: every `object-ui/*` rule that | |
| # `eslint.config.js` sets to `error` is a ratchet — added *specifically* so a | |
| # new violation fails CI, with its existing sites pre-cleaned first so the rule | |
| # lints clean on the day it lands. While nothing ran them, every one of them was | |
| # inert. | |
| # | |
| # `eslint.config.js` is the single list of those rules, and this comment | |
| # deliberately neither counts them nor names them: it used to hand-count, and | |
| # the count was stale by the time anyone read it (#3261). A hand-copied | |
| # enumeration drifts by construction, and a stale one still reads as | |
| # authoritative — `content/docs/guide/ci-cd-pipeline.md` avoids the number for | |
| # the same reason. `scripts/__tests__/lint-workflow.test.ts` holds that in | |
| # place: it fails if a count or a rule name reappears here, if the config stops | |
| # setting any `object-ui/*` rule to `error`, or if this workflow stops gating | |
| # pull requests. | |
| # | |
| # `--max-warnings` is deliberately not set: warnings repo-wide run into the | |
| # thousands, dominated by `no-explicit-any` plus React Compiler rules the config | |
| # downgrades on purpose — known historical debt, not a signal. This gate is | |
| # about errors. No exact figure here, for the reason above: this paragraph used | |
| # to carry a hand-maintained warning count and percentage that nothing | |
| # recomputed and nothing alarmed on as they aged (#3274), and | |
| # `scripts/check-lint-coverage.mjs` held a copy of the same number that would | |
| # have gone stale on its own clock. The order of magnitude is the whole | |
| # argument; the integer never was. | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths-ignore: | |
| - '**/*.md' | |
| - 'content/**' | |
| - 'docs/**' | |
| - '.changeset/**' | |
| # No `paths-ignore` here any more (objectui#3523, step 2) — it skipped the | |
| # whole workflow on a docs-only / changeset-only PR, so the `Lint` context was | |
| # absent exactly where a required check must still report. The path decision | |
| # moved into the job below. `push` above keeps its copy: nothing judges a push | |
| # to `main`. | |
| pull_request: | |
| branches: [main, develop] | |
| # ── Merge queue (objectui#3523) ──────────────────────────────────────── | |
| # The merge queue is ENFORCED on this repository by a ruleset — a direct push | |
| # to `main` returns 405 `Changes must be made through the merge queue` | |
| # (measured in #3243). Until this trigger landed, not one of the repository's | |
| # workflows subscribed `merge_group`: repo-wide `event=merge_group` runs stood | |
| # at total_count = 0, historically. A queue with nothing subscribed to it can | |
| # only have an EMPTY required-check set, so it rebuilt each PR on the current | |
| # `main` and let it through without validating anything. | |
| # | |
| # That is not a theoretical hole; it was cashed in on 2026-08-07. #3498 landed | |
| # a `scripts/` type gate, itself fully green, that left a TS2578 on `main`; | |
| # #3503, #3510 and #3516 then merged between 02:11Z and 02:15Z with `Type | |
| # Check` at conclusion=failure, and #3505 hot-fixed the result. objectstack | |
| # went through the same frames (objectstack#6067 -> #5615). | |
| # | |
| # `types:` is spelled out although `checks_requested` is the ONLY activity | |
| # type GitHub defines for `merge_group` today — the two spellings are | |
| # equivalent right now (objectstack's `ci.yml` and `lint.yml` use the bare | |
| # `merge_group:` form and produce queue builds normally, 3552 of them). Naming | |
| # the type means a second activity type added later cannot silently start | |
| # queue builds this workflow was never written for. | |
| # | |
| # `concurrency` below needs no merge-queue special case, and that was checked | |
| # rather than assumed: on `merge_group` the `github.event.pull_request` half of | |
| # the group expression is null, so the group falls back to `github.ref`, which | |
| # on a queue build is the queue's own generation — measured on objectstack, | |
| # `gh-readonly-queue/main/pr-6594-251e888ac9ace8226f3a8450951e5b40a0a84c2c`. | |
| # It can collide with neither a pull-request group (a bare PR number) nor a | |
| # push group (`refs/heads/main`), so a queue build and the PR build it came | |
| # from never cancel each other. | |
| merge_group: | |
| types: [checks_requested] | |
| workflow_dispatch: | |
| concurrency: | |
| group: lint-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: true | |
| # `fetch-depth: 0` for the gate step below (objectui#3523): it diffs | |
| # against the merge base, which a depth-1 clone cannot resolve. | |
| fetch-depth: 0 | |
| # ── Always report; run only when it matters (objectui#3523) ────────── | |
| # `on.pull_request.paths-ignore` used to skip this whole workflow on a | |
| # docs-only or changeset-only pull request, so the `Lint` context was | |
| # simply absent there — and a required check that never reports leaves the | |
| # PR pending forever (in the merge queue, until the ruleset's 60-minute | |
| # timeout fails it). The filter moved from the trigger into the job: the | |
| # job always runs and always reports, the paths decide only whether the | |
| # expensive steps execute. `ci.yml`'s `docs` job is the in-repo precedent | |
| # for the shape, and its `type-check` job carries the long version of this | |
| # note. The list below IS the `paths-ignore` it replaced; the `push` | |
| # trigger keeps its copy, because nothing judges a push to `main`. | |
| # | |
| # Fails OPEN: if the diff cannot be computed the job runs everything, | |
| # rather than reporting green having linted nothing (objectstack#4928). | |
| - name: Decide whether this change needs a full run | |
| id: relevant | |
| run: | | |
| if [ "${{ github.event_name }}" != 'pull_request' ]; then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo 'Not a pull request: push is filtered at the trigger, and a merge_group build is the last validation before main. Running everything.' | |
| exit 0 | |
| fi | |
| if ! CHANGED=$(git diff --name-only \ | |
| '${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}' -- \ | |
| . \ | |
| ':(exclude,glob)**/*.md' \ | |
| ':(exclude,glob)content/**' \ | |
| ':(exclude,glob)docs/**' \ | |
| ':(exclude,glob).changeset/**'); then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo 'Could not diff against the merge base. Running everything rather than skipping silently.' | |
| exit 0 | |
| fi | |
| if [ -n "$CHANGED" ]; then | |
| echo 'should_run=true' >> "$GITHUB_OUTPUT" | |
| echo "$CHANGED" | |
| else | |
| echo 'should_run=false' >> "$GITHUB_OUTPUT" | |
| echo 'Only ignored paths changed. Skipping the steps below; this check still reports.' | |
| fi | |
| - name: Enable Corepack | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm --version | |
| - name: Setup Node.js | |
| if: steps.relevant.outputs.should_run == 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22.x' | |
| cache: 'pnpm' | |
| # Every package must run ESLint or be declared a known gap. turbo skips | |
| # scriptless packages silently, so without this a package reads as clean | |
| # because nothing linted it. Runs before install: only reads package.json. | |
| - name: Verify lint coverage | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: node scripts/check-lint-coverage.mjs | |
| # ── One entry guard, one predicate (objectui#6092) ─────────────────── | |
| # A `scripts/**` CLI has to answer "did node run me, or did something | |
| # import me?" before it does anything, and the hand-typed answers in this | |
| # tree had drifted into NINE spellings across 28 `.mjs` files. Every one | |
| # of them fails the same way: node resolves symlinks for the module graph | |
| # but leaves `process.argv[1]` as the caller typed it, so a script reached | |
| # through a symlink compares two different paths, answers false, and does | |
| # NOTHING — exit 0, no output. The wrappers that spawn these tools hold | |
| # `result.status` only, so an inert child is a green gate. Measured on a | |
| # real blocking gate in this repository (objectui#6078): | |
| # `check-skills-paths.mjs` run directly on a deliberately broken tree | |
| # exits 1 with 696 bytes naming the dead path; run through a symlink | |
| # against the SAME tree it exits 0 with no output. | |
| # | |
| # `scripts/invoked-as.mjs` landed here in #5984 with a header stating that | |
| # a gate enforced the single-spelling rule. It did not exist, and the | |
| # sweep it described had not happened either (objectui#6078). This is that | |
| # gate. It lands BEFORE the conversion on purpose: the worklist grew while | |
| # the card sat open, so a sweep with nothing under it can be undone | |
| # silently by the next pull request. The 29 existing guards are baselined, | |
| # SHRINK-ONLY, and the script's own header says why that is safe. | |
| # | |
| # Runs before install, next to `check-lint-coverage.mjs` above and for the | |
| # same reason: it reads sources with no dependency beyond node builtins | |
| # and two local modules, so nothing it needs is in `node_modules`. Placed | |
| # before `pnpm install` it also cannot be made green by an install | |
| # failure. `--self-test` runs FIRST and is the half that stops the gate | |
| # rotting into decoration — it drives the scanner over fixture sources, | |
| # including the nine spellings measured in this tree, and pins the | |
| # baseline in every direction it can move. | |
| # | |
| # Invoked as `node` rather than through the `pnpm check:entry-guard` alias | |
| # for the pre-install placement, matching `check-lint-coverage.mjs` above | |
| # and `check-cross-repo-closer-outcome.mjs` below. The alias exists in | |
| # `package.json` for local use and `scripts/__tests__/entry-guard-wiring.test.ts` | |
| # holds the two spellings to the same script. | |
| - name: Verify every scripts/ entry guard goes through one predicate | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-entry-guard.mjs --self-test | |
| node scripts/check-entry-guard.mjs | |
| # ── The ported objectstack tooling is a PINNED copy (objectui#6642) ─── | |
| # `scripts/pm/check-half-states.mjs` came from objectstack (objectui#5791) | |
| # under a workflow header calling it a verbatim copy and enumerating the | |
| # three things a re-sync must not clobber. Nothing checked either half. | |
| # Measured 2026-08-28, before this step existed: the ported copy stood at | |
| # 9,340 lines against upstream's 12,948 — a 4,637-line `diff` — and its | |
| # own `--self-test` ran 1,116 cases where upstream's ran 1,574. So 458 | |
| # predicate cases had landed upstream and never arrived here, while the | |
| # patrol went on rendering a confident report with the corresponding rows | |
| # simply missing. | |
| # | |
| # The direction of harm is this repository's least visible one: a drifted | |
| # copy does not fail, it REPORTS. It became load-bearing once already — | |
| # objectui#6641 had to hand-port H22's closure floor into this copy, | |
| # because wiring the new environment variable in the workflow alone would | |
| # have set a variable this copy did not read. | |
| # | |
| # The gate reverses the DECLARED divergences out of each ported file and | |
| # requires the reconstruction to hash to the pinned upstream digest, so | |
| # drift beyond the declared set is byte-detectable in both directions — | |
| # an edit here, or upstream moving. ⛔ It fetches nothing: a gate that | |
| # reached api.github.com would be red on a network hiccup and green on a | |
| # cached 200, and this repo's whole reason for owning a patrol is that a | |
| # check which cannot read its input must never read as clean (#4690). | |
| # | |
| # Runs before install, next to the two gates above and for the same | |
| # reason: node builtins and one local module only, so an install failure | |
| # cannot take it down with it. `--self-test` runs FIRST — it drives the | |
| # real comparer over fixtures (parity holds, drift outside a region, | |
| # drift inside one, an ambiguous anchor, the pin-bump procedure, and | |
| # every malformed-pin shape), which is what stops a comparer that | |
| # recognises nothing from reading as a clean tree. | |
| - name: Verify the ported objectstack tooling still matches its pin | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-upstream-port-parity.mjs --self-test | |
| node scripts/check-upstream-port-parity.mjs | |
| - name: Turbo Cache | |
| if: steps.relevant.outputs.should_run == 'true' | |
| uses: actions/cache@v6 | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}- | |
| - name: Install dependencies | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Run linter | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm lint | |
| # ── The cross-repo closer's outcome contract (#5261) ────────────────── | |
| # `cross-repo-issue-closer.yml` carries ~250 lines of inline | |
| # github-script, and until this step existed it was code nobody had ever | |
| # seen run: it fires only on a merge, its conclusion is required by | |
| # nothing, and every one of its runs so far has been green. That last part | |
| # is the problem rather than the reassurance — measured over every merged | |
| # pull request in this repository, its close loop has had a live target | |
| # roughly one and a half times a day since it landed, took the same | |
| # `already closed -- skipping` exit every time, and left no backlink on | |
| # any of them. Greenly. The header of the workflow carries the figures. | |
| # | |
| # This step is the exercise: the shipped script is extracted from the YAML | |
| # with a real parser (never retyped) and run under doubles the way | |
| # actions/github-script runs it, as one AsyncFunction body. The scenarios | |
| # pin the target parse, the target KIND, and the outcome of every exit — | |
| # which of setFailed / warning / job summary fires, and which API calls | |
| # were made. | |
| # | |
| # Assertion 0 is the compile, and it is not theoretical: the framework's | |
| # copy of this workflow was taken down twice in one day by a `SyntaxError` | |
| # in the inline block, i.e. a script that never ran at all, on a | |
| # post-merge workflow whose red nothing else in CI can see. | |
| # | |
| # `--self-test` runs FIRST and is the half that stops the battery rotting | |
| # into decoration: it mutates the shipped script — downgrade the verdict | |
| # to a warning, break out of the loop instead of isolating, drop the | |
| # same-repo skip, collapse the already-closed branch, strip the backlink | |
| # marker, drop the pull-request guard, and the rest — and requires the | |
| # battery to go RED for each, naming the scenario that catches it. Neither | |
| # the mutations nor the scenarios are counted here: a hand-copied | |
| # enumeration drifts by construction, which is the lesson this workflow's | |
| # own header records. A mutation whose anchor no longer exists is a | |
| # failure too, so rewriting the workflow cannot leave them silently | |
| # matching nothing. | |
| # | |
| # Invoked as `node` rather than through a `pnpm check:*` alias, matching | |
| # `check-lint-coverage.mjs` above. No network, no build; well under a | |
| # second. | |
| - name: Cross-repo closer outcome contract | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: | | |
| node scripts/check-cross-repo-closer-outcome.mjs --self-test | |
| node scripts/check-cross-repo-closer-outcome.mjs | |
| # ── The product's own check command, on the product's own tree ──────── | |
| # `objectui check` is what a consumer runs against their schema tree, and | |
| # the root `check` script points that same command at this repository. | |
| # Nothing ran it. It exited 1 with 64 errors on `main` — and had done | |
| # since the first `tsconfig.json` grew a comment — until someone ran it by | |
| # hand while measuring something unrelated (objectui#5237, fixed by | |
| # #5245). A shipped command sitting red on its own repository is the | |
| # dogfood invariant failing, and the reason it could sit there is that no | |
| # gate ever asked. This is that gate (objectui#5246). | |
| # | |
| # The build step below is not a convenience. The root script is | |
| # `node packages/cli/dist/cli.js check`, and this job installs without | |
| # building, so without it the step dies on a missing file. That failure | |
| # mode is worse than no gate at all: it is red for a reason that has | |
| # nothing to do with the tree being checked, and the obvious repair from | |
| # outside is to delete the step — leaving the hole exactly as it was, now | |
| # with a commit saying it was considered. | |
| # | |
| # `...` is pnpm's dependency closure: the CLI plus every workspace | |
| # package it depends on, in topological order, derived from the graph | |
| # rather than listed here. The closure is the requirement and not just | |
| # tidiness — `dist/cli.js` imports `@object-ui/types`' built output at | |
| # startup, so building the CLI package alone produces a binary that | |
| # cannot load (objectui#5237 records the same import as the reproduce | |
| # recipe's first step). | |
| # | |
| # Deliberately NOT `turbo run build`, although the Turbo cache above is | |
| # restored by this point and would usually make it free. A turbo cache | |
| # HIT restores a task's recorded outputs, and an entry recorded with an | |
| # empty output set replays as "cache hit, replaying logs" plus FULL | |
| # TURBO while writing no `dist/` at all — measured on this repo, where | |
| # the CLI then died with ERR_MODULE_NOT_FOUND on the types import above. | |
| # A blocking gate must not be able to fail for a reason that lives in a | |
| # cache rather than in the tree it is judging, which is the same argument | |
| # as the paragraph above one level down. Building through pnpm has no | |
| # cache layer to replay, and it costs well under a minute. | |
| - name: Build the CLI the self-check runs | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm --filter '@object-ui/cli...' build | |
| # Errors only, which is the command's existing behaviour rather than a | |
| # setting chosen here: a parse failure increments the error count, a | |
| # non-zero count is the only thing that exits 1, and the unknown-schema- | |
| # type arm prints and moves on. So this step blocks on the same arm | |
| # `pnpm lint` above does, and for the same reason — errors are a signal, | |
| # the warning stream is known debt. Nothing here promotes those warnings | |
| # to failures, and no output-suppressing flag hides them either: they stay | |
| # visible in the log and non-blocking. That arm belongs to objectui#5127, | |
| # which is open; whatever it settles changes what this step PRINTS, never | |
| # what it fails on. No count is quoted, for the reason the `--max-warnings` | |
| # paragraph in the header gives. | |
| - name: Verify the CLI's own check command passes on this repository | |
| if: steps.relevant.outputs.should_run == 'true' | |
| run: pnpm check |