@dotobokuri/fleet-cli 1.10.1 → 1.11.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/dist/index.js CHANGED
@@ -81620,6 +81620,170 @@ function resolvedWorkspaceSnapshot(resolveWorkspace, tenantId) {
81620
81620
  tenantLabel: tenantId
81621
81621
  };
81622
81622
  }
81623
+ var RELEASE_NOTE_HEADINGS = ["Added", "Changed", "Fixed", "Removed", "Breaking Changes"];
81624
+ var VERSION_HEADER_PATTERN = /^## \[([^\]]+)\](?: - ([0-9]{4}-[0-9]{2}-[0-9]{2}))?$/;
81625
+ var SECTION_HEADER_PATTERN = /^### (Added|Changed|Fixed|Removed|Breaking Changes)$/;
81626
+ var BULLET_PATTERN = /^- (.+)$/;
81627
+ var PACKAGE_TAG_PATTERN = /^\[([^\]]+)\]/;
81628
+ function parseConsoleReleaseNotes(changelog) {
81629
+ const lines = changelog.split(/\r?\n/);
81630
+ const notes = [];
81631
+ for (let index = 0; index < lines.length; index += 1) {
81632
+ const match = VERSION_HEADER_PATTERN.exec(lines[index] ?? "");
81633
+ if (match === null) continue;
81634
+ const block = [];
81635
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
81636
+ if ((lines[cursor] ?? "").startsWith("## ")) break;
81637
+ block.push(lines[cursor] ?? "");
81638
+ }
81639
+ const sections = collectSections(block);
81640
+ if (sections.length === 0) continue;
81641
+ notes.push({ version: match[1] ?? "", date: match[2] ?? null, sections });
81642
+ }
81643
+ return notes;
81644
+ }
81645
+ function collectSections(lines) {
81646
+ const sections = [];
81647
+ let current = null;
81648
+ for (const line of lines) {
81649
+ const sectionMatch = SECTION_HEADER_PATTERN.exec(line);
81650
+ if (sectionMatch !== null) {
81651
+ current = { heading: sectionMatch[1], items: [] };
81652
+ sections.push(current);
81653
+ continue;
81654
+ }
81655
+ if (current === null) continue;
81656
+ if (line.startsWith("### ")) {
81657
+ current = null;
81658
+ continue;
81659
+ }
81660
+ const bulletMatch = BULLET_PATTERN.exec(line);
81661
+ if (bulletMatch !== null) current.items.push(parseReleaseNoteItem(bulletMatch[1] ?? ""));
81662
+ }
81663
+ return RELEASE_NOTE_HEADINGS.map((heading) => sections.find((section22) => section22.heading === heading)).filter((section22) => Boolean(section22 && section22.items.length > 0));
81664
+ }
81665
+ function parseReleaseNoteItem(rawText) {
81666
+ const packageTags = [];
81667
+ let text = rawText.trim();
81668
+ while (true) {
81669
+ const match = PACKAGE_TAG_PATTERN.exec(text);
81670
+ if (match === null) break;
81671
+ packageTags.push(match[1] ?? "");
81672
+ text = text.slice(match[0].length).trimStart();
81673
+ }
81674
+ return { packageTags, text: text.trim() };
81675
+ }
81676
+ var ConsoleReleaseNotesUnavailableError = class extends Error {
81677
+ reason;
81678
+ constructor(reason) {
81679
+ super("Console release notes are unavailable");
81680
+ this.name = "ConsoleReleaseNotesUnavailableError";
81681
+ this.reason = reason;
81682
+ }
81683
+ };
81684
+ var RAW_CHANGELOG_URL = "https://raw.githubusercontent.com/sbluemin/fleet-harness/main/CHANGELOG.md";
81685
+ var SOURCE_REF = "main";
81686
+ var FETCH_TIMEOUT_MS = 3e3;
81687
+ var MAX_CHANGELOG_BYTES = 1024 * 1024;
81688
+ var SUCCESS_TTL_MS = 60 * 60 * 1e3;
81689
+ var NEGATIVE_TTL_MS = 30 * 1e3;
81690
+ function createConsoleReleaseNotesService(deps = {}) {
81691
+ const fetchImpl = deps.fetchImpl ?? fetch;
81692
+ const now = deps.now ?? (() => Date.now());
81693
+ const setTimer = deps.setTimeout ?? setTimeout;
81694
+ const clearTimer = deps.clearTimeout ?? clearTimeout;
81695
+ let lastSuccess = null;
81696
+ let lastFailureAt = 0;
81697
+ let inFlight = null;
81698
+ let forceInFlight = null;
81699
+ async function refresh(options2 = {}) {
81700
+ const currentTime = now();
81701
+ if (options2.force) {
81702
+ if (forceInFlight) return forceInFlight;
81703
+ const pending2 = runFetch().finally(() => {
81704
+ if (forceInFlight === pending2) forceInFlight = null;
81705
+ });
81706
+ forceInFlight = pending2;
81707
+ return pending2;
81708
+ }
81709
+ if (lastSuccess && currentTime - lastSuccess.fetchedAt < SUCCESS_TTL_MS) return lastSuccess;
81710
+ if (lastFailureAt > 0 && currentTime - lastFailureAt < NEGATIVE_TTL_MS) {
81711
+ if (lastSuccess) return { ...lastSuccess, stale: true };
81712
+ throw new ConsoleReleaseNotesUnavailableError("negative_cache");
81713
+ }
81714
+ if (inFlight) return inFlight;
81715
+ const pending = runFetch().finally(() => {
81716
+ if (inFlight === pending) inFlight = null;
81717
+ });
81718
+ inFlight = pending;
81719
+ return pending;
81720
+ }
81721
+ function runFetch() {
81722
+ return fetchReleaseNotes().then((result) => {
81723
+ lastSuccess = result;
81724
+ lastFailureAt = 0;
81725
+ return result;
81726
+ }).catch((error512) => {
81727
+ lastFailureAt = now();
81728
+ if (lastSuccess) return { ...lastSuccess, stale: true };
81729
+ if (error512 instanceof ConsoleReleaseNotesUnavailableError) throw error512;
81730
+ throw new ConsoleReleaseNotesUnavailableError("cold_unavailable");
81731
+ });
81732
+ }
81733
+ async function fetchReleaseNotes() {
81734
+ const controller = new AbortController();
81735
+ const timer = setTimer(() => controller.abort(), FETCH_TIMEOUT_MS);
81736
+ timer.unref?.();
81737
+ try {
81738
+ const response = await fetchImpl(RAW_CHANGELOG_URL, {
81739
+ headers: { Accept: "text/plain; charset=utf-8" },
81740
+ signal: controller.signal
81741
+ });
81742
+ if (!response.ok) throw new ConsoleReleaseNotesUnavailableError("cold_unavailable");
81743
+ const text = await readTextWithByteLimit(response, controller);
81744
+ return {
81745
+ notes: parseConsoleReleaseNotes(text),
81746
+ sourceRef: SOURCE_REF,
81747
+ fetchedAt: now(),
81748
+ stale: false
81749
+ };
81750
+ } finally {
81751
+ clearTimer(timer);
81752
+ }
81753
+ }
81754
+ return { refresh };
81755
+ }
81756
+ async function readTextWithByteLimit(response, controller) {
81757
+ const body = response.body;
81758
+ if (body === null) return await response.text();
81759
+ const reader = body.getReader();
81760
+ const chunks = [];
81761
+ let totalBytes = 0;
81762
+ try {
81763
+ while (true) {
81764
+ const { done, value } = await reader.read();
81765
+ if (done) break;
81766
+ totalBytes += value.byteLength;
81767
+ if (totalBytes > MAX_CHANGELOG_BYTES) {
81768
+ controller.abort();
81769
+ throw new ConsoleReleaseNotesUnavailableError("cold_unavailable");
81770
+ }
81771
+ chunks.push(value);
81772
+ }
81773
+ return new TextDecoder().decode(joinChunks22(chunks, totalBytes));
81774
+ } finally {
81775
+ reader.releaseLock();
81776
+ }
81777
+ }
81778
+ function joinChunks22(chunks, totalBytes) {
81779
+ const joined = new Uint8Array(totalBytes);
81780
+ let offset = 0;
81781
+ for (const chunk of chunks) {
81782
+ joined.set(chunk, offset);
81783
+ offset += chunk.byteLength;
81784
+ }
81785
+ return joined;
81786
+ }
81623
81787
  var TENANT_EVENT_LIMIT = 1e3;
