nnw-theme 1.1.0 → 2.0.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/README.md CHANGED
@@ -16,15 +16,15 @@ Not affiliated with or endorsed by Ranchero Software or the NetNewsWire project.
16
16
  Run it with `npx`, from anywhere inside the theme repository. It needs Node 24 or newer.
17
17
 
18
18
  ```sh
19
- npx nnw-theme@1 init # once: name the theme and choose its permanent identifier
20
- npx nnw-theme@1 setup # once per machine: install the WebKit build checks use
21
- npx nnw-theme@1 preview # live gallery that reloads on save
22
- npx nnw-theme@1 check # the release gate: package, render, and test every case
19
+ npx nnw-theme@2 init # once: name the theme and choose its permanent identifier
20
+ npx nnw-theme@2 setup # once per machine: install the WebKit build checks use
21
+ npx nnw-theme@2 preview # live gallery that reloads on save
22
+ npx nnw-theme@2 check # the release gate: package, render, and test every case
23
23
  ```
24
24
 
25
- `@1` picks the newest 1.x release, so fixes arrive on their own and a breaking 2.0 never
25
+ `@2` picks the newest 2.x release, so fixes arrive on their own and a breaking 3.0 never
26
26
  does. Don't install it globally or add it to a theme repository. To use one exact
27
- version, name it: `npx nnw-theme@1.4.2 check`.
27
+ version, name it: `npx nnw-theme@2.0.0 check`.
28
28
 
29
29
  | Command | What it does |
30
30
  | --- | --- |
@@ -46,16 +46,16 @@ Every command takes `--help`.
46
46
  ## Shell completion
47
47
 
48
48
  `completion` prints a script that completes commands, options, and fixture names. It
49
- also defines an `nnw-theme` command that runs `npx --yes nnw-theme@1`, so you can type
49
+ also defines an `nnw-theme` command that runs `npx --yes nnw-theme@2`, so you can type
50
50
  `nnw-theme check`. The wrapper is skipped when a real `nnw-theme` is already installed.
51
51
 
52
52
  ```sh
53
53
  # fish
54
- npx nnw-theme@1 completion fish > ~/.config/fish/conf.d/nnw-theme.fish
54
+ npx nnw-theme@2 completion fish > ~/.config/fish/conf.d/nnw-theme.fish
55
55
  # zsh (~/.zshrc, after compinit)
56
- source <(npx --yes nnw-theme@1 completion zsh)
56
+ source <(npx --yes nnw-theme@2 completion zsh)
57
57
  # bash (~/.bashrc)
58
- source <(npx --yes nnw-theme@1 completion bash)
58
+ source <(npx --yes nnw-theme@2 completion bash)
59
59
  ```
60
60
 
61
61
  Fish loads a file rather than running npx at every shell start. Zsh and bash users can
@@ -64,11 +64,21 @@ Regenerate it when commands change.
64
64
 
65
65
  ## GitHub workflows
66
66
 
67
- Theme repositories call this repository's reusable workflows, pinned to `@v1`:
67
+ Theme repositories call this repository's reusable workflows, pinned to `@v2`:
68
68
  `theme-check.yml` (the release gate on every push), `theme-pages.yml` (publishes the
69
69
  checked gallery), `theme-release.yml` (manual release), and `theme-screenshot.yml`
70
70
  (refreshes the marketplace image). The template carries the ten-line callers.
71
71
 
72
+ ## Upgrading from 1.x
73
+
74
+ 2.0 fails `check` when `template.html` writes a macro in double brackets inside an HTML
75
+ comment. NetNewsWire substitutes it there too, so an article containing `-->` ends the
76
+ comment early and the rest shows as text; the 1.x starter template did this. Write the
77
+ names without brackets. Then replace the stub files `check` warns about (`AGENTS.md`,
78
+ the agent skill, and the four workflow callers) with the template's current copies,
79
+ which run `npx nnw-theme@2` and call the workflows at `@v2`. 1.x and `@v1` keep working
80
+ unchanged.
81
+
72
82
  ## Developing
73
83
 
74
84
  See [AGENTS.md](AGENTS.md). Report problems at
