book-kit 0.0.0-stage → 0.6.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.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +72 -0
  3. package/README.md +251 -2
  4. package/bin/book-kit.js +115 -0
  5. package/commands/_emoji_io.py +32 -0
  6. package/commands/audit-chapter-lengths.js +244 -0
  7. package/commands/check-epub-size.js +68 -0
  8. package/commands/check-pdf-page-count.js +162 -0
  9. package/commands/composite-kindle-emoji.py +85 -0
  10. package/commands/composite-print-emoji.py +98 -0
  11. package/commands/conventions.js +76 -0
  12. package/commands/convert-pdfx.sh +194 -0
  13. package/commands/download-icc-profile.sh +105 -0
  14. package/commands/fonts.py +212 -0
  15. package/commands/generate-reference-docx.py +344 -0
  16. package/commands/kindle-fonts.js +173 -0
  17. package/commands/links-to-footnotes.js +366 -0
  18. package/commands/markdownlint.js +25 -0
  19. package/commands/minify-css.js +43 -0
  20. package/commands/optimize-images.js +73 -0
  21. package/commands/parity.sh +150 -0
  22. package/commands/postprocess-epub.js +541 -0
  23. package/commands/prep-print-src.js +77 -0
  24. package/commands/prose.js +38 -0
  25. package/commands/release-files.js +89 -0
  26. package/commands/reorder-print-frontmatter.js +79 -0
  27. package/commands/strip-page-rules.js +1168 -0
  28. package/commands/strip-property-rules.js +93 -0
  29. package/commands/update-key-takeaways.js +314 -0
  30. package/commands/validate.js +283 -0
  31. package/configs/markdownlint-cli2.jsonc +98 -0
  32. package/configs/textlintrc.json +22 -0
  33. package/css/theme/01-tokens.css +83 -0
  34. package/css/theme/02-fonts.css +79 -0
  35. package/css/theme/03-prose.css +53 -0
  36. package/css/theme/04-base.css +226 -0
  37. package/css/theme/05-components.css +546 -0
  38. package/css/theme/06-utilities.css +40 -0
  39. package/css/theme/07-ereader.css +245 -0
  40. package/css/theme/08-epub.css +865 -0
  41. package/css/theme/09-back-matter.css +47 -0
  42. package/css/theme/10-dark.css +302 -0
  43. package/css/theme/11-print.css +1229 -0
  44. package/css/theme.css +38 -0
  45. package/defaults/book.config.yml +318 -0
  46. package/defaults/pandoc-base.yml +19 -0
  47. package/defaults/pandoc-docx.yml +6 -0
  48. package/defaults/pandoc-epub-fonts.yml +6 -0
  49. package/defaults/pandoc-epub.yml +15 -0
  50. package/defaults/pandoc-kindle.yml +6 -0
  51. package/emoji/1f382.png +0 -0
  52. package/emoji/1f389.png +0 -0
  53. package/emoji/1f440.png +0 -0
  54. package/emoji/1f44d.png +0 -0
  55. package/emoji/1f44f.png +0 -0
  56. package/emoji/1f454.png +0 -0
  57. package/emoji/1f4a1.png +0 -0
  58. package/emoji/1f4bb.png +0 -0
  59. package/emoji/1f602.png +0 -0
  60. package/emoji/1f603.png +0 -0
  61. package/emoji/1f604.png +0 -0
  62. package/emoji/1f60a.png +0 -0
  63. package/emoji/1f60f.png +0 -0
  64. package/emoji/1f64c.png +0 -0
  65. package/emoji/1f64f.png +0 -0
  66. package/emoji/1f914.png +0 -0
  67. package/emoji/1f923.png +0 -0
  68. package/emoji/2714.png +0 -0
  69. package/emoji/274c.png +0 -0
  70. package/filters/add-div-titles.lua +196 -0
  71. package/filters/arc-notice.lua +36 -0
  72. package/filters/body-emoji-images.lua +48 -0
  73. package/filters/callout-classes.lua +40 -0
  74. package/filters/lib/book_kit.lua +110 -0
  75. package/filters/revision.lua +17 -0
  76. package/filters/strip-comments.lua +40 -0
  77. package/filters/strip-emoji-kindle.lua +134 -0
  78. package/fonts/LICENSE-source-code-pro.md +93 -0
  79. package/fonts/LICENSE-source-sans.md +93 -0
  80. package/fonts/LICENSE-source-serif.md +93 -0
  81. package/fonts/recipe.json +41 -0
  82. package/just/book-kit.just +229 -0
  83. package/lib/acronyms.js +47 -0
  84. package/lib/callouts.js +17 -0
  85. package/lib/config.js +211 -0
  86. package/lib/is-main.js +21 -0
  87. package/lib/lint.js +148 -0
  88. package/lib/pandoc.js +148 -0
  89. package/lib/paragraphs.js +173 -0
  90. package/lib/revision.js +35 -0
  91. package/lib/structure.js +39 -0
  92. package/lib/textlint-github-formatter.js +58 -0
  93. package/lib/validate.js +202 -0
  94. package/package.json +98 -4
  95. package/presets/business-nonfiction/CONVENTIONS.md +35 -0
  96. package/presets/business-nonfiction/preset.yml +128 -0
  97. package/profiles/pdfx-def.ps +42 -0
  98. package/requirements.txt +14 -0
  99. package/rules/no-ai-like-patterns.js +209 -0
  100. package/rules/no-assumptive.js +76 -0
  101. package/rules/no-bare-acronyms.js +129 -0
  102. package/rules/no-cliches.js +544 -0
  103. package/rules/no-cultural-idioms.js +422 -0
  104. package/rules/no-vague-intensifiers.js +94 -0
  105. package/templates/template-epub.html +79 -0
  106. package/templates/template.html +73 -0
  107. package/toolchain.json +42 -0
  108. package/validators/acronym-expansion.js +94 -0
  109. package/validators/bold-keywords.js +56 -0
  110. package/validators/callout-balance.js +74 -0
  111. package/validators/callout-divs.js +66 -0
  112. package/validators/callout-labels.js +102 -0
  113. package/validators/chapter-lengths.js +14 -0
  114. package/validators/chapter.js +57 -0
  115. package/validators/cross-references.js +45 -0
  116. package/validators/doubled-words.js +76 -0
  117. package/validators/duplicate-headings.js +52 -0
  118. package/validators/em-dashes.js +73 -0
  119. package/validators/escaped-smallcaps.js +46 -0
  120. package/validators/footnotes.js +78 -0
  121. package/validators/heading-hierarchy.js +58 -0
  122. package/validators/hypothetical-hooks.js +103 -0
  123. package/validators/index-integrity.js +28 -0
  124. package/validators/index.js +73 -0
  125. package/validators/list-punctuation.js +123 -0
  126. package/validators/min-opener-length.js +87 -0
  127. package/validators/no-bare-url-link-text.js +38 -0
  128. package/validators/no-todo-markers.js +49 -0
  129. package/validators/no-transition-todos.js +34 -0
  130. package/validators/number-range-dashes.js +72 -0
  131. package/validators/paragraph-length.js +62 -0
  132. package/validators/quote-punctuation.js +85 -0
  133. package/validators/section-intro-final-lead.js +90 -0
  134. package/validators/section-intro-has-lead.js +41 -0
  135. package/validators/section-intro-no-tldr.js +37 -0
  136. package/validators/section-intros.js +33 -0
  137. package/validators/sentence-starters.js +132 -0
  138. package/validators/smallcaps.js +67 -0
  139. package/validators/tldr-length.js +58 -0
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Stage a release: copy the built formats to version-named files and write
5
+ * their checksums.
6
+ *
7
+ * Downloads all named book.epub look alike, so a stale one is easy to upload
8
+ * by mistake. The copies carry the version, e.g. dist/release/book-v1.2.0.epub
9
+ * and book-kindle-v1.2.0.epub, beside a sha256sum-compatible checksums.txt.
10
+ * dist/ keeps its plain names for local builds and scripts.
11
+ *
12
+ * The version comes from paths.version, the same file the build stamps on the
13
+ * copyright page. Pass the release tag with --tag and a tag that disagrees
14
+ * with it fails, so a file can't be named for one version and stamped with
15
+ * another.
16
+ *
17
+ * Usage: book-kit release-files [--tag vX.Y.Z] [--out DIR]
18
+ * Prints the output directory.
19
+ */
20
+
21
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
22
+ import { createHash } from 'node:crypto';
23
+ import { basename, extname, join, resolve } from 'node:path';
24
+ import { loadConfig, outputFile, pathFor } from '../lib/config.js';
25
+ import { readVersion } from '../lib/revision.js';
26
+ import { isMain } from '../lib/is-main.js';
27
+
28
+ // The retail formats, in the order checksums.txt lists them. A book that
29
+ // doesn't build one (no Kindle, no PDF/X) just ships the rest.
30
+ export function releaseSources(config) {
31
+ return [
32
+ outputFile('.epub', config),
33
+ outputFile('-kindle.epub', config),
34
+ outputFile('.pdf', config),
35
+ outputFile(`${config.output.pdfx_suffix}.pdf`, config),
36
+ ];
37
+ }
38
+
39
+ // book-kindle.epub + v1.2.0 -> book-kindle-v1.2.0.epub
40
+ export function versionedName(file, label) {
41
+ const ext = extname(file);
42
+ return `${basename(file, ext)}-${label}${ext}`;
43
+ }
44
+
45
+ export function stageRelease({ config = loadConfig(), tag, out } = {}) {
46
+ const versionFile = pathFor('version', config);
47
+ if (!existsSync(versionFile)) {
48
+ throw new Error(`no ${versionFile}: a release needs the version the build stamps`);
49
+ }
50
+ const label = `v${readVersion(versionFile)}`;
51
+ if (tag && tag !== label) {
52
+ throw new Error(`tag ${tag} doesn't match ${basename(versionFile)} (${label}); bump it in the tagged commit`);
53
+ }
54
+
55
+ const sources = releaseSources(config).filter((f) => existsSync(f));
56
+ if (sources.length === 0) throw new Error('nothing to release: build the formats first');
57
+
58
+ const dir = out ? resolve(config.root, out) : join(pathFor('dist', config), 'release');
59
+ rmSync(dir, { recursive: true, force: true });
60
+ mkdirSync(dir, { recursive: true });
61
+
62
+ const lines = sources.map((src) => {
63
+ const name = versionedName(src, label);
64
+ copyFileSync(src, join(dir, name));
65
+ const hash = createHash('sha256').update(readFileSync(src)).digest('hex');
66
+ return `${hash} ${name}`;
67
+ });
68
+ writeFileSync(join(dir, 'checksums.txt'), `${lines.join('\n')}\n`);
69
+ return { dir, label, files: lines.map((l) => l.split(' ')[1]) };
70
+ }
71
+
72
+ function main(argv) {
73
+ const opt = (flag) => {
74
+ const i = argv.indexOf(flag);
75
+ return i === -1 ? undefined : argv[i + 1];
76
+ };
77
+ try {
78
+ const { dir, files } = stageRelease({ tag: opt('--tag') || undefined, out: opt('--out') });
79
+ for (const f of files) console.error(` ${f}`);
80
+ console.log(dir);
81
+ } catch (e) {
82
+ console.error(`book-kit release-files: ${e.message}`);
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ if (isMain(import.meta.url)) {
88
+ main(process.argv.slice(2));
89
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Move the print front matter (copyright page + dedication) ahead of the
5
+ * auto-generated table of contents.
6
+ *
7
+ * Why: copyright.md and dedication.md are the first `input-files` in
8
+ * index.yml, so pandoc renders them as the first body content — which lands
9
+ * *after* the template's auto-inserted TOC. That order (Title → TOC →
10
+ * Copyright → Dedication → body) is nonstandard for a printed book, where the
11
+ * copyright page belongs on the verso of the title page, before the TOC.
12
+ *
13
+ * The TOC is emitted by pandoc's template at a fixed position before `$body$`,
14
+ * so it can't be repositioned in Markdown. Instead, the `pdf` recipe renders
15
+ * standalone HTML, runs this script to splice the two front-matter blocks in
16
+ * front of the `<nav id="TOC">`, then hands the HTML to WeasyPrint — which
17
+ * recomputes the TOC page numbers against the new order.
18
+ *
19
+ * Print-only: the EPUB/Kindle builds (nav-first, by convention) are untouched.
20
+ *
21
+ * Usage: book-kit reorder-print-frontmatter <file.html>
22
+ */
23
+
24
+ import { readFileSync, writeFileSync } from 'node:fs';
25
+
26
+ const file = process.argv[2];
27
+ if (!file) {
28
+ console.error('Usage: book-kit reorder-print-frontmatter <file.html>');
29
+ process.exit(1);
30
+ }
31
+
32
+ /**
33
+ * Extract a balanced <div>…</div> block starting at the open tag that matches
34
+ * `needle`. Counts nested <div> opens/closes so inner containers don't end the
35
+ * match early.
36
+ * @param {string} html
37
+ * @param {string} needle - literal start of the opening tag, e.g. '<div id="copyright-page"'
38
+ * @returns {string|null} the full block, including its opening and closing
39
+ * tags, or null when the book has no such block
40
+ */
41
+ function extractBalancedDiv(html, needle) {
42
+ const start = html.indexOf(needle);
43
+ if (start === -1) return null;
44
+
45
+ const tag = /<\/?div\b[^>]*>/g;
46
+ tag.lastIndex = start;
47
+ let depth = 0;
48
+ let match;
49
+ while ((match = tag.exec(html)) !== null) {
50
+ depth += match[0].startsWith('</') ? -1 : 1;
51
+ if (depth === 0) return html.slice(start, match.index + match[0].length);
52
+ }
53
+ throw new Error(`Unbalanced <div> for anchor: ${needle}`);
54
+ }
55
+
56
+ let html = readFileSync(file, 'utf8');
57
+
58
+ // Either page is optional: a book without a dedication (or a copyright page)
59
+ // just has less to move.
60
+ const blocks = [
61
+ ['copyright page', extractBalancedDiv(html, '<div id="copyright-page"')],
62
+ ['dedication', extractBalancedDiv(html, '<div class="dedication"')],
63
+ ].filter(([, block]) => block !== null);
64
+
65
+ if (blocks.length === 0) {
66
+ console.log('No copyright page or dedication to move ahead of the table of contents.');
67
+ process.exit(0);
68
+ }
69
+
70
+ const tocAnchor = '<nav id="TOC"';
71
+ if (!html.includes(tocAnchor)) throw new Error(`Anchor not found: ${tocAnchor}`);
72
+
73
+ // Remove the blocks from their current (post-TOC) position…
74
+ for (const [, block] of blocks) html = html.replace(block, '');
75
+ // …and re-insert them, in reading order, immediately before the TOC.
76
+ html = html.replace(tocAnchor, `${blocks.map(([, block]) => block).join('\n')}\n${tocAnchor}`);
77
+
78
+ writeFileSync(file, html);
79
+ console.log(`Moved ${blocks.map(([name]) => name).join(' and ')} ahead of the table of contents.`);