arcane-cli 1.8.1 → 1.9.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
@@ -7735,6 +7735,33 @@ function renderShowReport(model, templateHtml) {
7735
7735
  });
7736
7736
  }
7737
7737
 
7738
+ // src/modules/show-report/theme.ts
7739
+ var REPORT_THEMES = ["auto", "light", "dark"];
7740
+ function radioTagPattern(id) {
7741
+ return new RegExp(`<input\\b[^>]*\\bid="arcane-report-theme-${id}"[^>]*?/?>`);
7742
+ }
7743
+ function withoutChecked(tag) {
7744
+ return tag.replace(/\s+checked(?:="[^"]*")?/, "");
7745
+ }
7746
+ function withChecked(tag) {
7747
+ return /\bchecked\b/.test(tag) ? tag : tag.replace(/(\/?>)$/, " checked$1");
7748
+ }
7749
+ function applyReportTheme(html, theme) {
7750
+ if (theme === "auto") return { html, applied: true };
7751
+ if (!radioTagPattern(theme).test(html)) return { html, applied: false };
7752
+ let result = html;
7753
+ for (const id of REPORT_THEMES) {
7754
+ const match = radioTagPattern(id).exec(result);
7755
+ if (!match) continue;
7756
+ const original = match[0];
7757
+ const replaced = id === theme ? withChecked(original) : withoutChecked(original);
7758
+ if (replaced !== original) {
7759
+ result = result.slice(0, match.index) + replaced + result.slice(match.index + original.length);
7760
+ }
7761
+ }
7762
+ return { html: result, applied: true };
7763
+ }
7764
+
7738
7765
  // src/modules/show-report/generate.ts
7739
7766
  var TEMPLATE_VERSION = "v0-interim";
7740
7767
  var NO_EPICS_REASON = "no parseable `- [x] **ID \u2014 Title.**` epic bullets";
@@ -7801,7 +7828,7 @@ async function readOptionalFile(path4) {
7801
7828
  throw error;
7802
7829
  }
7803
7830
  }
7804
- async function generateReport(rootDir, program2, templateHtml) {
7831
+ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
7805
7832
  const planContent = await readFile15(join18(rootDir, program2.planRelPath), "utf8");
7806
7833
  const frontmatter = parseFrontmatter(planContent);
7807
7834
  const model = await buildShowReportModel({
@@ -7813,19 +7840,24 @@ async function generateReport(rootDir, program2, templateHtml) {
7813
7840
  compiledAt: deterministicCompiledAt(frontmatter),
7814
7841
  templateVersion: TEMPLATE_VERSION
7815
7842
  });
7843
+ const themed = applyReportTheme(renderShowReport(model, templateHtml), theme);
7816
7844
  return {
7817
7845
  json: `${JSON.stringify(model, null, 2)}
7818
7846
  `,
7819
- html: renderShowReport(model, templateHtml)
7847
+ html: themed.html,
7848
+ themeApplied: themed.applied
7820
7849
  };
7821
7850
  }
7822
7851
  async function runReportGeneration(options) {
7823
7852
  const templateHtml = await readFile15(options.templatePath, "utf8");
7853
+ const theme = options.theme ?? "auto";
7824
7854
  const drifted = [];
7825
7855
  const repaired = [];
7826
7856
  const unwritten = [];
7857
+ const themeSkipped = [];
7827
7858
  for (const program2 of options.programs) {
7828
- const generated = await generateReport(options.rootDir, program2, templateHtml);
7859
+ const generated = await generateReport(options.rootDir, program2, templateHtml, theme);
7860
+ if (!generated.themeApplied) themeSkipped.push(program2.slug);
7829
7861
  const missing = JSON.parse(generated.json).sections.flatMap((section) => section.rows).filter((row) => row.descriptionState === "unwritten").map((row) => row.id);
7830
7862
  if (missing.length > 0) unwritten.push({ slug: program2.slug, ids: missing });
7831
7863
  const outAbs = options.outDir ? isAbsolute(options.outDir) ? options.outDir : resolve4(options.rootDir, options.outDir) : resolve4(options.rootDir, dirname10(program2.planRelPath));
@@ -7846,7 +7878,7 @@ async function runReportGeneration(options) {
7846
7878
  }
7847
7879
  }
7848
7880
  }
7849
- return { drifted, repaired, unwritten };
7881
+ return { drifted, repaired, unwritten, themeSkipped };
7850
7882
  }
7851
7883
 
7852
7884
  // src/commands/report.ts
@@ -7866,6 +7898,14 @@ async function runReport(targetDir, options = {}, assetsDir) {
7866
7898
  "--refresh: external-data snapshotting (gh PR titles/merge dates) is not implemented yet; regenerating from local sources."
7867
7899
  );
7868
7900
  }
7901
+ const theme = options.theme ?? "auto";
7902
+ if (!REPORT_THEMES.includes(theme)) {
7903
+ console.error(
7904
+ `\u2716 arcane: --theme must be one of ${REPORT_THEMES.join(" | ")}, got "${options.theme}".`
7905
+ );
7906
+ process.exitCode = 1;
7907
+ return;
7908
+ }
7869
7909
  if (await isShallowRepository(targetDir)) {
7870
7910
  console.warn(
7871
7911
  "\u26A0 arcane: shallow clone -- the report's version span and cast are derived from git history and will be omitted. Run `git fetch --unshallow` for a complete report."
@@ -7895,13 +7935,19 @@ async function runReport(targetDir, options = {}, assetsDir) {
7895
7935
  templatePath,
7896
7936
  mode: "write",
7897
7937
  programs,
7898
- outDir: options.out
7938
+ outDir: options.out,
7939
+ theme
7899
7940
  });
7900
7941
  for (const u of result.unwritten) {
7901
7942
  console.warn(
7902
7943
  `\u26A0 arcane: ${u.slug} has ${u.ids.length} epic(s) with no \`**Report:**\` line in PLAN.md -- they render as "unwritten": ${u.ids.join(", ")}.`
7903
7944
  );
7904
7945
  }
7946
+ for (const slug of result.themeSkipped) {
7947
+ console.warn(
7948
+ `\u26A0 arcane: ${slug} -- --theme ${theme} requested, but this template has no theme toggle to set (older vendored template?); left unchanged.`
7949
+ );
7950
+ }
7905
7951
  if (result.repaired.length === 0) {
7906
7952
  console.log(`Show report: ${programs.length} program(s) already up to date.`);
7907
7953
  return;
@@ -8054,6 +8100,9 @@ program.command("report").description(
8054
8100
  ).option("--plan <path>", "Report on one PLAN.md instead of auto-discovering docs/plans/*/PLAN.md").option("--out <dir>", "Write show-report.{json,html} into this directory instead of next to the plan").option(
8055
8101
  "--refresh",
8056
8102
  "Accepted for forward compatibility; external-data snapshotting is not implemented yet, so this regenerates from local sources"
8103
+ ).option(
8104
+ "--theme <mode>",
8105
+ "Force the in-page toggle's initial state: auto | light | dark (default auto, byte-for-byte unchanged). Pair with --out for a one-off themed copy so it doesn't overwrite the tracked show-report.html"
8057
8106
  ).action(async (opts) => {
8058
8107
  await runReport(process.cwd(), opts, ASSETS_DIR);
8059
8108
  });