@@ -31,6 +31,7 @@ body = """
31
31
  <figure><img alt="Abstract green and cream test pattern" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='675' viewBox='0 0 1200 675'%3E%3Crect width='1200' height='675' fill='%23d8d2c6'/%3E%3Ccircle cx='380' cy='337' r='240' fill='%232f6655'/%3E%3Cpath d='M0 560L1200 180V675H0Z' fill='%23f7f5f0'/%3E%3C/svg%3E"><figcaption>A synthetic image checks wide-media scaling.</figcaption></figure>
32
32
  <p>The note marker uses common generated markup.<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup></p>
33
33
  <div class="footnotes"><ol><li id="fn:1"><p>Footnote content should remain readable in place and in NetNewsWire’s popover. <a href="#fnref:1" class="reversefootnote">↩</a></p></li></ol></div>
34
+ <!-- SC_OFF --><div class="md"><p>A comment-wrapped paragraph, as Reddit sends every post.</p></div><!-- SC_ON -->
34
35
  <h3>Final heading</h3>
35
36
  <p>End of the fixture.</p>
36
37
  """
@@ -10,7 +10,7 @@ checked on macOS and iPhone in light and dark. Names used by check scenarios
10
10
  (`large-text`, `article-javascript-off`) are reserved. Use `'''literal'''` strings for
11
11
  HTML bodies so they need no escaping.
12
12
 
13
- Prefer real articles for content a theme must handle. `npx nnw-theme@1 capture`
13
+ Prefer real articles for content a theme must handle. `npx nnw-theme@2 capture`
14
14
  prints the steps for saving the selected article from a NetNewsWire debug build with
15
15
  the `nnwdump` lldb command; it needs Xcode and a NetNewsWire clone, so the person
16
16
  usually runs it. Rendering swaps every image that would load over the network for a
@@ -3,8 +3,8 @@
3
3
  Read this before versioning, promoting the marketplace screenshot, changing GitHub
4
4
  metadata, or releasing.
5
5
 
6
- Run `npx nnw-theme@1 bump`, commit the new integer `Version`, and run
7
- `npx nnw-theme@1 check`. The bundle filename and `ThemeIdentifier` cannot change
6
+ Run `npx nnw-theme@2 bump`, commit the new integer `Version`, and run
7
+ `npx nnw-theme@2 check`. The bundle filename and `ThemeIdentifier` cannot change
8
8
  after the first release. Release tags may use any valid Git ref beginning with `v`;
9
9
  semantic tags such as `v1.0.0` are the suggested default and are independent of the
10
10
  plist version.
@@ -21,7 +21,7 @@ Marketplace participation is optional. Automatic discovery expects a public,
21
21
  non-archived, non-fork repository; the `netnewswire-theme` or `netnewswire` topic; a
22
22
  stable GitHub release; and the valid ZIP asset. A useful repository description and
23
23
  `screenshots/theme-preview.png` improve the card. Missing optional marketplace/card
24
- metadata warns but does not block publication. `npx nnw-theme@1 marketplace enable`
24
+ metadata warns but does not block publication. `npx nnw-theme@2 marketplace enable`
25
25
  adds the recommended topic through the author’s authenticated `gh` session.
26
26
 
27
27
  The screenshot workflow pushes a branch and opens a pull request for review, so the
@@ -1,8 +1,8 @@
1
1
  # Create NetNewsWire themes
2
2
 
3
3
  This guide is for people and agents building a theme with `nnw-theme`. Run the tool
4
- as `npx nnw-theme@1 <command>`; never install it globally or add it to the theme
5
- repository. Read another topic with `npx nnw-theme@1 guide <topic>`: `theme-format`,
4
+ as `npx nnw-theme@2 <command>`; never install it globally or add it to the theme
5
+ repository. Read another topic with `npx nnw-theme@2 guide <topic>`: `theme-format`,
6
6
  `fixtures`, `design-checklist`, or `publishing`.
7
7
 
8
8
  The upstream `dave-atx/netnewswire-theme-template` repository is a GitHub template.
@@ -11,7 +11,7 @@ upstream template or a fork. If they already have their own copy open, use it.
11
11
  Otherwise, help them create one from the template if you can, or ask them to create
12
12
  and open it; confirm before creating a GitHub repository. Then work on its single
13
13
  root-level `*.nnwtheme` bundle. If `.nnw-theme-uninitialized` exists, run the guided
