From 1da688ee91bc0178322e22f77a1030f5066f329f Mon Sep 17 00:00:00 2001 From: Anthony Fitzpatrick <11820350+anthonyfitzpatrick@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:46:01 +0200 Subject: [PATCH] Bundle logo and attest release artifacts --- .github/workflows/release.yml | 44 +++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- DEVELOPER_GUIDE.md | 4 ++-- MANUAL_TESTING.md | 2 +- README.md | 4 ++-- RELEASE_NOTES.md | 5 ++-- RELEASE_READINESS.md | 6 +++-- SECURITY.md | 3 +-- SUPPORT.md | 2 +- USER_GUIDE.md | 8 +++---- esbuild.config.mjs | 2 +- esbuild.test.mjs | 2 +- scripts/package.mjs | 11 ++++++--- src/assets.d.ts | 6 ++--- src/ui.ts | 8 +++++-- tests/run.ts | 6 ++--- 16 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9b3266f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: Release + +on: + release: + types: [published] + +permissions: + id-token: write + attestations: write + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Check out release tag + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + ref: ${{ github.event.release.tag_name }} + - name: Set up Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + cache: npm + - name: Install dependencies + run: npm ci + - name: Verify release tag + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: test "$(node -p "require('./manifest.json').version")" = "$RELEASE_TAG" + - name: Build release assets + run: npm run build + - name: Attest runtime assets + uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 + with: + subject-path: | + main.js + manifest.json + styles.css + - name: Upload release assets + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$RELEASE_TAG" main.js manifest.json styles.css --clobber diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 462a92b..0087070 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,4 +89,4 @@ Keep the PR focused. Address review comments with new commits unless the maintai ## Releases -A release tag must exactly equal the version in `manifest.json`, without a `v` prefix. Attach `main.js`, `manifest.json`, `styles.css`, and `logo.svg` individually. The optional ZIP must contain exactly those four files. Follow [RELEASE_READINESS.md](RELEASE_READINESS.md) and preserve every unchecked manual gate in [MANUAL_TESTING.md](MANUAL_TESTING.md) until it is actually completed. +A release tag must exactly equal the version in `manifest.json`, without a `v` prefix. Attach `main.js`, `manifest.json`, and `styles.css` individually. The optional ZIP must contain exactly those three files; `logo.svg` remains repository branding and is bundled into `main.js` for runtime display. Follow [RELEASE_READINESS.md](RELEASE_READINESS.md) and preserve every unchecked manual gate in [MANUAL_TESTING.md](MANUAL_TESTING.md) until it is actually completed. diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 440e303..7f416cc 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -229,7 +229,7 @@ If a change introduces caching, document ownership and invalidation. Never cache - Scope CSS to plugin roots and retain theme variables, focus-visible, reduced-motion, and high-contrast behavior. - Keep UI sentence case and accessible. - Keep `manifest.json` identity and minimum version stable unless the product release explicitly changes them. -- Release exactly `main.js`, `manifest.json`, `styles.css`, and `logo.svg`. +- Release exactly `main.js`, `manifest.json`, and `styles.css`; keep `logo.svg` only as the repository's editable branding source. ## CI and release process @@ -242,7 +242,7 @@ npm run package npm run package:validate ``` -The ZIP is optional; attach `main.js`, `manifest.json`, `styles.css`, and `logo.svg` individually to the GitHub release. The tag must exactly match `manifest.json` without a `v` prefix. Obsidian's Community Plugins installer downloads only its recognised runtime files; the logo remains a companion GitHub/manual-install asset. Do not package tests, fixtures, source, diagnostics, data files, or inspection artifacts. +The ZIP is optional; attach `main.js`, `manifest.json`, and `styles.css` individually to the GitHub release. The tag must exactly match `manifest.json` without a `v` prefix. The logo artwork is bundled into `main.js`; `logo.svg` remains in the repository for branding and must not be packaged. Do not package tests, fixtures, source, diagnostics, data files, or inspection artifacts. ## Common mistakes diff --git a/MANUAL_TESTING.md b/MANUAL_TESTING.md index 5edd017..a1a7241 100644 --- a/MANUAL_TESTING.md +++ b/MANUAL_TESTING.md @@ -4,7 +4,7 @@ All items are intentionally unchecked. Record date, tester, Obsidian version, op ## Installation and workflow -- [ ] Install only `main.js`, `manifest.json`, `styles.css`, and `logo.svg` in a clean Obsidian vault. +- [ ] Install only `main.js`, `manifest.json`, and `styles.css` in a clean Obsidian vault; confirm the settings page still displays the logo. - [ ] Upgrade an existing vault from the prior plugin version; confirm profiles, history, and formatting choices survive. - [ ] Load malformed persisted settings and confirm bounded, actionable recovery. - [ ] Right-click a folder and confirm the exact root opens in **Manuscript → Contents → Create file**. diff --git a/README.md b/README.md index c5dc17a..ac9826a 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ Manuscript Compiler is not yet listed in Obsidian's Community Plugins catalogue. ### Manual Installation -Download `main.js`, `manifest.json`, `styles.css`, and `logo.svg` from the same [GitHub release](https://github.com/anthonyfitzpatrick/manuscript-compiler/releases). Place them directly in `/.obsidian/plugins/manuscript-compiler/`, reload Obsidian, and enable **Manuscript Compiler** under Community Plugins. +Download `main.js`, `manifest.json`, and `styles.css` from the same [GitHub release](https://github.com/anthonyfitzpatrick/manuscript-compiler/releases). Place them directly in `/.obsidian/plugins/manuscript-compiler/`, reload Obsidian, and enable **Manuscript Compiler** under Community Plugins. ## Known Limitations @@ -229,7 +229,7 @@ npm audit git diff --check ``` -The release archive is `release/manuscript-compiler-0.10.0.zip` and contains exactly `main.js`, `manifest.json`, `styles.css`, and `logo.svg`. Obsidian's Community Plugins installer fetches its three recognised runtime files; `logo.svg` is also published for manual installs and release branding. +The release archive is `release/manuscript-compiler-0.10.0.zip` and contains exactly `main.js`, `manifest.json`, and `styles.css`. The editable `logo.svg` remains in the repository for README, GitHub, documentation, website, and social branding; its artwork is compiled into `main.js` and it is not a runtime or release-package file. Automated structural validation is not a substitute for opening outputs in Word, Vellum, LibreOffice, multiple EPUB readers, text editors, and browsers. See the [Manual Release Checklist](MANUAL_TESTING.md). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0a1b09a..8976d6f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -101,12 +101,11 @@ The User Guide covers installation, manuscript organisation, the complete three- Until Manuscript Compiler is accepted into the Obsidian Community Plugins directory, install it manually from this GitHub Release. -Download all four release assets: +Download all three runtime release assets: - `main.js` - `manifest.json` - `styles.css` -- `logo.svg` Create the following folder inside the vault if it does not already exist: @@ -114,7 +113,7 @@ Create the following folder inside the vault if it does not already exist: /.obsidian/plugins/manuscript-compiler/ ``` -Place the four files directly inside that folder. Do not mix files from different releases. +Place the three files directly inside that folder. Do not mix files from different releases. The settings logo is already bundled into `main.js`; the repository's `logo.svg` is branding source artwork and is not required for installation. Restart Obsidian or reload Community Plugins, then open **Settings → Community plugins** and enable **Manuscript Compiler**. diff --git a/RELEASE_READINESS.md b/RELEASE_READINESS.md index de489a2..06c7073 100644 --- a/RELEASE_READINESS.md +++ b/RELEASE_READINESS.md @@ -38,9 +38,11 @@ Run and record current output before publishing: - `npm audit` - `git diff --check` -The release archive must be `release/manuscript-compiler-0.10.0.zip` and contain exactly `main.js`, `manifest.json`, `styles.css`, and `logo.svg`. +The release archive must be `release/manuscript-compiler-0.10.0.zip` and contain exactly `main.js`, `manifest.json`, and `styles.css`. -Attach those four files individually to the GitHub release. The release tag must be exactly `0.10.0`, without a `v` prefix; the ZIP is optional and is not an installation dependency. Obsidian's Community Plugins installer uses the three recognised runtime assets, while GitHub and manual-install users also receive the standalone logo. +The three runtime assets are `main.js`, `manifest.json`, and `styles.css`. GitHub Artifact Attestations cover those files. The release tag must be exactly `0.10.0`, without a `v` prefix; the ZIP is optional and is not an installation dependency. + +`logo.svg` remains the master editable artwork in the repository for branding and documentation. The build bundles its SVG text into `main.js`, so neither the release package nor an installed plugin needs a separate logo file. ## Runtime dependencies diff --git a/SECURITY.md b/SECURITY.md index 260a8ca..5718eed 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -45,9 +45,8 @@ Official release assets are exactly: - `main.js` - `manifest.json` - `styles.css` -- `logo.svg` -The optional release ZIP contains only those four files. The package validator rejects active content and external references in the standalone SVG. Use assets from one release tag and verify that the tag exactly matches the version in `manifest.json`. Dependency changes require lockfile review, licence review, audit, updated third-party notices, and explicit approval. Runtime dependencies must not be introduced as convenience wrappers around platform, export, or network behavior. +The optional release ZIP contains only those three files. The repository's `logo.svg` is validated as branding source and compiled into `main.js`; it is not read from disk at runtime or included in the release package. Use assets from one release tag and verify that the tag exactly matches the version in `manifest.json`. Dependency changes require lockfile review, licence review, audit, updated third-party notices, and explicit approval. Runtime dependencies must not be introduced as convenience wrappers around platform, export, or network behavior. ## Supported versions diff --git a/SUPPORT.md b/SUPPORT.md index f1b567d..dd9d236 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -5,7 +5,7 @@ Manuscript Compiler is independently maintained. The documentation is the fastes ## Before requesting help 1. Read the [User Guide](USER_GUIDE.md), especially its troubleshooting and known-limitations sections. -2. Confirm the installed `main.js`, `manifest.json`, `styles.css`, and `logo.svg` came from the same release. +2. Confirm the installed `main.js`, `manifest.json`, and `styles.css` came from the same release. 3. Reproduce the problem with a small synthetic manuscript where possible. 4. Remove manuscript prose, private metadata, absolute vault paths, account details, and access tokens from anything you share. 5. Search existing [issues](https://github.com/anthonyfitzpatrick/manuscript-compiler/issues) and [Discussions](https://github.com/anthonyfitzpatrick/manuscript-compiler/discussions). diff --git a/USER_GUIDE.md b/USER_GUIDE.md index b1d1c4a..00e05c0 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -30,21 +30,21 @@ For a first export, work through all three stages without skipping the Contents Until Manuscript Compiler is available through Obsidian's Community Plugins catalogue: -1. Download `main.js`, `manifest.json`, `styles.css`, and `logo.svg` from a release whose tag exactly matches the manifest version. +1. Download `main.js`, `manifest.json`, and `styles.css` from a release whose tag exactly matches the manifest version. 2. Create `/.obsidian/plugins/manuscript-compiler/` if it does not exist. -3. Put only those four files in that folder. +3. Put only those three files in that folder. The settings logo is already bundled into `main.js`. 4. Restart Obsidian or reload community plugins. 5. Open **Settings → Community plugins** and enable **Manuscript Compiler**. The plugin is mobile-safe, but the exact save/share experience depends on the mobile operating system and Obsidian host. -After enabling the plugin, confirm the installation before working with a real manuscript. Open the command palette and search for Manuscript Compiler, or right-click a test folder in File Explorer and confirm that **Compile manuscript from this folder** appears. If the command is missing, verify that the four release files are directly inside the plugin folder rather than inside an additional nested directory. +After enabling the plugin, confirm the installation before working with a real manuscript. Open the command palette and search for Manuscript Compiler, or right-click a test folder in File Explorer and confirm that **Compile manuscript from this folder** appears. If the command is missing, verify that the three release files are directly inside the plugin folder rather than inside an additional nested directory. ## Updating 1. Close any open Manuscript Compiler window. 2. Back up the vault using the same process used for other important Obsidian data. -3. Replace `main.js`, `manifest.json`, `styles.css`, and `logo.svg` with all four files from the same release. +3. Replace `main.js`, `manifest.json`, and `styles.css` with all three files from the same release. 4. Restart Obsidian. 5. Open the plugin once and confirm existing profiles and formatting choices remain available. diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 54e5778..abb5508 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -16,7 +16,7 @@ const context = await esbuild.context({ bundle: true, external: ["obsidian", "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr", ...builtinModules, ...builtinModules.map((module) => `node:${module}`)], format: "cjs", - loader: { ".svg": "dataurl" }, + loader: { ".svg": "text" }, target: "es2018", logLevel: "info", sourcemap: production ? false : "inline", diff --git a/esbuild.test.mjs b/esbuild.test.mjs index 2ade1ee..2bdd803 100644 --- a/esbuild.test.mjs +++ b/esbuild.test.mjs @@ -1,4 +1,4 @@ import esbuild from "esbuild"; -await esbuild.build({ entryPoints: ["tests/run.ts"], bundle: true, platform: "node", format: "esm", target: "node18", outfile: ".test-build/run.mjs", alias: { obsidian: "./tests/obsidian-stub.ts" }, loader: { ".svg": "dataurl" }, logLevel: "warning" }); +await esbuild.build({ entryPoints: ["tests/run.ts"], bundle: true, platform: "node", format: "esm", target: "node18", outfile: ".test-build/run.mjs", alias: { obsidian: "./tests/obsidian-stub.ts" }, loader: { ".svg": "text" }, logLevel: "warning" }); await import("./.test-build/run.mjs"); diff --git a/scripts/package.mjs b/scripts/package.mjs index 41079df..2126cd7 100644 --- a/scripts/package.mjs +++ b/scripts/package.mjs @@ -2,7 +2,8 @@ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises"; import path from "node:path"; import { unzipSync, zipSync } from "fflate"; -const required = ["main.js", "manifest.json", "styles.css", "logo.svg"]; +const required = ["main.js", "manifest.json", "styles.css"]; +const repositoryLogo = "logo.svg"; const allowedManifestKeys = new Set(["id", "name", "version", "minAppVersion", "description", "author", "authorUrl", "fundingUrl", "isDesktopOnly"]); const semverPattern = /^\d+\.\d+\.\d+$/; const packageJson = await jsonFile("package.json"); @@ -35,13 +36,17 @@ async function validateAssets() { const info = await stat(name); assert(info.isFile() && info.size > 0, `Required release asset is missing or empty: ${name}`); } + const logoInfo = await stat(repositoryLogo); + assert(logoInfo.isFile() && logoInfo.size > 0, `Repository branding asset is missing or empty: ${repositoryLogo}`); const bundle = await readFile("main.js", "utf8"); - const logo = await readFile("logo.svg", "utf8"); - const releaseText = `${bundle}\n${await readFile("manifest.json", "utf8")}\n${await readFile("styles.css", "utf8")}\n${logo}`; + const logo = await readFile(repositoryLogo, "utf8"); + const releaseText = `${bundle}\n${await readFile("manifest.json", "utf8")}\n${await readFile("styles.css", "utf8")}`; assert(!/sourceMappingURL|\.map(?:\s|$)/i.test(bundle), "Production bundle contains a source map reference."); assert(!/require\(["'](?:electron|(?:node:)?(?:fs|path|os|child_process))["']\)/.test(bundle), "Production bundle contains a prohibited desktop or Node import."); assert(!/\b(?:fetch|requestUrl|XMLHttpRequest|WebSocket|sendBeacon)\s*\(/.test(bundle), "Production bundle contains a prohibited network API."); assert(!/\b(?:eval|Function)\s*\(/.test(bundle), "Production bundle contains dynamic code execution."); + assert(!bundle.includes(repositoryLogo), "Production bundle contains a runtime logo file path."); + assert(bundle.includes("Semantic Tree") && bundle.includes("A hierarchy of parts, chapters and scenes inside a document."), "Production bundle is missing the bundled plugin logo."); assert(/ `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; +const buyMeACoffeeArtworkUrl = svgDataUrl(buyMeACoffeeArtwork); +const pluginLogoUrl = svgDataUrl(pluginLogo); + /** Reusable vault-folder picker for compatibility commands/settings. */ export class FolderSuggestModal extends FuzzySuggestModal { constructor(app: App, private readonly onChoose: (folder: TFolder) => void) { super(app); } @@ -151,7 +155,7 @@ export class ManuscriptCompilerSettingTab extends PluginSettingTab { } private renderSupportContent(panel: HTMLElement): void { const identity = panel.createDiv({ cls: "manuscript-support-identity" }); - identity.createEl("img", { cls: "manuscript-support-logo", attr: { src: pluginLogo, alt: "", "aria-hidden": "true" } }); + identity.createEl("img", { cls: "manuscript-support-logo", attr: { src: pluginLogoUrl, alt: "", "aria-hidden": "true" } }); const identityText = identity.createDiv({ cls: "manuscript-support-identity-text" }); identityText.createEl("strong", { cls: "manuscript-support-name", text: "Manuscript Compiler" }); identityText.createEl("p", { text: `Version ${this.plugin.manifest.version}` }); @@ -162,7 +166,7 @@ export class ManuscriptCompilerSettingTab extends PluginSettingTab { const button = new ButtonComponent(actions).setTooltip(action.label).setClass("manuscript-support-button"); if (action.label === "Buy me a coffee") { const icon = button.buttonEl.createSpan({ cls: "manuscript-support-bmc-icon", attr: { "aria-hidden": "true" } }); - icon.createEl("img", { attr: { src: buyMeACoffeeArtwork, alt: "" } }); + icon.createEl("img", { attr: { src: buyMeACoffeeArtworkUrl, alt: "" } }); } else { button.setIcon(action.icon); } diff --git a/tests/run.ts b/tests/run.ts index 841722a..9981217 100644 --- a/tests/run.ts +++ b/tests/run.ts @@ -385,10 +385,10 @@ test("advanced controls remain available and settings lead with Defaults", async }); test("settings end with an accessible responsive Support & Links panel", async () => { - const ui = await readFile(path.join("src", "ui.ts"), "utf8"); const css = await readFile("styles.css", "utf8"); + const ui = await readFile(path.join("src", "ui.ts"), "utf8"); const css = await readFile("styles.css", "utf8"); const build = await readFile("esbuild.config.mjs", "utf8"); const records = ui.indexOf('setName("Export records")'); const support = ui.indexOf("this.renderSupportPanel(container);"); assert.ok(records >= 0 && support > records, "support must render after every setting"); assert.match(ui, /container\.addClass\("manuscript-compiler-settings"\)/); assert.match(css, /\.manuscript-compiler-settings > \.setting-item:not\(\.setting-item-heading\)[\s\S]*padding-block: var\(--size-4-2\)/); assert.match(css, /\.manuscript-compiler-settings > details > \.setting-item/); - assert.match(ui, /const SUPPORT_SECTION_TITLE = "Support & Links"/); assert.match(ui, /import pluginLogo from "\.\.\/logo\.svg"/); assert.match(ui, /manuscript-support-logo/); assert.match(ui, /text: `Version \$\{this\.plugin\.manifest\.version\}`/); assert.equal((ui.match(/\{ label: /g) ?? []).length, 5); + assert.match(ui, /const SUPPORT_SECTION_TITLE = "Support & Links"/); assert.match(ui, /import pluginLogo from "\.\.\/logo\.svg"/); assert.match(build, /loader: \{ "\.svg": "text" \}/); assert.match(ui, /const pluginLogoUrl = svgDataUrl\(pluginLogo\)/); assert.match(ui, /src: pluginLogoUrl/); assert.doesNotMatch(ui, /readFile.*logo|getResourcePath.*logo|adapter.*logo/i); assert.match(ui, /manuscript-support-logo/); assert.match(ui, /text: `Version \$\{this\.plugin\.manifest\.version\}`/); assert.equal((ui.match(/\{ label: /g) ?? []).length, 5); for (const action of [ '{ label: "Report a bug", icon: "bug", url: "https://github.com/anthonyfitzpatrick/manuscript-compiler/issues/new?template=bug_report.yml" }', '{ label: "Feature request", icon: "lightbulb", url: "https://github.com/anthonyfitzpatrick/manuscript-compiler/issues/new?template=feature_request.yml" }', @@ -544,7 +544,7 @@ test("plugin CSS selectors are owned, theme-safe, and retain accessibility modes assert.doesNotMatch(css, /#[0-9a-f]{3,8}\b|!important/i); assert.match(css, /@media \(forced-colors: active\)/); assert.match(css, /@media \(prefers-reduced-motion: reduce\)/); assert.match(css, /:focus-visible/); }); -test("0.10.0 version metadata and release package allowlist are consistent", async () => { const manifest = JSON.parse(await readFile("manifest.json", "utf8")); const packageJson = JSON.parse(await readFile("package.json", "utf8")); const lock = JSON.parse(await readFile("package-lock.json", "utf8")); const versions = JSON.parse(await readFile("versions.json", "utf8")); const packaging = await readFile(path.join("scripts", "package.mjs"), "utf8"); assert.equal(manifest.version, "0.10.0"); assert.equal(packageJson.version, manifest.version); assert.equal(lock.version, manifest.version); assert.equal(lock.packages[""].version, manifest.version); assert.equal(versions[manifest.version], manifest.minAppVersion); assert.equal(manifest.authorUrl, "https://wolf359.app"); assert.equal(manifest.fundingUrl, "https://buymeacoffee.com/wolf359pressab"); assert.match(packaging, /const required = \["main\.js", "manifest\.json", "styles\.css", "logo\.svg"\]/); }); +test("0.10.0 version metadata, bundled branding, and release package allowlist are consistent", async () => { const manifest = JSON.parse(await readFile("manifest.json", "utf8")); const packageJson = JSON.parse(await readFile("package.json", "utf8")); const lock = JSON.parse(await readFile("package-lock.json", "utf8")); const versions = JSON.parse(await readFile("versions.json", "utf8")); const packaging = await readFile(path.join("scripts", "package.mjs"), "utf8"); const readme = await readFile("README.md", "utf8"); assert.equal(manifest.version, "0.10.0"); assert.equal(packageJson.version, manifest.version); assert.equal(lock.version, manifest.version); assert.equal(lock.packages[""].version, manifest.version); assert.equal(versions[manifest.version], manifest.minAppVersion); assert.equal(manifest.authorUrl, "https://wolf359.app"); assert.equal(manifest.fundingUrl, "https://buymeacoffee.com/wolf359pressab"); assert.match(packaging, /const required = \["main\.js", "manifest\.json", "styles\.css"\]/); assert.doesNotMatch(packaging, /const required = \[[^\]]*logo\.svg/); assert.match(packaging, /const repositoryLogo = "logo\.svg"/); assert.match(readme, / { for (const candidate of ["data.json", ".obsidian/plugins/manuscript-compiler/data.json", "nested/manuscript-compiler/data.json"]) { await execFileAsync("git", ["check-ignore", "--no-index", "--quiet", candidate]);