81624
81788
  var TENANT_FINALIZED_JOB_LIMIT = 100;
81625
81789
  var TENANT_JOB_LIMIT = 200;
@@ -83540,6 +83704,13 @@ var SERVER_API_CATALOG = [
83540
83704
  category: "Observer",
83541
83705
  gate: "loopback"
83542
83706
  },
83707
+ {
83708
+ method: "GET",
83709
+ path: "/observer/release-notes",
83710
+ summary: "Get the console release notes.",
83711
+ category: "Update",
83712
+ gate: "loopback"
83713
+ },
83543
83714
  {
83544
83715
  method: "GET",
83545
83716
  path: "/observer/tenants",
@@ -83691,6 +83862,7 @@ function createConsoleServer(deps = {}) {
83691
83862
  registerDefaultCarriers2(carrierRegistry);
83692
83863
  const lock = createConsoleLock({ hostname: () => host });
83693
83864
  const observability = createConsoleObservabilityStore();
83865
+ const releaseNotes = deps.releaseNotes ?? createConsoleReleaseNotesService();
83694
83866
  const updateCheck = deps.updateCheck ?? createConsoleUpdateCheckService();
83695
83867
  const updateApply = deps.updateApply ?? createConsoleUpdateApplyService();
83696
83868
  const theaters = new TheaterRegistry();
@@ -83885,6 +84057,10 @@ function createConsoleServer(deps = {}) {
83885
84057
  handleObserverApiCatalog(req, res);
83886
84058
  return;
83887
84059
  }
84060
+ if (pathname === "/observer/release-notes") {
84061
+ runAsyncHandler(handleObserverReleaseNotes(req, res), res);
84062
+ return;
84063
+ }
83888
84064
  if (pathname === "/update/apply") {
83889
84065
  runAsyncHandler(handleUpdateApply(req, res), res);
83890
84066
  return;
@@ -84335,6 +84511,22 @@ function createConsoleServer(deps = {}) {
84335
84511
  function handleObserverApiCatalog(_req, res) {
84336
84512
  writeJson(res, 200, { version: version22, routes: buildApiCatalog() });
84337
84513
  }
84514
+ async function handleObserverReleaseNotes(req, res) {
84515
+ if (req.method !== "GET") {
84516
+ writeJson(res, 405, { error: "Method not allowed" });
84517
+ return;
84518
+ }
84519
+ try {
84520
+ const force = readUrl(req).searchParams.get("force") === "true";
84521
+ writeJson(res, 200, await releaseNotes.refresh({ force }));
84522
+ } catch (error512) {
84523
+ if (error512 instanceof ConsoleReleaseNotesUnavailableError) {
84524
+ writeJson(res, 503, { error: "release_notes_unavailable" });
84525
+ return;
84526
+ }
84527
+ throw error512;
84528
+ }
84529
+ }
84338
84530
  async function handleUpdateApply(req, res) {
84339
84531
  if (req.method !== "POST") {
84340
84532
  writeJson(res, 405, { error: "Method not allowed" });