14
- `npx nnw-theme@1 init` before design work; let the person approve the permanent
14
+ `npx nnw-theme@2 init` before design work; let the person approve the permanent
15
15
  identifier even when other identity details are already known.
16
16
 
17
17
  Collect or infer a compact design brief: mood, typography, color direction, and any
@@ -24,18 +24,18 @@ footnotes, add a fixture as the `fixtures` topic describes.
24
24
  Iterate with this loop:
25
25
 
26
26
  1. Make a focused theme edit.
27
- 2. Run `npx nnw-theme@1 render` for fast generated pages or
28
- `npx nnw-theme@1 screenshot` when a checked image is needed.
27
+ 2. Run `npx nnw-theme@2 render` for fast generated pages or
28
+ `npx nnw-theme@2 screenshot` when a checked image is needed.
29
29
  3. Inspect relevant light/dark and device previews under `build/preview/`.
30
30
  4. Explain what changed in visual terms and incorporate the person’s reaction.
31
31
 
32
32
  Use the `design-checklist` topic during final visual review. Always finish
33
- implementation with `npx nnw-theme@1 check`; do not substitute a Chromium result for
33
+ implementation with `npx nnw-theme@2 check`; do not substitute a Chromium result for
34
34
  the required WebKit gate. Give the person a concrete preview or screenshot path they
35
- can open. If `check` reports that WebKit is missing, run `npx nnw-theme@1 setup`.
35
+ can open. If `check` reports that WebKit is missing, run `npx nnw-theme@2 setup`.
36
36
 
37
37
  Treat the root bundle filename and `ThemeIdentifier` as permanent after the first
38
- release. Increase the integer `Version` with `npx nnw-theme@1 bump` for each release.
38
+ release. Increase the integer `Version` with `npx nnw-theme@2 bump` for each release.
39
39
  Read the `publishing` topic for publishing work. Local packaging is not publication.
40
40
  Obtain clear user confirmation immediately before triggering a GitHub release or
41
41
  another external mutation.
@@ -34,7 +34,8 @@ the size classes are inert on iOS, where the app never sets one. Do not reach fo
34
34
  uses it: no desktop WebKit implements that property, so every preview resolves the iOS
35
35
  branch as false and platform-conditional CSS is checked the wrong way round. Do not
36
36
  write macro names in double brackets inside comments either, because substitution
37
- rewrites those too. NetNewsWire's stylesheet also sets `font: -apple-system-body` at
37
+ rewrites those too: a body containing `-->`, as every Reddit post does, closes the
38
+ comment early and shows the rest of it as text. `check` fails on them. NetNewsWire's stylesheet also sets `font: -apple-system-body` at
38
39
  `:root`; a theme with its own typography should not, because that shorthand resets
39
40
  `font-family` and `line-height`.
40
41
 
@@ -2,21 +2,21 @@
2
2
  name: creating-nnw-themes
3
3
  description: Create, restyle, preview, test, package, or publish the NetNewsWire theme in this repository. Do not use for generic web design or NetNewsWire app development.
4
4
  ---
5
- <!-- nnw-theme-stub: skill v1 -->
5
+ <!-- nnw-theme-stub: skill v2 -->
6
6
 
7
7
  # Create NetNewsWire themes
8
8
 
9
- Read the full guide first: `npx nnw-theme@1 guide`. Details live in its topics
10
- (`npx nnw-theme@1 guide theme-format`, `fixtures`, `design-checklist`, `publishing`),
9
+ Read the full guide first: `npx nnw-theme@2 guide`. Details live in its topics
10
+ (`npx nnw-theme@2 guide theme-format`, `fixtures`, `design-checklist`, `publishing`),
11
11
  which update with the tool.
12
12
 
13
- 1. If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@1 init` and let the person
13
+ 1. If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@2 init` and let the person
14
14
  approve the permanent theme identifier.
15
15
  2. Collect a compact design brief: mood, typography, color, reading needs.
16
16
  3. Edit `stylesheet.css` first; change `template.html` only when structure requires it.
17
- 4. Run `npx nnw-theme@1 render` and inspect `build/preview/` in light and dark on each
17
+ 4. Run `npx nnw-theme@2 render` and inspect `build/preview/` in light and dark on each
18
18
  device; explain the change and take the person's reaction.
19
- 5. Finish with `npx nnw-theme@1 check` and give the person the preview path.
19
+ 5. Finish with `npx nnw-theme@2 check` and give the person the preview path.
20
20
 
