clearotron 0.3.2-beta.1 → 0.3.2-beta.2
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/.env.example +19 -0
- package/build-info.json +2 -2
- package/docs/architecture/04-configuration-reference.md +1 -0
- package/docs/architecture/05-config-governance.md +9 -1
- package/driver/CHANGELOG.md +6 -0
- package/driver/driver.config.mjs +19 -3
- package/driver/package.json +1 -1
- package/driver/suite-census.json +8 -2
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/package.json +1 -1
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/core.js +29 -1
- package/scripts/release-pre-gate.mjs +111 -0
package/.env.example
CHANGED
|
@@ -480,6 +480,25 @@ CLEAROTRON_CUT_REF=
|
|
|
480
480
|
# effect: tuning
|
|
481
481
|
CLEAROTRON_RELEASE_WAIT_MS=
|
|
482
482
|
|
|
483
|
+
# A GitHub token carrying Actions read and write. The release workflow supplies it from a repository
|
|
484
|
+
# secret so that a cut does not wait for a person to approve the version pull request's parked CI run:
|
|
485
|
+
# that run is authored by the repository's own Actions bot, and GitHub parks bot-authored runs as
|
|
486
|
+
# `action_required`. The built-in token cannot approve a run — self-approval is blocked deliberately —
|
|
487
|
+
# which is why this is a second, separate credential.
|
|
488
|
+
#
|
|
489
|
+
# NEVER SET THIS ON A DEPLOYMENT. No installed instance reads it and no clearance run reaches it. It is
|
|
490
|
+
# written down here because shipped code reads it, and every name shipped code reads has a row; the
|
|
491
|
+
# catalogue is the contract, not a list of things you are expected to set.
|
|
492
|
+
#
|
|
493
|
+
# UNSET IS THE ORDINARY CASE AND NOT A MISCONFIGURATION. The script names the absent token on stdout
|
|
494
|
+
# and exits successfully, and the version run then waits for a person exactly as it did before the
|
|
495
|
+
# token existed. Nothing fails and no run changes its conclusion.
|
|
496
|
+
#
|
|
497
|
+
# It carries no effect declaration because the closed vocabulary has no class that is true of it: the
|
|
498
|
+
# credential class claims a run refuses at preflight when the name is absent, and this one does not
|
|
499
|
+
# refuse at all. Read by scripts/release-approve-parked.mjs.
|
|
500
|
+
ACTIONS_APPROVE_TOKEN=
|
|
501
|
+
|
|
483
502
|
# The trickle floor: the fewest output tokens per second of ACTIVE time (elapsed minus tool wait) a
|
|
484
503
|
# model turn may produce before it is stopped as a stall. Unset uses 1. A stage streaming a token every
|
|
485
504
|
# few seconds holds off both other clocks — the stall clock resets on any byte, and the no-progress
|
package/build-info.json
CHANGED
|
@@ -296,6 +296,7 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
296
296
|
| `CLEAROTRON_UPDATER_STAMP` | `_updater-identity.json` beside the update script, in the directory the updater runs from | The full path of the file in which the updater that deploys this box records which copy of itself ran. The updater writes it and deploy health reads it under this one name, so a box that moves the stamp sets it once for both. On a box with no updater unit, setting it says an updater exists elsewhere and is to be judged. Effect class `deployment`. |
|
|
297
297
|
| `CLEAROTRON_CUT_REF` | `HEAD` | Which ref the cut decision reads the version from. **Read only by the release workflow, never set on a deployment.** The jobs that ask about `main` set it to `origin/main` explicitly, because their checkout is pinned to the run's own ref and `HEAD` there is that ref rather than the branch they are deciding about. A job that asks the wrong subject gets a confident wrong answer. |
|
|
298
298
|
| `CLEAROTRON_RELEASE_WAIT_MS` | 25 minutes | How long a requested cut waits for the version pull request to merge itself before giving up. **Read only by the release workflow.** A rehearsal sets it to `0` so the wiring is exercised without holding a runner. Giving up is a quiet success by design — the scheduled run underneath catches a cut whose wait expired — so this budget failing shows up as a slow job rather than a red one. |
|
|
299
|
+
| `ACTIONS_APPROVE_TOKEN` | unset | A GitHub token with Actions read and write, used to approve the version pull request's parked CI run so a cut does not wait for a person. **Read only by the release workflow, never set on a deployment.** The built-in token cannot approve a run — GitHub blocks self-approval — so this is a second, separate credential. Unset is the ordinary case and not an error: the script names the absent token and exits successfully, and the version run waits for a person as it did before. Actions write is broader than approval alone — it also dispatches workflows, cancels any run in the repository and deletes run logs. |
|
|
299
300
|
| `CLEAROTRON_AGENT_MCP_URL` | unset (⇒ `null`) | The API-key MCP door advertised to a signed-in client. Null until that door is deployed, and the UI keeps its honest empty state rather than inventing a URL. |
|
|
300
301
|
| `CLEAROTRON_AGENTS` | derived | Comma-separated agent ids for `scripts/purge-runs.mjs` to sweep. |
|
|
301
302
|
| `CLEAROTRON_E2E_DIR` | **none — the script refuses without it** | The config repo's `e2e/` directory. There is one suite and it is not in this repo (ruling 2026-08-07), so the comparison script names the variable rather than defaulting anywhere. |
|
|
@@ -495,7 +495,15 @@ origin; all portal config lives server-side in portal-service.
|
|
|
495
495
|
|
|
496
496
|
These are read by the release workflow and by nothing a deployment runs. They are listed here because a
|
|
497
497
|
name absent from this register is a name nobody can look up, not because an operator has any reason to set
|
|
498
|
-
one — and setting
|
|
498
|
+
one — and setting any of them on a box does nothing at all.
|
|
499
|
+
|
|
500
|
+
`ACTIONS_APPROVE_TOKEN` (unset) — a GitHub token with Actions read and write, used to approve the
|
|
501
|
+
version pull request's parked CI run so that a cut does not wait for someone to click. That run is
|
|
502
|
+
authored by the repository's own Actions bot and GitHub parks bot-authored runs; the built-in token
|
|
503
|
+
cannot release one, because self-approval is blocked deliberately. Unset is the ordinary case: the
|
|
504
|
+
script names the absent token and exits successfully, and the run waits for a person as before.
|
|
505
|
+
Actions write is wider than approving — it also dispatches workflows, cancels any run in the
|
|
506
|
+
repository and deletes run logs — so it is worth rotating on the same schedule as a deploy key.
|
|
499
507
|
|
|
500
508
|
`CLEAROTRON_CUT_REF` (default `HEAD`) — which ref the cut decision reads the version from. The jobs that
|
|
501
509
|
ask about `main` set it to `origin/main` explicitly, because their checkout is pinned to the run's own ref
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.3.2-beta.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8642064: Fixed: On one register the "Filings containing the name" figure counted only identical filings. A report could therefore show a field as far less crowded than it really is. That column now asks the register the question its label promises. Where a register cannot answer a given kind of search, the figure is reported as unavailable rather than filled in from a narrower one.
|
|
8
|
+
|
|
3
9
|
## 0.3.2-beta.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/driver/driver.config.mjs
CHANGED
|
@@ -1184,8 +1184,16 @@ export const PROVIDERS = {
|
|
|
1184
1184
|
// modes, not strategies, and the API rejects them in the strategies array. These wrappers
|
|
1185
1185
|
// hand-built the vendor shape and so were untouched by that fix — the translator is the one
|
|
1186
1186
|
// place that knows which mode rides which request shape, and every caller must go through it.
|
|
1187
|
-
|
|
1188
|
-
|
|
1187
|
+
// A MODE THIS PROVIDER CANNOT EXPRESS REFUSES THE CELL, rather than being answered by a
|
|
1188
|
+
// different predicate's number. The translator names it; this turns it into an honest unknown,
|
|
1189
|
+
// which the count kernel already treats as a disclosed gap. The alternative is what this
|
|
1190
|
+
// issue was: a narrower query answering under the wider query's label, invisibly.
|
|
1191
|
+
const signaParams = core.toSignaParams({ name, match_mode: matchMode || "exact", nice_classes: classes, regions });
|
|
1192
|
+
if (signaParams.unsupported_match_mode) {
|
|
1193
|
+
return { ok: false, total: null, unsupported: true,
|
|
1194
|
+
cause: `this register provider cannot express the "${signaParams.unsupported_match_mode}" match mode, so this count is UNKNOWN — it is not answered with a different predicate's number` };
|
|
1195
|
+
}
|
|
1196
|
+
const r = await core.doCountHits(process.env.SIGNA_API_KEY, base, signaParams,
|
|
1189
1197
|
{ kind: "count", agentId, sessionKey, sessionId: null, recordLog });
|
|
1190
1198
|
const text = typeof r?.text === "string" ? r.text : "";
|
|
1191
1199
|
if (text.startsWith("ERROR")) return { ok: false, cause: text.slice(0, 200) };
|
|
@@ -1207,8 +1215,16 @@ export const PROVIDERS = {
|
|
|
1207
1215
|
catch (e) { return { ok: false, records: null, reason: `plugin core unavailable: ${e.message}` }; }
|
|
1208
1216
|
const base = process.env.SIGNA_BASE_URL || core.DEFAULT_BASE;
|
|
1209
1217
|
try {
|
|
1218
|
+
// The listing takes the same refusal as the count above, and for the same reason one level on:
|
|
1219
|
+
// a narrower search here returns FEWER records under the wider query's name, so the listing
|
|
1220
|
+
// would under-report and read as complete.
|
|
1221
|
+
const signaListParams = core.toSignaParams({ name, match_mode: matchMode || "exact", nice_classes: classes, regions });
|
|
1222
|
+
if (signaListParams.unsupported_match_mode) {
|
|
1223
|
+
return { ok: false, records: null,
|
|
1224
|
+
reason: `this register provider cannot express the "${signaListParams.unsupported_match_mode}" match mode, so this listing was not taken — it is not answered with a narrower search` };
|
|
1225
|
+
}
|
|
1210
1226
|
const r = await core.doSearch(process.env.SIGNA_API_KEY, base,
|
|
1211
|
-
{ ...
|
|
1227
|
+
{ ...signaListParams, limit },
|
|
1212
1228
|
{ kind: "search", agentId, sessionKey, sessionId: null, recordLog });
|
|
1213
1229
|
const text = typeof r?.text === "string" ? r.text : "";
|
|
1214
1230
|
if (text.startsWith("ERROR")) return { ok: false, records: null, reason: text.slice(0, 200) };
|
package/driver/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "clearotron-driver",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.2-beta.
|
|
5
|
+
"version": "0.3.2-beta.2",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
|
|
8
8
|
"engines": {
|
package/driver/suite-census.json
CHANGED
|
@@ -3694,8 +3694,8 @@
|
|
|
3694
3694
|
"todos": 0
|
|
3695
3695
|
},
|
|
3696
3696
|
"release-pipeline.test.mjs": {
|
|
3697
|
-
"tests":
|
|
3698
|
-
"asserts":
|
|
3697
|
+
"tests": 99,
|
|
3698
|
+
"asserts": 376,
|
|
3699
3699
|
"skips": 0,
|
|
3700
3700
|
"todos": 0
|
|
3701
3701
|
},
|
|
@@ -6377,6 +6377,12 @@
|
|
|
6377
6377
|
"skips": 0,
|
|
6378
6378
|
"todos": 0
|
|
6379
6379
|
},
|
|
6380
|
+
"providers/signa/test/match-mode-is-not-approximated.test.mjs": {
|
|
6381
|
+
"tests": 4,
|
|
6382
|
+
"asserts": 11,
|
|
6383
|
+
"skips": 0,
|
|
6384
|
+
"todos": 0
|
|
6385
|
+
},
|
|
6380
6386
|
"providers/uspto-local/test/backfile-window.test.mjs": {
|
|
6381
6387
|
"tests": 6,
|
|
6382
6388
|
"asserts": 11,
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
3
|
+
"version": "0.3.2-beta.2",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
package/package.json
CHANGED
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.3.2-beta.
|
|
5
|
+
"version": "0.3.2-beta.2",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
|
|
8
8
|
"engines": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-oauth-mcp-bridge",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
3
|
+
"version": "0.3.2-beta.2",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "OAuth 2.1 MCP stdio bridge used by the engine's case-law gather stage (courtlistener / legaldatahunter).",
|
|
@@ -576,7 +576,35 @@ export function toSignaParams(p = {}) {
|
|
|
576
576
|
const mode = String(p.match_mode ?? "").trim();
|
|
577
577
|
if (mode === "exact" || mode === "phonetic" || mode === "prefix") out.strategies = [mode];
|
|
578
578
|
else if (mode === "starts_with" || mode === "ends_with" || mode === "contains") out.match = mode;
|
|
579
|
-
|
|
579
|
+
// ── `default` IS THE COUNT LANE'S WORD FOR THE SAME UNANCHORED QUERY THE PLAN LANE CALLS `{}` ────
|
|
580
|
+
//
|
|
581
|
+
// THE DEFECT. The count lane hands `match_mode: "default"` straight through — that is the word
|
|
582
|
+
// `COUNT_PREDICATES` gives the CONTAINING predicate, the one whose figure a client reads under
|
|
583
|
+
// "Filings containing the name". `default` matched none of the branches here, so neither request
|
|
584
|
+
// shape was selected, and `buildSearchRequest`'s else branch sent `strategies: ["exact"]`. The
|
|
585
|
+
// containing column therefore printed an exact count, on the axis that most signals how crowded a
|
|
586
|
+
// field is, and a client was told the field was less busy than it is in a number the report states
|
|
587
|
+
// as fact. Nothing failed and nothing was logged: a narrower query answers perfectly well, it just
|
|
588
|
+
// answers a different question.
|
|
589
|
+
//
|
|
590
|
+
// The branch below already does exactly this for the plan lane, which reaches here with NO mode at
|
|
591
|
+
// all. The two lanes mean the same thing by different words; they now take the same shape.
|
|
592
|
+
else if (mode === "default") {
|
|
593
|
+
if (typeof out.query === "string" && !out.query.includes("*")) out.match = "contains";
|
|
594
|
+
}
|
|
595
|
+
// ── AN UNRECOGNISED MODE REFUSES; IT DOES NOT QUIETLY BECOME A NARROWER SEARCH ──────────────────
|
|
596
|
+
//
|
|
597
|
+
// THE CLASS, and it has now bitten twice. This function's own header records `starts_with` falling
|
|
598
|
+
// through to a plain exact search — "a NARROWER query than the plan asked for, answering as though it
|
|
599
|
+
// were the one requested" — and `default` has just done the same on a client-facing figure. Both were
|
|
600
|
+
// fixed by adding the missing word. Adding words one defect at a time leaves the next unmapped mode
|
|
601
|
+
// to do it a third time, silently, in whichever direction happens to be wrong.
|
|
602
|
+
//
|
|
603
|
+
// So a mode this provider cannot express is now NAMED rather than approximated. The caller turns it
|
|
604
|
+
// into a refused cell, and a refused cell is a disclosed gap a reader can see; a wrong number is not
|
|
605
|
+
// visible at all. This can only fire where a number would otherwise have been silently wrong.
|
|
606
|
+
else if (mode) out.unsupported_match_mode = mode;
|
|
607
|
+
if (!mode && !p.match && !(Array.isArray(p.strategies) && p.strategies.length)) {
|
|
580
608
|
// ── THE `default` PREDICATE, WHICH JUST MADE EXECUTABLE ────────────────────────────────
|
|
581
609
|
// `planPredicateParams` returns {} for the plan's `default` predicate — no match_mode at all —
|
|
582
610
|
// and this branch is the only thing standing between that and `strategies: ["exact"]`. With
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
|
+
//
|
|
4
|
+
// release-pre-gate.mjs — let a stable cut reach the version script when every note has already been
|
|
5
|
+
// consumed by a pre-release.
|
|
6
|
+
//
|
|
7
|
+
// THE PROBLEM THIS EXISTS FOR, MEASURED. `changesets/action` decides whether to run `version-script` by
|
|
8
|
+
// counting the release notes it can see, and it does that ONCE, at the top of the run, before it touches
|
|
9
|
+
// the working tree. Its reader is `readChangesetState`:
|
|
10
|
+
//
|
|
11
|
+
// let preState = await readPreState(cwd);
|
|
12
|
+
// let changesets = await readChangesets(cwd);
|
|
13
|
+
// if (preState !== undefined && preState.mode === "pre") {
|
|
14
|
+
// return { preState, changesets: changesets.filter((c) => !c.id.startsWith("pre/")) };
|
|
15
|
+
// }
|
|
16
|
+
// return { preState: undefined, changesets };
|
|
17
|
+
//
|
|
18
|
+
// `readChangesets` DOES enumerate `.changeset/pre/`, giving each note an id beginning `pre/`. The filter
|
|
19
|
+
// that hides them applies only while the tree says `mode: "pre"`.
|
|
20
|
+
//
|
|
21
|
+
// A pre-release consumes a note by MOVING it into `.changeset/pre/`, where it waits so the eventual
|
|
22
|
+
// stable can list every change since the last stable. So the ordinary state after cutting a beta is: no
|
|
23
|
+
// notes at the top level, every note under `pre/`, and the flag still saying `pre`. In that state the
|
|
24
|
+
// action counts zero, skips the version script, emits no pull request number, and the stable cut refuses
|
|
25
|
+
// with "Nothing to cut" — a refusal about the wrong thing, because the tree holds a whole release.
|
|
26
|
+
//
|
|
27
|
+
// WHAT THIS DOES, AND WHY IT IS NOT A TRICK. It writes the channel the dispatch asked for into the file
|
|
28
|
+
// the action reads, before the action reads it. A stable cut IS a tree leaving pre-release mode, and the
|
|
29
|
+
// notes under `pre/` ARE the notes that stable will carry; saying so is a true statement about this cut,
|
|
30
|
+
// not a device to get past a check. `release-version.mjs` already states the same principle for the
|
|
31
|
+
// other reader: "the mode a cut needs is a fact about the channel asked for, not about what the last cut
|
|
32
|
+
// happened to do." There are two readers of that fact and they are reached separately.
|
|
33
|
+
//
|
|
34
|
+
// THE WRITE IS DISCARDED, ON PURPOSE, AND THAT IS THE POINT. The action's own first act inside
|
|
35
|
+
// `runVersion` is `git checkout changeset-release/main` followed by `git reset --hard <the run's
|
|
36
|
+
// commit>`, which puts `pre.json` back to `pre`. The version script then runs in that rebuilt tree, finds
|
|
37
|
+
// the tree still in pre mode, and performs the real transition itself with `changeset pre exit` — which
|
|
38
|
+
// is where the transition has to happen, and the reason it was moved there. So this changes what the
|
|
39
|
+
// action COUNTS and nothing about what the cut DOES. Nothing here versions anything, and a tree this
|
|
40
|
+
// touched still produces the same version it would have produced had the gate never been in the way.
|
|
41
|
+
//
|
|
42
|
+
// WHAT IT REFUSES TO DO. It is silent on a beta, on a tree that is not in pre mode, on a tree whose notes
|
|
43
|
+
// are at the top level where the action can already see them, and on a tree with no notes anywhere. That
|
|
44
|
+
// last one matters most: a cut with genuinely nothing to cut must still refuse, and it still does,
|
|
45
|
+
// because this writes nothing when `.changeset/pre/` is empty.
|
|
46
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
|
|
47
|
+
import { join, dirname } from "node:path";
|
|
48
|
+
import { fileURLToPath } from "node:url";
|
|
49
|
+
import { isEntrypoint } from "../shared/is-entrypoint.mjs";
|
|
50
|
+
|
|
51
|
+
const ROOT = join(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
52
|
+
|
|
53
|
+
/** A note is a `.md` file that is not the directory's own README. */
|
|
54
|
+
export function countNotes(dir) {
|
|
55
|
+
if (!existsSync(dir)) return 0;
|
|
56
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").length;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* PURE. Whether this cut has to restate its channel for the action's counter, and nothing else.
|
|
61
|
+
*
|
|
62
|
+
* `null` means leave the tree alone, and every `null` here is a case the action already reads correctly
|
|
63
|
+
* — including the two that must keep refusing. Only the one state the action cannot see returns a write.
|
|
64
|
+
*
|
|
65
|
+
* Kept separate from the file it writes so the decision can be driven against every member of the class
|
|
66
|
+
* rather than only against the tree that happens to be checked out.
|
|
67
|
+
*/
|
|
68
|
+
export function preGateDecision({ cut, mode, topLevelNotes, preNotes }) {
|
|
69
|
+
// A beta never restates anything. A beta with no pending notes MUST still refuse, and that refusal is
|
|
70
|
+
// this function returning null.
|
|
71
|
+
if (cut !== "stable") return null;
|
|
72
|
+
// Not in pre mode: the action's filter is not engaged, so whatever is there is already counted.
|
|
73
|
+
if (mode !== "pre") return null;
|
|
74
|
+
// The action can see these. Touching the flag here would change the pull request's title for a cut
|
|
75
|
+
// that never needed help.
|
|
76
|
+
if (topLevelNotes > 0) return null;
|
|
77
|
+
// Nothing anywhere. "Nothing to cut" is then the truth, and the refusal downstream is correct.
|
|
78
|
+
if (preNotes === 0) return null;
|
|
79
|
+
return { mode: "exit" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function main(root = ROOT, argv = process.argv.slice(2)) {
|
|
83
|
+
const cut = (argv.find((a) => a.startsWith("--cut=")) ?? "").slice("--cut=".length);
|
|
84
|
+
// `--root` exists so this can be driven against a built tree rather than only against the checkout it
|
|
85
|
+
// happens to live in. The workflow never passes it: there the script sits in the repository being cut,
|
|
86
|
+
// which is what ROOT already means.
|
|
87
|
+
const rootFlag = argv.find((a) => a.startsWith("--root="));
|
|
88
|
+
if (rootFlag) root = rootFlag.slice("--root=".length);
|
|
89
|
+
const preJson = join(root, ".changeset", "pre.json");
|
|
90
|
+
const flag = existsSync(preJson) ? JSON.parse(readFileSync(preJson, "utf8")) : null;
|
|
91
|
+
const mode = flag?.mode ?? "none";
|
|
92
|
+
const topLevelNotes = countNotes(join(root, ".changeset"));
|
|
93
|
+
const preNotes = countNotes(join(root, ".changeset", "pre"));
|
|
94
|
+
|
|
95
|
+
const decision = preGateDecision({ cut, mode, topLevelNotes, preNotes });
|
|
96
|
+
const state = `cut=${cut || "none"}, mode=${mode}, ${topLevelNotes} note(s) pending, ${preNotes} already consumed by the pre-release`;
|
|
97
|
+
if (!decision) {
|
|
98
|
+
// SAYS SO EITHER WAY. A step that is silent when it does nothing cannot be told from a step that did
|
|
99
|
+
// not run, and this one is skipped far more often than it acts.
|
|
100
|
+
console.log(`release-pre-gate: ${state} — leaving the flag alone.`);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
writeFileSync(preJson, `${JSON.stringify({ ...flag, ...decision }, null, 2)}\n`);
|
|
104
|
+
console.log(`release-pre-gate: ${state} — the notes this stable carries are all under .changeset/pre/,`
|
|
105
|
+
+ " where the action does not count them while the flag says pre. Writing mode=exit so it counts them."
|
|
106
|
+
+ " The version script performs the real transition after the action rebuilds the tree; this write is"
|
|
107
|
+
+ " discarded by that rebuild and changes no version.");
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isEntrypoint(import.meta.url)) process.exit(main());
|