@ingcreators/annot-product-docs-xlsx 0.1.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,104 @@
1
+ # @ingcreators/annot-product-docs-xlsx
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e92c97: First publish of the living-product-docs package family. Phases
8
+ 1-5 + 7 of `docs/plans/living-product-docs.md` landed across
9
+ PRs 876-899; this entry flips the three packages from
10
+ `private: true` to publishable and stamps `0.1.0`.
11
+
12
+ ### `@ingcreators/annot-product-docs`
13
+ - MDX parser (`parseMdx` / `parseMdxFile`) — Remark / unified
14
+ pipeline that walks `.mdx` files with `annot:` frontmatter
15
+ and extracts `<Screen>` / `<Overlay>` / `<Transition>` /
16
+ `<HistoryEntry>` / `<ScreenList>` JSX components.
17
+ - Match resolver (`parseSnapshot` / `resolveMatch` /
18
+ `resolveOverlays`) for the Playwright `aria-snapshot`
19
+ YAML, honouring `match.under` disambiguation and emitting
20
+ `not-found` / `ambiguous` / `renamed` / `role-changed` /
21
+ `live-mismatch` diagnostics.
22
+ - `screen` fixture extending `@ingcreators/annot-playwright`
23
+ with `screen.capture({ id, mdxPath })` that re-syncs
24
+ `annot:snapshot` + `annot:attributes` MDX comment blocks
25
+ in place.
26
+ - Drift detector (`detectDrift` / `detectDriftFromYaml`) — six
27
+ finding kinds (added / removed / renamed / role-changed /
28
+ duplicated / attribute-drift) with severity buckets.
29
+ - `annot-docs` CLI (`init` / `sync` / `lint`) with `--json`
30
+ / `--ci` / `--fix` flags + a sample GitHub Actions workflow
31
+ emitting GitHub annotations on PR diff views.
32
+
33
+ ### `@ingcreators/annot-product-docs-astro`
34
+ - `productDocsIntegration()` Astro 5.x integration factory.
35
+ - 7 docs components: `<Screen>`, `<Overlay>`, `<Transition>`,
36
+ `<TransitionTable>`, `<HistoryEntry>`, `<ScreenList>`,
37
+ `<TransitionGraph>`. Shipped as `.astro` source under
38
+ `./components/*.astro` exports.
39
+ - Image Service (`renderAnnotatedScreen` + SHA-keyed
40
+ `createFileCache` / `createMemoryCache`) that composes the
41
+ base screenshot with overlay callouts at build time.
42
+
43
+ ### `@ingcreators/annot-product-docs-xlsx`
44
+ - MDX → normalised bundle extractor; per-role default layout
45
+ (cover / history / list / screen / reference); customer-
46
+ template support with `{var}` placeholder substitution
47
+ (including `{annot:date}` special vars + `{name:format}`
48
+ date formatting); Excel Named Range writers
49
+ (`annotImage` / `annotItemTable` / `annotHistory` /
50
+ `annotList` / `annotSnapshot` / `annotAttributes`).
51
+ - `annot-docs-xlsx render` CLI with multi-book emit + per-book
52
+ template config.
53
+
54
+ ### Patch Changes
55
+
56
+ - 657a685: **Republish with `dist/` included.** The `0.1.0` tarballs of all
57
+ three packages shipped to npm without their `dist/` directory —
58
+ the `publish.yml` workflow's pre-pack `pnpm build` step had only
59
+ filtered four other packages, so `pnpm pack` packed the three
60
+ `product-docs*` packages against empty `dist/`s. The
61
+ `publishConfig.main` (`./dist/index.js`) consequently pointed at
62
+ a missing file, breaking `npm install` for every consumer.
63
+
64
+ The source fix landed in
65
+ [#947](https://github.com/ingcreators/annot/pull/947) with two
66
+ defences:
67
+ 1. Three new `--filter` lines in the workflow's build step so
68
+ all seven publishable packages get built before pack.
69
+ 2. A per-package `prepack` script (`pnpm run build`) so even a
70
+ misconfigured workflow (or a manual `pnpm pack` / `pnpm
71
+ publish`) refreshes `dist/` before packing.
72
+
73
+ No source-code changes in any of the three packages — only the
74
+ packaging is fixed. This patch publish exists solely to deliver
75
+ working tarballs to the registry; the public API surface is
76
+ byte-identical to `0.1.0`.
77
+
78
+ Verified locally:
79
+
80
+ ```
81
+ $ pnpm --filter @ingcreators/annot-product-docs pack --dry-run
82
+ Tarball Contents
83
+ bin/annot-docs.mjs
84
+ dist/cli.d.ts
85
+ dist/config.d.ts
86
+ dist/drift.d.ts
87
+ dist/fixture.d.ts
88
+ dist/index.d.ts
89
+ dist/index.js
90
+ dist/mdx.d.ts
91
+ dist/resolver.d.ts
92
+ dist/types-config.d.ts
93
+ dist/types.d.ts
94
+ LICENSE
95
+ package.json
96
+ README.md
97
+ ```
98
+
99
+ Before the fix the same command produced 4 files (LICENSE +
100
+ README + package.json + bin/annot-docs.mjs), no compiled code.
101
+
102
+ - Updated dependencies [2e92c97]
103
+ - Updated dependencies [657a685]
104
+ - @ingcreators/annot-product-docs@0.2.0
package/README.md CHANGED
@@ -9,12 +9,12 @@ files with `annot:` frontmatter, fills customer-supplied Excel
9
9
  templates via `{var}` placeholders + Excel Named Ranges, and
10
10
  emits one `.xlsx` per book.
11
11
 
12
- Targets the Japanese SI 画面設計書 use case where customers
13
- expect a specific corporate Excel template, populated from a
12
+ Targets the corporate screen-specifications use case where
13
+ customers expect a specific Excel template populated from a
14
14
  code-driven source of truth.
15
15
 
16
16
  Phase 3 of
17
- [`docs/plans/living-product-docs.md`](https://github.com/ingcreators/annot/blob/main/docs/plans/living-product-docs.md).
17
+ [`docs/plans/_done/living-product-docs.md`](https://github.com/ingcreators/annot/blob/main/docs/plans/_done/living-product-docs.md).
18
18
 
19
19
  ## Install
20
20
 
@@ -26,7 +26,7 @@ pnpm add @ingcreators/annot-product-docs-xlsx
26
26
 
27
27
  ```sh
28
28
  annot-docs-xlsx render --root docs --out dist/xlsx
29
- annot-docs-xlsx render --book "Screen spec"
29
+ annot-docs-xlsx render --book "Screen specifications"
30
30
  ```
31
31
 
32
32
  Reads `annot-docs.config.ts` from the project root; uses a
@@ -41,19 +41,19 @@ import { defineConfig } from "@ingcreators/annot-product-docs";
41
41
 
42
42
  export default defineConfig({
43
43
  meta: {
44
- projectName: "顧客管理システム",
45
- customerName: "株式会社XYZ",
44
+ projectName: "Customer management system",
45
+ customerName: "Acme Corp.",
46
46
  },
47
47
  xlsx: {
48
- defaultBook: "画面設計書",
48
+ defaultBook: "Screen specifications",
49
49
  books: {
50
- "画面設計書": {
50
+ "Screen specifications": {
51
51
  template: "./templates/customer-screen-spec.xlsx",
52
52
  templateSheets: {
53
- cover: "表紙テンプレ",
54
- history: "改訂履歴テンプレ",
55
- list: "画面一覧テンプレ",
56
- screen: "個別画面テンプレ",
53
+ cover: "Cover template",
54
+ history: "History template",
55
+ list: "Screen list template",
56
+ screen: "Per-screen template",
57
57
  },
58
58
  },
59
59
  },
@@ -71,7 +71,7 @@ In template cells:
71
71
  {meta.author} → frontmatter meta.author
72
72
  {projectName} → project meta.projectName
73
73
  {annot:date} → "2026-05-21"
74
- {annot:date:yyyyMMdd} → "20260521"
74
+ {annot:date:yyyy-MM-dd} → "2026-05-21"
75
75
  {meta.createdDate:yyyy/MM/dd} → "2026/05/21"
76
76
  {annot:sheetIndex}/{annot:totalSheets} → "3/12"
77
77
  ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export interface CliOptions {
2
+ cwd?: string;
3
+ stdout?: (line: string) => void;
4
+ stderr?: (line: string) => void;
5
+ }
6
+ /**
7
+ * `main(argv)` entrypoint. Returns the process exit code so the
8
+ * bin script can `process.exit(...)`. Also driveable from
9
+ * vitest via custom `stdout` / `stderr` / `cwd`.
10
+ */
11
+ export declare function main(argv: string[], options?: CliOptions): Promise<number>;
@@ -0,0 +1,28 @@
1
+ import { default as ExcelJS } from 'exceljs';
2
+ import { ExcelMdxBundle } from './extract.js';
3
+ export interface ApplyDefaultLayoutInput {
4
+ workbook: ExcelJS.Workbook;
5
+ bundles: ExcelMdxBundle[];
6
+ }
7
+ /**
8
+ * Apply the default no-template layout. The workbook is
9
+ * mutated in place — every bundle contributes one sheet,
10
+ * ordered by role then by `xlsx.order` then by file path.
11
+ */
12
+ export declare function applyDefaultLayout(input: ApplyDefaultLayoutInput): void;
13
+ /**
14
+ * Sort bundles in the order they appear in the workbook.
15
+ * Documented in plan section "Open question 7":
16
+ * 1. Role-default order (cover → history → list → screen → reference)
17
+ * 2. Within same role: `order` field (default 100)
18
+ * 3. Tie-break: alphabetical by MDX file path.
19
+ *
20
+ * Exported so the template-path branch (PR 3) can reuse the
21
+ * same sort.
22
+ */
23
+ export declare function sortBundlesForLayout(bundles: ExcelMdxBundle[]): ExcelMdxBundle[];
24
+ /**
25
+ * Excel limits sheet names to 31 chars and forbids `: \ / ? * [ ]`.
26
+ * This is shared with the future template-path branch.
27
+ */
28
+ export declare function sanitiseSheetName(name: string): string;
@@ -0,0 +1,48 @@
1
+ import { AnnotFrontmatter, HistoryEntrySpec, OverlaySpec, ParsedMdx } from '@ingcreators/annot-product-docs';
2
+ export interface ExcelMdxBundle {
3
+ /** Absolute or cwd-relative path to the source `.mdx` file. */
4
+ mdxPath: string;
5
+ /** Verbatim source bytes — kept so downstream tools can hash. */
6
+ source: string;
7
+ /** The `annot:` frontmatter block. */
8
+ frontmatter: AnnotFrontmatter;
9
+ /** Flat per-screen rows (one entry per `<Screen>`). */
10
+ screens: NormalisedScreen[];
11
+ /** Flat per-overlay rows (one entry per `<Overlay>`, including its
12
+ * parent screen's id + screen-relative number). */
13
+ overlays: NormalisedOverlay[];
14
+ /** Flat per-history-entry rows (only populated for `role: history` MDXs). */
15
+ history: HistoryEntrySpec[];
16
+ /** Verbatim aria-snapshot block as captured by the fixture. */
17
+ snapshotYaml: string;
18
+ /** Verbatim attributes block as captured by the fixture. */
19
+ attributesYaml: string;
20
+ }
21
+ export interface NormalisedScreen {
22
+ id: string;
23
+ src: string | undefined;
24
+ overlayCount: number;
25
+ }
26
+ export interface NormalisedOverlay {
27
+ screenId: string;
28
+ number: number;
29
+ intent: OverlaySpec["intent"];
30
+ /** `role + name` joined for one-cell rendering. */
31
+ matchLabel: string;
32
+ matchRole: string;
33
+ matchName: string;
34
+ /** Markdown body — caller renders to rich text. */
35
+ body: string;
36
+ }
37
+ /**
38
+ * Extract a normalised bundle from a parsed MDX. Used directly
39
+ * by `extractMdxFile` and re-exposed so tests can drive it
40
+ * without filesystem I/O.
41
+ */
42
+ export declare function extractFromParsed(parsed: ParsedMdx, mdxPath: string): ExcelMdxBundle;
43
+ /**
44
+ * Convenience: parse the MDX file and normalise. Returns `null`
45
+ * for files without `annot:` frontmatter so the bulk-extract
46
+ * caller can keep its glob noise-free.
47
+ */
48
+ export declare function extractMdxFile(mdxPath: string): Promise<ExcelMdxBundle | null>;
@@ -0,0 +1,14 @@
1
+ export type { CliOptions } from './cli.js';
2
+ export { main, main as cliMain } from './cli.js';
3
+ export type { ApplyDefaultLayoutInput } from './default-layout.js';
4
+ export { applyDefaultLayout, sanitiseSheetName, sortBundlesForLayout, } from './default-layout.js';
5
+ export type { ExcelMdxBundle, NormalisedOverlay, NormalisedScreen, } from './extract.js';
6
+ export { extractFromParsed, extractMdxFile } from './extract.js';
7
+ export type { NamedRangeWriteInput } from './named-range.js';
8
+ export { applyNamedRanges } from './named-range.js';
9
+ export type { SubstituteOptions } from './placeholder.js';
10
+ export { applyPlaceholders, applyPlaceholdersToSheet, resolvePlaceholders, } from './placeholder.js';
11
+ export type { ApplyTemplateInput } from './template.js';
12
+ export { applyTemplateLayout, loadTemplateWorkbook } from './template.js';
13
+ export type { BuildWorkbookInput } from './workbook.js';
14
+ export { buildEmptyWorkbook, groupBundlesByBook, writeWorkbookToBytes, } from './workbook.js';
package/dist/index.js ADDED
@@ -0,0 +1,616 @@
1
+ import { mkdir as e, readdir as t, writeFile as n } from "node:fs/promises";
2
+ import { join as r, resolve as i } from "node:path";
3
+ import { pathToFileURL as a } from "node:url";
4
+ import { parseMdxFile as o } from "@ingcreators/annot-product-docs";
5
+ import s from "exceljs";
6
+ //#region src/default-layout.ts
7
+ var c = {
8
+ cover: 1,
9
+ history: 2,
10
+ list: 3,
11
+ screen: 4,
12
+ reference: 5
13
+ };
14
+ function l(e) {
15
+ let t = u(e.bundles);
16
+ for (let n of t) {
17
+ let t = n.frontmatter.xlsx?.role ?? "screen", r = g(n, t), i = e.workbook.addWorksheet(r);
18
+ switch (t) {
19
+ case "cover":
20
+ d(i, n);
21
+ break;
22
+ case "history":
23
+ f(i, n);
24
+ break;
25
+ case "list":
26
+ p(i, e.bundles);
27
+ break;
28
+ case "reference":
29
+ h(i, n);
30
+ break;
31
+ default: m(i, n);
32
+ }
33
+ }
34
+ }
35
+ function u(e) {
36
+ return [...e].sort((e, t) => {
37
+ let n = c[e.frontmatter.xlsx?.role ?? "screen"] ?? 99, r = c[t.frontmatter.xlsx?.role ?? "screen"] ?? 99;
38
+ if (n !== r) return n - r;
39
+ let i = e.frontmatter.xlsx?.order ?? 100, a = t.frontmatter.xlsx?.order ?? 100;
40
+ return i === a ? e.mdxPath.localeCompare(t.mdxPath) : i - a;
41
+ });
42
+ }
43
+ function d(e, t) {
44
+ let n = t.frontmatter;
45
+ if (e.columns = [{
46
+ key: "label",
47
+ width: 24
48
+ }, {
49
+ key: "value",
50
+ width: 60
51
+ }], e.addRow({
52
+ label: "Title",
53
+ value: n.title ?? n.id
54
+ }), e.addRow({
55
+ label: "Document ID",
56
+ value: n.id
57
+ }), n.purpose && e.addRow({
58
+ label: "Purpose",
59
+ value: n.purpose
60
+ }), n.meta) for (let [t, r] of Object.entries(n.meta)) e.addRow({
61
+ label: `meta.${t}`,
62
+ value: String(r)
63
+ });
64
+ e.getColumn(1).font = { bold: !0 };
65
+ }
66
+ function f(e, t) {
67
+ e.columns = [
68
+ {
69
+ header: "Version",
70
+ key: "version",
71
+ width: 12
72
+ },
73
+ {
74
+ header: "Date",
75
+ key: "date",
76
+ width: 14
77
+ },
78
+ {
79
+ header: "Author",
80
+ key: "author",
81
+ width: 18
82
+ },
83
+ {
84
+ header: "Notes",
85
+ key: "body",
86
+ width: 60
87
+ }
88
+ ];
89
+ for (let n of t.history) e.addRow({
90
+ version: n.version,
91
+ date: n.date,
92
+ author: n.author,
93
+ body: n.body
94
+ });
95
+ e.getRow(1).font = { bold: !0 };
96
+ }
97
+ function p(e, t) {
98
+ e.columns = [
99
+ {
100
+ header: "ID",
101
+ key: "id",
102
+ width: 14
103
+ },
104
+ {
105
+ header: "Title",
106
+ key: "title",
107
+ width: 36
108
+ },
109
+ {
110
+ header: "Screens",
111
+ key: "screens",
112
+ width: 10
113
+ },
114
+ {
115
+ header: "Overlays",
116
+ key: "overlays",
117
+ width: 10
118
+ }
119
+ ];
120
+ for (let n of t) (n.frontmatter.xlsx?.role ?? "screen") === "screen" && e.addRow({
121
+ id: n.frontmatter.id,
122
+ title: n.frontmatter.title ?? n.frontmatter.id,
123
+ screens: n.screens.length,
124
+ overlays: n.overlays.length
125
+ });
126
+ e.getRow(1).font = { bold: !0 };
127
+ }
128
+ function m(e, t) {
129
+ let n = t.frontmatter;
130
+ e.addRow(["ID", n.id]), e.addRow(["Title", n.title ?? ""]), n.purpose && e.addRow(["Purpose", n.purpose]), e.addRow([]), e.addRow([
131
+ "#",
132
+ "Role",
133
+ "Name",
134
+ "Intent",
135
+ "Notes"
136
+ ]);
137
+ let r = e.lastRow;
138
+ r.font = { bold: !0 };
139
+ for (let n of t.overlays) e.addRow([
140
+ n.number,
141
+ n.matchRole,
142
+ n.matchName,
143
+ n.intent ?? "",
144
+ n.body
145
+ ]);
146
+ e.getColumn(1).width = 6, e.getColumn(2).width = 14, e.getColumn(3).width = 24, e.getColumn(4).width = 12, e.getColumn(5).width = 60, e.getColumn(1).font = { bold: !0 };
147
+ }
148
+ function h(e, t) {
149
+ e.addRow(["ID", t.frontmatter.id]), e.addRow(["Title", t.frontmatter.title ?? ""]), t.frontmatter.purpose && e.addRow(["Purpose", t.frontmatter.purpose]);
150
+ }
151
+ function g(e, t) {
152
+ let n = e.frontmatter.xlsx;
153
+ return typeof n?.sheet == "string" && n.sheet.length > 0 ? _(n.sheet) : t === "cover" ? "Cover" : t === "history" ? "Revision history" : t === "list" ? "Screen list" : _(t === "reference" ? e.frontmatter.id : e.frontmatter.title ?? e.frontmatter.id);
154
+ }
155
+ function _(e) {
156
+ let t = e.replace(/[\\/?*[\]:]/g, "_").slice(0, 31);
157
+ return t.length > 0 ? t : "Sheet";
158
+ }
159
+ //#endregion
160
+ //#region src/extract.ts
161
+ function v(e, t) {
162
+ let n = e.screens.map((e) => ({
163
+ id: e.id,
164
+ src: e.src,
165
+ overlayCount: e.overlays.length
166
+ })), r = [];
167
+ for (let t of e.screens) {
168
+ let e = 1;
169
+ for (let n of t.overlays) {
170
+ let i = n.number ?? e++;
171
+ r.push({
172
+ screenId: t.id,
173
+ number: i,
174
+ intent: n.intent,
175
+ matchLabel: ee(n),
176
+ matchRole: n.match.role,
177
+ matchName: n.match.name,
178
+ body: n.body
179
+ });
180
+ }
181
+ }
182
+ return {
183
+ mdxPath: t,
184
+ source: e.source,
185
+ frontmatter: e.frontmatter,
186
+ screens: n,
187
+ overlays: r,
188
+ history: e.history,
189
+ snapshotYaml: e.commentBlocks.snapshot ?? "",
190
+ attributesYaml: e.commentBlocks.attributes ?? ""
191
+ };
192
+ }
193
+ async function y(e) {
194
+ let t = await o(e);
195
+ return t ? v(t, e) : null;
196
+ }
197
+ function ee(e) {
198
+ let t = `${e.match.role} "${e.match.name}"`;
199
+ return e.match.under ? `${t} under ${e.match.under.role} "${e.match.under.name}"` : t;
200
+ }
201
+ //#endregion
202
+ //#region src/placeholder.ts
203
+ var te = /\{([^{}]+)\}/g, b = "annot:";
204
+ function x(e, t, n = {}) {
205
+ let r = n.totalSheets ?? e.worksheets.length;
206
+ e.worksheets.forEach((e, i) => {
207
+ S(e, t, {
208
+ ...n,
209
+ sheetIndex: n.sheetIndex ?? i + 1,
210
+ totalSheets: r
211
+ });
212
+ });
213
+ }
214
+ function S(e, t, n = {}) {
215
+ e.eachRow({ includeEmpty: !1 }, (e) => {
216
+ e.eachCell({ includeEmpty: !1 }, (e) => {
217
+ let r = e.value;
218
+ if (typeof r != "string") return;
219
+ let i = C(r, t, n);
220
+ i !== r && (e.value = i);
221
+ });
222
+ });
223
+ }
224
+ function C(e, t, n = {}) {
225
+ return e.replace(te, (e, r) => {
226
+ let { key: i, format: a } = w(r), o = T(i, t, n);
227
+ return o === void 0 ? e : a ? E(o, a) : o;
228
+ });
229
+ }
230
+ function w(e) {
231
+ let t = e.trim();
232
+ if (t.startsWith(b)) {
233
+ let e = t.slice(6), n = e.indexOf(":");
234
+ return n < 0 ? {
235
+ key: t,
236
+ format: void 0
237
+ } : {
238
+ key: b + e.slice(0, n),
239
+ format: e.slice(n + 1)
240
+ };
241
+ }
242
+ let n = t.indexOf(":");
243
+ return n < 0 ? {
244
+ key: t,
245
+ format: void 0
246
+ } : {
247
+ key: t.slice(0, n),
248
+ format: t.slice(n + 1)
249
+ };
250
+ }
251
+ function T(e, t, n) {
252
+ if (e.startsWith(b)) {
253
+ let t = e.slice(6), r = n.renderTime ?? /* @__PURE__ */ new Date();
254
+ switch (t) {
255
+ case "date": return O(r, "yyyy-MM-dd");
256
+ case "datetime": return r.toISOString();
257
+ case "sheetIndex": return n.sheetIndex === void 0 ? void 0 : String(n.sheetIndex);
258
+ case "totalSheets": return n.totalSheets === void 0 ? void 0 : String(n.totalSheets);
259
+ }
260
+ return;
261
+ }
262
+ if (e.startsWith("meta.")) {
263
+ let r = e.slice(5), i = t.frontmatter.meta?.[r] ?? n.projectMeta?.[r];
264
+ return i === void 0 ? void 0 : String(i);
265
+ }
266
+ if (e === "id") return t.frontmatter.id;
267
+ if (e === "title") return t.frontmatter.title;
268
+ if (e === "purpose") return t.frontmatter.purpose;
269
+ let r = n.projectMeta?.[e];
270
+ if (r !== void 0) return String(r);
271
+ }
272
+ function E(e, t) {
273
+ let n = D(e);
274
+ return n ? O(n, t) : e;
275
+ }
276
+ function D(e) {
277
+ let t = e.trim(), n = t.match(/^(\d{4})-(\d{2})-(\d{2})$/);
278
+ if (n) return new Date(Number.parseInt(n[1], 10), Number.parseInt(n[2], 10) - 1, Number.parseInt(n[3], 10));
279
+ let r = new Date(t);
280
+ return Number.isNaN(r.getTime()) ? null : r;
281
+ }
282
+ function O(e, t) {
283
+ let n = String(e.getFullYear()).padStart(4, "0"), r = String(e.getMonth() + 1).padStart(2, "0"), i = String(e.getDate()).padStart(2, "0"), a = String(e.getHours()).padStart(2, "0"), o = String(e.getMinutes()).padStart(2, "0"), s = String(e.getSeconds()).padStart(2, "0");
284
+ return t.replace(/yyyy/g, n).replace(/MM/g, r).replace(/dd/g, i).replace(/HH/g, a).replace(/mm/g, o).replace(/ss/g, s);
285
+ }
286
+ //#endregion
287
+ //#region src/template.ts
288
+ function k(e) {
289
+ let t = u(e.bundles);
290
+ for (let n of t) {
291
+ let t = n.frontmatter.xlsx?.role ?? "screen", r = e.bookConfig.templateSheets?.[t];
292
+ if (!r) continue;
293
+ let i = e.template.getWorksheet(r);
294
+ if (!i) throw Error(`Template sheet "${r}" not found for role "${t}" in book template.`);
295
+ let a = n.frontmatter.xlsx?.sheets;
296
+ if (a) {
297
+ let t = Object.entries(a);
298
+ for (let [, r] of t) S(j(e.workbook, i, _(r)), n, e.substitute ?? {});
299
+ continue;
300
+ }
301
+ let o = _(n.frontmatter.xlsx?.sheet ?? n.frontmatter.title ?? n.frontmatter.id);
302
+ S(j(e.workbook, i, o), n, e.substitute ?? {});
303
+ }
304
+ }
305
+ async function A(e) {
306
+ let t = new s.Workbook();
307
+ return await t.xlsx.readFile(e), t;
308
+ }
309
+ function j(e, t, n) {
310
+ let r = e.addWorksheet(n);
311
+ r.columns = t.columns.map((e) => ({
312
+ header: e.header,
313
+ key: e.key,
314
+ width: e.width,
315
+ style: e.style
316
+ })), t.eachRow({ includeEmpty: !0 }, (e, t) => {
317
+ let n = r.getRow(t);
318
+ e.eachCell({ includeEmpty: !0 }, (e, t) => {
319
+ let r = n.getCell(t);
320
+ r.value = e.value, e.style && (r.style = e.style);
321
+ }), e.height !== void 0 && (n.height = e.height), n.commit();
322
+ });
323
+ for (let e of Object.values(M(t))) r.mergeCells(e);
324
+ return r;
325
+ }
326
+ function M(e) {
327
+ return e._merges ?? {};
328
+ }
329
+ //#endregion
330
+ //#region src/workbook.ts
331
+ function N(e) {
332
+ let t = new s.Workbook();
333
+ return t.creator = "@ingcreators/annot-product-docs-xlsx", t.created = /* @__PURE__ */ new Date(), t.title = e.book, t;
334
+ }
335
+ async function P(e) {
336
+ let t = await e.xlsx.writeBuffer();
337
+ return t instanceof Buffer ? new Uint8Array(t.buffer, t.byteOffset, t.byteLength) : new Uint8Array(t);
338
+ }
339
+ function F(e, t) {
340
+ let n = /* @__PURE__ */ new Map();
341
+ for (let r of e) {
342
+ let e = r.frontmatter.xlsx?.book ?? t;
343
+ if (!e) continue;
344
+ let i = n.get(e);
345
+ i || (i = [], n.set(e, i)), i.push(r);
346
+ }
347
+ return n;
348
+ }
349
+ //#endregion
350
+ //#region src/cli.ts
351
+ var I = [
352
+ "annot-docs-xlsx <command> [options]",
353
+ "",
354
+ "Commands:",
355
+ " render Walk MDXs and emit one .xlsx per book",
356
+ "",
357
+ "Options:",
358
+ " --root <dir> MDX search root (default: docs)",
359
+ " --out <dir> Output dir for .xlsx files (default: dist/xlsx)",
360
+ " --book <name> Restrict to a single book name",
361
+ " --config <file> Path to annot-docs.config.ts (default: ./annot-docs.config.ts)",
362
+ " --help Show this help"
363
+ ].join("\n");
364
+ async function L(e, t = {}) {
365
+ let n = t.stdout ?? ((e) => process.stdout.write(`${e}\n`)), r = t.stderr ?? ((e) => process.stderr.write(`${e}\n`)), i = t.cwd ?? process.cwd(), [, , a, ...o] = e;
366
+ if (a !== "render") return r(I), +!!a;
367
+ let s = R(o);
368
+ try {
369
+ return await z(i, s, n);
370
+ } catch (e) {
371
+ return r(`annot-docs-xlsx render: ${e.message}`), 1;
372
+ }
373
+ }
374
+ function R(e) {
375
+ let t = "docs", n = "dist/xlsx", r, i = "annot-docs.config.ts";
376
+ for (let a = 0; a < e.length; a++) {
377
+ let o = e[a];
378
+ o === "--root" ? t = e[++a] ?? t : o === "--out" ? n = e[++a] ?? n : o === "--book" ? r = e[++a] : o === "--config" && (i = e[++a] ?? i);
379
+ }
380
+ return {
381
+ root: t,
382
+ out: n,
383
+ book: r,
384
+ config: i
385
+ };
386
+ }
387
+ async function z(t, a, o) {
388
+ let s = await B(i(t, a.config), o), c = await H(await V(i(t, a.root)));
389
+ if (c.length === 0) return o(`annot-docs-xlsx render: no MDX files with \`annot:\` frontmatter under ${a.root}.`), 0;
390
+ let u = F(c, s.xlsx?.defaultBook), d = i(t, a.out);
391
+ await e(d, { recursive: !0 });
392
+ let f = 0;
393
+ for (let [e, c] of u.entries()) {
394
+ if (a.book && e !== a.book) continue;
395
+ let u = N({
396
+ book: e,
397
+ bundles: c
398
+ }), p = s.xlsx?.books?.[e], m = p?.template;
399
+ m ? k({
400
+ workbook: u,
401
+ template: await A(i(t, m)),
402
+ bookConfig: p,
403
+ bundles: c,
404
+ substitute: {
405
+ projectMeta: s.meta,
406
+ renderTime: /* @__PURE__ */ new Date()
407
+ }
408
+ }) : l({
409
+ workbook: u,
410
+ bundles: c
411
+ });
412
+ let h = await P(u), g = r(d, `${U(e)}.xlsx`);
413
+ await n(g, h), o(` wrote ${g} (${c.length} MDX file(s))`), f++;
414
+ }
415
+ return a.book && f === 0 ? (o(`annot-docs-xlsx render: book "${a.book}" not found (had: ${[...u.keys()].join(", ")}).`), 1) : (o(`annot-docs-xlsx render: emitted ${f} workbook(s) to ${a.out}`), 0);
416
+ }
417
+ async function B(e, t) {
418
+ try {
419
+ let t = await import(a(e).href);
420
+ return t.default ?? t;
421
+ } catch (n) {
422
+ if (n.code === "ERR_MODULE_NOT_FOUND") return t(`annot-docs-xlsx render: no config at ${e} — using defaults.`), {};
423
+ throw n;
424
+ }
425
+ }
426
+ async function V(e) {
427
+ let n = [], i = [e];
428
+ for (; i.length > 0;) {
429
+ let e = i.pop(), a;
430
+ try {
431
+ a = await t(e, { withFileTypes: !0 });
432
+ } catch (e) {
433
+ if (e.code === "ENOENT") continue;
434
+ throw e;
435
+ }
436
+ for (let t of a) {
437
+ let a = r(e, t.name);
438
+ if (t.isDirectory()) {
439
+ if (t.name === "node_modules" || t.name.startsWith(".")) continue;
440
+ i.push(a);
441
+ } else t.isFile() && t.name.endsWith(".mdx") && n.push(a);
442
+ }
443
+ }
444
+ return n.sort();
445
+ }
446
+ async function H(e) {
447
+ let t = [];
448
+ for (let n of e) try {
449
+ let e = await o(n);
450
+ e && t.push(v(e, n));
451
+ } catch {}
452
+ return t;
453
+ }
454
+ function U(e) {
455
+ return e.replace(/[\\/?*[\]:<>|"]/g, "_");
456
+ }
457
+ //#endregion
458
+ //#region src/named-range.ts
459
+ var W = "annot";
460
+ function G(e) {
461
+ for (let t of ie(e.workbook)) K(e, t);
462
+ }
463
+ function K(e, t) {
464
+ let [n, r] = q(t.name.replace(/^annot/, ""));
465
+ switch (n) {
466
+ case "Image":
467
+ J(e, t, r);
468
+ return;
469
+ case "ItemTable":
470
+ X(t, e.bundle);
471
+ return;
472
+ case "Transitions":
473
+ Z(t, e.bundle);
474
+ return;
475
+ case "History":
476
+ ne(t, e.bundle);
477
+ return;
478
+ case "List":
479
+ re(t, e.allBundles ?? [e.bundle]);
480
+ return;
481
+ case "Snapshot":
482
+ Q(t, e.bundle.snapshotYaml);
483
+ return;
484
+ case "Attributes":
485
+ Q(t, e.bundle.attributesYaml);
486
+ return;
487
+ }
488
+ }
489
+ function q(e) {
490
+ let t = e.indexOf("_");
491
+ return t < 0 ? [e, void 0] : [e.slice(0, t), e.slice(t + 1)];
492
+ }
493
+ function J(e, t, n) {
494
+ let r = n ? e.imagesByScreenId?.[n] : e.defaultImage ?? e.imagesByScreenId?.[e.bundle.screens[0]?.id ?? ""];
495
+ if (!r) return;
496
+ let i = e.workbook.addImage({
497
+ buffer: Buffer.from(r),
498
+ extension: "png"
499
+ });
500
+ t.sheet.addImage(i, t.address);
501
+ }
502
+ function Y(e, t, n) {
503
+ let { sheet: r, box: i } = e;
504
+ for (let e = 0; e < t.length; e++) {
505
+ let n = r.getCell(i.startRow, i.startCol + e);
506
+ n.value = t[e] ?? "", n.font = { bold: !0 };
507
+ }
508
+ for (let e = 0; e < n.length; e++) {
509
+ let t = n[e], a = i.startRow + 1 + e;
510
+ if (a > i.endRow) break;
511
+ for (let e = 0; e < t.length; e++) r.getCell(a, i.startCol + e).value = t[e] ?? "";
512
+ }
513
+ }
514
+ function X(e, t) {
515
+ Y(e, [
516
+ "#",
517
+ "Role",
518
+ "Name",
519
+ "Intent",
520
+ "Notes"
521
+ ], t.overlays.map((e) => [
522
+ e.number,
523
+ e.matchRole,
524
+ e.matchName,
525
+ e.intent ?? "",
526
+ e.body
527
+ ]));
528
+ }
529
+ function Z(e, t) {
530
+ Y(e, [
531
+ "Trigger",
532
+ "Event",
533
+ "Target",
534
+ "Notes"
535
+ ], []);
536
+ }
537
+ function ne(e, t) {
538
+ Y(e, [
539
+ "Version",
540
+ "Date",
541
+ "Author",
542
+ "Notes"
543
+ ], t.history.map((e) => [
544
+ e.version,
545
+ e.date,
546
+ e.author,
547
+ e.body
548
+ ]));
549
+ }
550
+ function re(e, t) {
551
+ Y(e, [
552
+ "ID",
553
+ "Title",
554
+ "Screens",
555
+ "Overlays"
556
+ ], t.filter((e) => (e.frontmatter.xlsx?.role ?? "screen") === "screen").map((e) => [
557
+ e.frontmatter.id,
558
+ e.frontmatter.title ?? e.frontmatter.id,
559
+ e.screens.length,
560
+ e.overlays.length
561
+ ]));
562
+ }
563
+ function Q(e, t) {
564
+ let n = e.sheet.getCell(e.box.startRow, e.box.startCol);
565
+ n.value = t, n.alignment = {
566
+ wrapText: !0,
567
+ vertical: "top"
568
+ };
569
+ }
570
+ function ie(e) {
571
+ let t = /* @__PURE__ */ new Map();
572
+ for (let n of e.worksheets) n.eachRow({ includeEmpty: !0 }, (e, r) => {
573
+ e.eachCell({ includeEmpty: !0 }, (e, i) => {
574
+ let a = ae(e);
575
+ for (let e of a) {
576
+ if (!e.startsWith(W)) continue;
577
+ let a = `${n.name}|${e}`, o = t.get(a);
578
+ o || (o = {
579
+ sheet: n,
580
+ name: e,
581
+ cells: []
582
+ }, t.set(a, o)), o.cells.push({
583
+ row: r,
584
+ col: i
585
+ });
586
+ }
587
+ });
588
+ });
589
+ return [...t.values()].map((e) => {
590
+ let t = e.cells.map((e) => e.row), n = e.cells.map((e) => e.col), r = {
591
+ startRow: Math.min(...t),
592
+ startCol: Math.min(...n),
593
+ endRow: Math.max(...t),
594
+ endCol: Math.max(...n)
595
+ };
596
+ return {
597
+ name: e.name,
598
+ sheet: e.sheet,
599
+ box: r,
600
+ address: `${$(r.startCol)}${r.startRow}:${$(r.endCol)}${r.endRow}`
601
+ };
602
+ });
603
+ }
604
+ function ae(e) {
605
+ return e.names ?? [];
606
+ }
607
+ function $(e) {
608
+ let t = e, n = "";
609
+ for (; t > 0;) {
610
+ let e = (t - 1) % 26;
611
+ n = String.fromCharCode(65 + e) + n, t = Math.floor((t - 1) / 26);
612
+ }
613
+ return n;
614
+ }
615
+ //#endregion
616
+ export { l as applyDefaultLayout, G as applyNamedRanges, x as applyPlaceholders, S as applyPlaceholdersToSheet, k as applyTemplateLayout, N as buildEmptyWorkbook, L as cliMain, L as main, v as extractFromParsed, y as extractMdxFile, F as groupBundlesByBook, A as loadTemplateWorkbook, C as resolvePlaceholders, _ as sanitiseSheetName, u as sortBundlesForLayout, P as writeWorkbookToBytes };
@@ -0,0 +1,18 @@
1
+ import { default as ExcelJS } from 'exceljs';
2
+ import { ExcelMdxBundle } from './extract.js';
3
+ export interface NamedRangeWriteInput {
4
+ workbook: ExcelJS.Workbook;
5
+ bundle: ExcelMdxBundle;
6
+ /** PNG bytes per `<Screen id>` (Phase 2 PR 2 Image Service). */
7
+ imagesByScreenId?: Record<string, Uint8Array>;
8
+ /** Fallback PNG when an `annotImage` (un-suffixed) range is
9
+ * used and the MDX has exactly one screen. */
10
+ defaultImage?: Uint8Array;
11
+ /** All bundles in the same book — used by `annotList`. */
12
+ allBundles?: ExcelMdxBundle[];
13
+ }
14
+ /**
15
+ * Apply every recognised `annot*` named range in the workbook
16
+ * for one MDX bundle. Mutates the workbook in place.
17
+ */
18
+ export declare function applyNamedRanges(input: NamedRangeWriteInput): void;
@@ -0,0 +1,34 @@
1
+ import { default as ExcelJS } from 'exceljs';
2
+ import { ExcelMdxBundle } from './extract.js';
3
+ export interface SubstituteOptions {
4
+ /** Project-level `meta` from `annot-docs.config.ts`. */
5
+ projectMeta?: Record<string, unknown>;
6
+ /** Used by `{annot:date}` / `{annot:datetime}`. Defaults to
7
+ * `new Date()` per call but the pipeline can pin a single
8
+ * `renderTime` across all substitutions so the workbook
9
+ * has consistent timestamps. */
10
+ renderTime?: Date;
11
+ /** Used by `{annot:sheetIndex}` / `{annot:totalSheets}`. The
12
+ * per-sheet wrapper passes the index of the sheet being
13
+ * rendered + the total. */
14
+ sheetIndex?: number;
15
+ totalSheets?: number;
16
+ }
17
+ /**
18
+ * Walk every cell on every worksheet in the workbook and
19
+ * substitute `{var}` placeholders. Mutates in place.
20
+ */
21
+ export declare function applyPlaceholders(workbook: ExcelJS.Workbook, bundle: ExcelMdxBundle, options?: SubstituteOptions): void;
22
+ /**
23
+ * Single-sheet version — exposed so the template-driven path
24
+ * (which clones one template sheet per `<Screen>`) can call it
25
+ * after the clone.
26
+ */
27
+ export declare function applyPlaceholdersToSheet(sheet: ExcelJS.Worksheet, bundle: ExcelMdxBundle, options?: SubstituteOptions): void;
28
+ /**
29
+ * Pure string transform — replace every `{name}` or
30
+ * `{name:format}` token in a source string with its resolved
31
+ * value. Exposed for unit tests and for callers (sheet-name
32
+ * substitution, log messages, …).
33
+ */
34
+ export declare function resolvePlaceholders(source: string, bundle: ExcelMdxBundle, options?: SubstituteOptions): string;
@@ -0,0 +1,28 @@
1
+ import { BookConfig } from '@ingcreators/annot-product-docs';
2
+ import { default as ExcelJS } from 'exceljs';
3
+ import { ExcelMdxBundle } from './extract.js';
4
+ import { SubstituteOptions } from './placeholder.js';
5
+ export interface ApplyTemplateInput {
6
+ /** The empty workbook to populate. */
7
+ workbook: ExcelJS.Workbook;
8
+ /** Pre-loaded ExcelJS workbook from the customer template. */
9
+ template: ExcelJS.Workbook;
10
+ /** Per-book config from `annot-docs.config.ts`. */
11
+ bookConfig: BookConfig;
12
+ /** All bundles assigned to this book. */
13
+ bundles: ExcelMdxBundle[];
14
+ /** Substitution options forwarded to `applyPlaceholdersToSheet`. */
15
+ substitute?: SubstituteOptions;
16
+ }
17
+ /**
18
+ * Apply the customer template path. Mutates `workbook` in
19
+ * place: clones the configured template sheet for each role
20
+ * once per matching bundle, renames each clone, and runs
21
+ * placeholder substitution on every cloned cell.
22
+ */
23
+ export declare function applyTemplateLayout(input: ApplyTemplateInput): void;
24
+ /**
25
+ * Load a template workbook from disk. Thin wrapper around
26
+ * ExcelJS so the caller doesn't have to import it directly.
27
+ */
28
+ export declare function loadTemplateWorkbook(path: string): Promise<ExcelJS.Workbook>;
@@ -0,0 +1,28 @@
1
+ import { default as ExcelJS } from 'exceljs';
2
+ import { ExcelMdxBundle } from './extract.js';
3
+ export interface BuildWorkbookInput {
4
+ /** Book name from MDX frontmatter (`xlsx.book`). All MDXs for
5
+ * this book end up in the same workbook. */
6
+ book: string;
7
+ /** All MDX bundles assigned to this book. */
8
+ bundles: ExcelMdxBundle[];
9
+ }
10
+ /**
11
+ * Produce an `ExcelJS.Workbook` with the book name set as the
12
+ * workbook title but no sheets yet. PR 2 of Phase 3 fills it
13
+ * with the default layout; PR 3 adds template support.
14
+ */
15
+ export declare function buildEmptyWorkbook(input: BuildWorkbookInput): ExcelJS.Workbook;
16
+ /**
17
+ * Serialise a workbook to PNG bytes. Wrapper around ExcelJS's
18
+ * `xlsx.writeBuffer` that normalises the return type to
19
+ * `Uint8Array` — ExcelJS uses Node `Buffer` natively.
20
+ */
21
+ export declare function writeWorkbookToBytes(wb: ExcelJS.Workbook): Promise<Uint8Array>;
22
+ /**
23
+ * Group a list of `ExcelMdxBundle`s by their `xlsx.book` value.
24
+ * MDXs without a book fall under the optional `defaultBook`
25
+ * key (typically wired from the project config); MDXs without
26
+ * either get filtered out.
27
+ */
28
+ export declare function groupBundlesByBook(bundles: ExcelMdxBundle[], defaultBook?: string): Map<string, ExcelMdxBundle[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingcreators/annot-product-docs-xlsx",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Excel adapter for `@ingcreators/annot-product-docs`. Walks MDX files with `annot:` frontmatter, fills customer-supplied Excel templates via `{var}` placeholders + named ranges, and emits one `.xlsx` per book. Phase 3 of docs/plans/living-product-docs.md.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ingcreators",
@@ -22,7 +22,7 @@
22
22
  "mdx",
23
23
  "docs",
24
24
  "screen-spec",
25
- "画面設計書"
25
+ "screen-specifications"
26
26
  ],
27
27
  "type": "module",
28
28
  "main": "./dist/index.js",
@@ -47,12 +47,12 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "typescript": "^6.0.3",
50
- "vite": "^6.4.2",
51
- "vite-plugin-dts": "^5.0.0"
50
+ "vite": "^8.0.13",
51
+ "vite-plugin-dts": "^5.0.1"
52
52
  },
53
53
  "dependencies": {
54
54
  "exceljs": "^4.4.0",
55
- "@ingcreators/annot-product-docs": "0.1.0"
55
+ "@ingcreators/annot-product-docs": "0.2.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "vite build",