21
21
  Never change the bundle name or `ThemeIdentifier` after the first release. Confirm
22
22
  with the person immediately before any release or other GitHub mutation.
@@ -1,4 +1,4 @@
1
- # nnw-theme-stub: check v1
1
+ # nnw-theme-stub: check v2
2
2
  # Runs the nnw-theme release gate: https://github.com/dave-atx/nnw-theme
3
3
  name: Theme checks
4
4
 
@@ -11,4 +11,4 @@ permissions:
11
11
 
12
12
  jobs:
13
13
  check:
14
- uses: dave-atx/nnw-theme/.github/workflows/theme-check.yml@v1
14
+ uses: dave-atx/nnw-theme/.github/workflows/theme-check.yml@v2
@@ -1,4 +1,4 @@
1
- # nnw-theme-stub: pages v1
1
+ # nnw-theme-stub: pages v2
2
2
  # Publishes the checked preview gallery when Pages builds from GitHub Actions.
3
3
  name: Publish preview pages
4
4
 
@@ -21,4 +21,4 @@ concurrency:
21
21
 
22
22
  jobs:
23
23
  pages:
24
- uses: dave-atx/nnw-theme/.github/workflows/theme-pages.yml@v1
24
+ uses: dave-atx/nnw-theme/.github/workflows/theme-pages.yml@v2
@@ -1,4 +1,4 @@
1
- # nnw-theme-stub: release v1
1
+ # nnw-theme-stub: release v2
2
2
  name: Publish theme
3
3
 
4
4
  on:
@@ -18,7 +18,7 @@ permissions:
18
18
 
19
19
  jobs:
20
20
  release:
21
- uses: dave-atx/nnw-theme/.github/workflows/theme-release.yml@v1
21
+ uses: dave-atx/nnw-theme/.github/workflows/theme-release.yml@v2
22
22
  with:
23
23
  tag: ${{ inputs.tag }}
24
24
  notes: ${{ inputs.notes }}
@@ -1,4 +1,4 @@
1
- # nnw-theme-stub: screenshot v1
1
+ # nnw-theme-stub: screenshot v2
2
2
  name: Refresh marketplace screenshot
3
3
 
4
4
  on:
@@ -21,7 +21,7 @@ permissions:
21
21
 
22
22
  jobs:
23
23
  screenshot:
24
- uses: dave-atx/nnw-theme/.github/workflows/theme-screenshot.yml@v1
24
+ uses: dave-atx/nnw-theme/.github/workflows/theme-screenshot.yml@v2
25
25
  with:
26
26
  fixture: ${{ inputs.fixture }}
27
27
  appearance: ${{ inputs.appearance }}
@@ -1,10 +1,10 @@
1
- <!-- nnw-theme-stub: agents v1 -->
1
+ <!-- nnw-theme-stub: agents v2 -->
2
2
  # Repository guidance
3
3
 
4
4
  This repository is one NetNewsWire theme: the single root `*.nnwtheme` bundle,
5
5
  its `fixtures/`, and `screenshots/`. The tooling is the `nnw-theme` npm package;
6
- run it as `npx nnw-theme@1 <command>`. Never install it globally and never add
7
- it to this repository. Read `npx nnw-theme@1 guide` before theme work.
6
+ run it as `npx nnw-theme@2 <command>`. Never install it globally and never add
7
+ it to this repository. Read `npx nnw-theme@2 guide` before theme work.
8
8
 
9
9
  If this repository is `dave-atx/netnewswire-theme-template` itself (check
10
10
  `git remote -v`), it is the GitHub template, not a theme: do not initialize,
@@ -13,13 +13,13 @@ fork, or design in it. Help the user create their own repository from it with
13
13
  dave-atx/netnewswire-theme-template --clone`, confirming before you create it,
14
14
  then work in that copy.
15
15
 
16
- If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@1 init` first and let
16
+ If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@2 init` first and let
17
17
  the user confirm the permanent theme identifier. Prefer CSS changes; change
18
18
  `template.html` only when the requested structure or behavior requires it.
19
- Finish theme work with `npx nnw-theme@1 check` and report the preview path.
19
+ Finish theme work with `npx nnw-theme@2 check` and report the preview path.
20
20
 
21
21
  Do not change `ThemeIdentifier` or the `.nnwtheme` bundle name after the first
22
- release. Increase the plist `Version` (`npx nnw-theme@1 bump`) before every
22
+ release. Increase the plist `Version` (`npx nnw-theme@2 bump`) before every
23
23
  release.
24
24
 
25
25
  If the tooling misbehaves, do not work around it here: report it at
package/dist/browser.js CHANGED
@@ -11,7 +11,7 @@ import { packagePath, ThemeError } from "./project.js";
11
11
  import { pyReprList } from "./pyformat.js";
12
12
  const FOOTNOTE_CHECK = readFileSync(packagePath("assets", "footnotes.js"), "utf8");
13
13
  const LIVE_RELOAD = readFileSync(packagePath("assets", "live-reload.js"), "utf8");
14
- const NOT_INSTALLED = "WebKit is not installed; run `npx nnw-theme@1 setup`";
14
+ const NOT_INSTALLED = "WebKit is not installed; run `npx nnw-theme@2 setup`";
15
15
  const CONTENT_TYPES = {
16
16
  ".html": "text/html; charset=utf-8",
17
17
  ".png": "image/png",
@@ -18,7 +18,7 @@ export default function capture() {
18
18
  nnwdump ${output}
19
19
  continue
20
20
 
21
- 4. Preview it with \`npx nnw-theme@1 render my-article\`.
21
+ 4. Preview it with \`npx nnw-theme@2 render my-article\`.
22
22
 
23
23
  nnwdump embeds the feed's real icon; pass --no-icon to keep the generated one.
24
24
  The debugger's working directory is /, so give nnwdump an absolute path.
@@ -75,7 +75,7 @@ function templateRoot() {
75
75
  "clone it, and run init inside it. With the GitHub CLI:\n" +
76
76
  ` gh repo create my-theme --template ${TEMPLATE} --public --clone\n` +
77
77
  " cd my-theme\n" +
78
- " npx nnw-theme@1 init");
78
+ " npx nnw-theme@2 init");
79
79
  }
80
80
  }
81
81
  async function ask(value, flag, label, fallback) {
@@ -89,7 +89,7 @@ export default async function init({ values }) {
89
89
  const root = templateRoot();
90
90
  let theme = findTheme(root);
91
91
  if (!existsSync(join(root, PLACEHOLDER_MARKER))) {
92
- throw new ThemeError("this repository is already initialized; run `npx nnw-theme@1 setup` to install preview tools");
92
+ throw new ThemeError("this repository is already initialized; run `npx nnw-theme@2 setup` to install preview tools");
93
93
  }
94
94
  const flag = (name) => values[name];
95
95
  const prompting = !(flag("name") && flag("creator") && flag("homepage"));
@@ -143,7 +143,7 @@ export default async function init({ values }) {
143
143
  rmSync(join(root, "screenshots", "theme-preview.png"), { force: true });
144
144
  rmSync(join(root, PLACEHOLDER_MARKER));
145
145
  console.log(`Initialized ${name}.nnwtheme with identifier ${identifier}.`);
146
- console.log("Create a deliberate marketplace image with `npx nnw-theme@1 screenshot --promote`.");
146
+ console.log("Create a deliberate marketplace image with `npx nnw-theme@2 screenshot --promote`.");
147
147
  let marketplace = flag("marketplace");
148
148
  if (marketplace === undefined) {
149
149
  marketplace =
@@ -165,8 +165,8 @@ export default async function init({ values }) {
165
165
  }
166
166
  catch (error) {
167
167
  console.error(`Warning: initialization succeeded, but preview setup did not: ${error.message}\n` +
168
- "Finish preview setup later with `npx nnw-theme@1 setup`.");
168
+ "Finish preview setup later with `npx nnw-theme@2 setup`.");
169
169
  }
170
170
  }
171
- console.log(`Next: work on your design in ${relative(process.cwd(), theme) || "."}, then run \`npx nnw-theme@1 preview\`.`);
171
+ console.log(`Next: work on your design in ${relative(process.cwd(), theme) || "."}, then run \`npx nnw-theme@2 preview\`.`);
172
172
  }
@@ -5,13 +5,13 @@ export function marketplaceEnable() {
5
5
  encoding: "utf8",
6
6
  });
7
7
  if (result.error) {
8
- console.error("Warning: GitHub CLI is not installed. Run `npx nnw-theme@1 marketplace enable` " +
8
+ console.error("Warning: GitHub CLI is not installed. Run `npx nnw-theme@2 marketplace enable` " +
9
9
  "after installing and authenticating gh.");
10
10
  return false;
11
11
  }
12
12
  if (result.status) {
13
13
  console.error("Warning: could not add the marketplace topic. " +
14
- `Run \`npx nnw-theme@1 marketplace enable\` later.\n${(result.stderr || result.stdout).trim()}`);
14
+ `Run \`npx nnw-theme@2 marketplace enable\` later.\n${(result.stderr || result.stdout).trim()}`);
15
15
  return false;
16
16
  }
17
17
  console.log("Added the netnewswire-theme GitHub topic.");
@@ -1,6 +1,6 @@
1
1
  import { ThemeError } from "../project.js";
2
2
  export default function update() {
3
- throw new ThemeError("update was removed: the tooling now runs from npm with `npx nnw-theme@1`, so " +
3
+ throw new ThemeError("update was removed: the tooling now runs from npm with `npx nnw-theme@2`, so " +
4
4
  "fixes arrive without changing this repository. Delete src/, tests/, " +
5
5
  "pyproject.toml, uv.lock, and .python-version, and replace the stubs `check` " +
6
6
  "warns about.");
package/dist/commands.js CHANGED
@@ -145,7 +145,7 @@ export const COMMANDS = [
145
145
  {
146
146
  name: "completion",
147
147
  summary: "print shell completion and an npx wrapper for fish, zsh, or bash",
148
- description: "Print a script that defines an nnw-theme command running `npx --yes nnw-theme@1` " +
148
+ description: "Print a script that defines an nnw-theme command running `npx --yes nnw-theme@2` " +
149
149
  "(skipped when nnw-theme is already installed) and completes its commands, " +
150
150
  "options, and fixture names.",
151
151
  options: [],
@@ -1,9 +1,9 @@
1
1
  // Shell completion scripts, generated from the same command spec the parser uses.
2
- // Each script also defines an nnw-theme function that runs `npx --yes nnw-theme@1`
2
+ // Each script also defines an nnw-theme function that runs `npx --yes nnw-theme@2`
3
3
  // unless a real nnw-theme is already on PATH. The scripts never call npx to complete,
4
4
  // so completion is instant.
5
5
  import { BUILT_IN_FIXTURE_NAMES, COMMANDS, PROGRAM, } from "./commands.js";
6
- const RUN = "npx --yes nnw-theme@1";
6
+ const RUN = "npx --yes nnw-theme@2";
7
7
  const visible = () => COMMANDS.filter((command) => !command.hidden);
8
8
  function flags(option) {
9
9
  return option.negatable ? [`--${option.name}`, `--no-${option.name}`] : [`--${option.name}`];
@@ -26,7 +26,7 @@ function fishQuote(text) {
26
26
  }
27
27
  export function fish() {
28
28
  const lines = [
29
- "# nnw-theme completion for fish. Install: npx nnw-theme@1 completion fish > ~/.config/fish/conf.d/nnw-theme.fish",
29
+ "# nnw-theme completion for fish. Install: npx nnw-theme@2 completion fish > ~/.config/fish/conf.d/nnw-theme.fish",
30
30
  `if not command -q ${PROGRAM}`,
31
31
  ` function ${PROGRAM} --description 'Run nnw-theme through npx'`,
32
32
  ` ${RUN} $argv`,
@@ -101,7 +101,7 @@ export function bash() {
101
101
  positional = `COMPREPLY=($(compgen -W ${single(words.join(" "))} -- "$cur"))`;
102
102
  cases.push(` ${command.name})`, ...(valueCases.length ? [' case "$prev" in', ...valueCases, " esac"] : []), ' if [[ "$cur" == -* ]]; then', ` COMPREPLY=($(compgen -W ${single(allFlags(command).join(" "))} -- "$cur"))`, " else", ` ${positional}`, " fi", " ;;");
103
103
  }
104
- return `# nnw-theme completion for bash. Install: source <(npx --yes nnw-theme@1 completion bash)
104
+ return `# nnw-theme completion for bash. Install: source <(npx --yes nnw-theme@2 completion bash)
105
105
  if ! type -P ${PROGRAM} >/dev/null 2>&1; then
106
106
  ${PROGRAM}() { ${RUN} "$@"; }
107
107
  fi
@@ -163,7 +163,7 @@ export function zsh() {
163
163
  specs.push(`'1:${command.positionals?.name ?? "command"}:(${words.join(" ")})'`);
164
164
  cases.push(` ${command.name}) _arguments -s ${specs.join(" ")} ;;`);
165
165
  }
166
- return `# nnw-theme completion for zsh. Install: source <(npx --yes nnw-theme@1 completion zsh)
166
+ return `# nnw-theme completion for zsh. Install: source <(npx --yes nnw-theme@2 completion zsh)
167
167
  if (( ! $+commands[${PROGRAM}] )); then
168
168
  ${PROGRAM}() { ${RUN} "$@"; }
169
169
  fi
package/dist/main.js CHANGED
@@ -78,7 +78,7 @@ export function mainHelp() {
78
78
  ]),
79
79
  "",
80
80
  `Run \`${PROGRAM} COMMAND --help\` for a command's options.`,
81
- `Run it through npx: \`npx nnw-theme@1 COMMAND\`.`,
81
+ `Run it through npx: \`npx nnw-theme@2 COMMAND\`.`,
82
82
  "",
83
83
  ].join("\n");
84
84
  }
package/dist/render.js CHANGED
@@ -81,7 +81,7 @@ const SCENARIOS = [
81
81
  "Stress test",
82
82
  "The kitchen-sink fixture: a very long headline, byline, and feed name; inline " +
83
83
  "formatting; nested lists; an unbroken identifier that exposes horizontal " +
84
- "overflow; a quotation, code block, table, figure, and footnote.",
84
+ "overflow; a quotation, code block, table, figure, footnote, and HTML comments in the body.",
85
85
  ],
86
86
  [
87
87
  "large-text",
@@ -437,7 +437,7 @@ export function writeGallery(site, themeName, targets, results = null) {
437
437
  if (results === null) {
438
438
  summary =
439
439
  `<p class=summary>${targets.length} cases, not checked yet. Run ` +
440
- "<code>npx nnw-theme@1 check</code> to verify them in WebKit.</p>";
440
+ "<code>npx nnw-theme@2 check</code> to verify them in WebKit.</p>";
441
441
  }
442
442
  else {
443
443
  const failed = targets.filter((target) => results[target.slug]?.length);
package/dist/stubs.js CHANGED
@@ -4,12 +4,12 @@ import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  export const TEMPLATE_BLOB = "https://github.com/dave-atx/netnewswire-theme-template/blob/main";
6
6
  export const STUBS = [
7
- { path: "AGENTS.md", name: "agents", version: 1 },
8
- { path: ".agents/skills/creating-nnw-themes/SKILL.md", name: "skill", version: 1 },
9
- { path: ".github/workflows/check.yml", name: "check", version: 1 },
10
- { path: ".github/workflows/pages.yml", name: "pages", version: 1 },
11
- { path: ".github/workflows/release.yml", name: "release", version: 1 },
12
- { path: ".github/workflows/screenshot.yml", name: "screenshot", version: 1 },
7
+ { path: "AGENTS.md", name: "agents", version: 2 },
8
+ { path: ".agents/skills/creating-nnw-themes/SKILL.md", name: "skill", version: 2 },
9
+ { path: ".github/workflows/check.yml", name: "check", version: 2 },
10
+ { path: ".github/workflows/pages.yml", name: "pages", version: 2 },
11
+ { path: ".github/workflows/release.yml", name: "release", version: 2 },
12
+ { path: ".github/workflows/screenshot.yml", name: "screenshot", version: 2 },
13
13
  ];
14
14
  const MARKER = /nnw-theme-stub:\s*([a-z-]+)\s+v(\d+)/;
15
15
  /** The marker in a stub file: its first line, or the first after YAML front matter. */
package/dist/validate.js CHANGED
@@ -20,6 +20,7 @@ const SAFE_BUNDLE_NAME = /^[^/\\\0]+$/;
20
20
  const OPTIONAL_THEME_FILE = /^(?:LICENSE|NOTICE)(?:\.[A-Za-z0-9-]+)?$/;
21
21
  const S_IFMT = 0o170000;
22
22
  const S_IFLNK = 0o120000;
23
+ const MACRO = /\[\[[A-Za-z0-9_-]+\]\]/g;
23
24
  export class ValidationReport {
24
25
  errors = [];
25
26
  warnings = [];
@@ -52,6 +53,15 @@ function resourceReferences(template) {
52
53
  parser.end(template);
53
54
  return references;
54
55
  }
56
+ /** Macros written inside HTML comments, which NetNewsWire substitutes like any other. */
57
+ function commentedMacros(template) {
58
+ const found = new Set();
59
+ for (const comment of template.matchAll(/<!--[\s\S]*?-->/g)) {
60
+ for (const macro of comment[0].matchAll(MACRO))
61
+ found.add(macro[0]);
62
+ }
63
+ return [...found];
64
+ }
55
65
  function isRemote(value) {
56
66
  return value.startsWith("//") || ["http", "https"].includes(urlsplit(value).scheme);
57
67
  }
@@ -113,7 +123,7 @@ export function validateSource(theme, { allowRemoteMedia = false } = {}) {
113
123
  report.errors.push("theme bundle name contains path-unsafe characters");
114
124
  }
115
125
  if (existsSync(join(dirname(theme), PLACEHOLDER_MARKER))) {
116
- report.errors.push("run `npx nnw-theme@1 init` before packaging this theme");
126
+ report.errors.push("run `npx nnw-theme@2 init` before packaging this theme");
117
127
  }
118
128
  const names = readdirSync(theme);
119
129
  for (const required of REQUIRED_THEME_FILES) {
@@ -144,6 +154,14 @@ export function validateSource(theme, { allowRemoteMedia = false } = {}) {
144
154
  if (!template.includes("[[")) {
145
155
  report.warnings.push("template.html contains no NetNewsWire macros");
146
156
  }
157
+ const commented = commentedMacros(template);
158
+ if (commented.length) {
159
+ report.errors.push(`template.html has macros inside an HTML comment (${commented[0]}` +
160
+ `${commented.length > 1 ? ` and ${commented.length - 1} more` : ""}). ` +
161
+ "NetNewsWire substitutes them there too, so an article containing --> " +
162
+ "ends the comment early and the rest shows as text; " +
163
+ "write macro names without double brackets");
164
+ }
147
165
  const references = resourceReferences(template);
148
166
  if (/<style\b[^>]*>[\s\S]*?@import\s/i.test(template)) {
149
167
  report.errors.push("CSS @import is not allowed in template.html");
package/lldb/nnwdump.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Stopped at the final `return d` of ArticleRenderer.articleSubstitutions(), this
4
4
  reads the substitution dictionary NetNewsWire built for the selected article and
5
- writes it as a TOML fixture. `npx nnw-theme@1 capture` prints the setup steps.
5
+ writes it as a TOML fixture. `npx nnw-theme@2 capture` prints the setup steps.
6
6
 
7
7
  Values cross the debugger boundary base64-encoded, so nothing is truncated or
8
8
  mis-escaped. HTML values are written as '''literal''' strings, so fixtures stay
@@ -30,7 +30,7 @@ def resolve_output(argument: str) -> Path:
30
30
  """Relative paths resolve against the working directory.
31
31
 
32
32
  This file ships in the nnw-theme package, not the theme repository, and lldb's
33
- working directory under Xcode is /, so `npx nnw-theme@1 capture` prints an absolute
33
+ working directory under Xcode is /, so `npx nnw-theme@2 capture` prints an absolute
34
34
  output path to pass instead.
35
35
  """
36
36
  path = Path(argument).expanduser()
@@ -60,7 +60,7 @@ def fixture_text(pairs: list[tuple[str, str]], name: str) -> str:
60
60
  ordered = sorted(pairs, key=lambda pair: (pair[0] in _HTML_KEYS, pair[0]))
61
61
  header = (
62
62
  "# NetNewsWire article captured with nnwdump.\n"
63
- f"# Preview with: npx nnw-theme@1 render {name}\n\n"
63
+ f"# Preview with: npx nnw-theme@2 render {name}\n\n"
64
64
  )
65
65
  return header + "\n".join(toml_line(key, value) for key, value in ordered) + "\n"
66
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nnw-theme",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Create, preview, check, and publish a NetNewsWire theme.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dave Marquard",