fix: limit GitHub release assets to Obsidian install files

This commit is contained in:
haperone 2026-06-17 13:07:59 +02:00
parent 0f3da95b48
commit 75f3378776
9 changed files with 21 additions and 14 deletions

View file

@ -76,6 +76,5 @@ jobs:
build/manifest.json
build/main.js
build/styles.css
build/versions.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -33,7 +33,7 @@ The release is self-contained. You do not need `pngquant`, `mozjpeg`, Homebrew,
Source lives in `src-ts`. Root `main.js` is generated and ignored, so readable TypeScript rather than compiled output stays in Git. `npm run build` creates a production-minified local bundle; `npm run test:release` rebuilds it twice, requires deterministic bytes, validates inline WASM, and stages the release allowlist.
The GitHub release artifact contains only Obsidian install files: `manifest.json`, `main.js`, `styles.css`, and `versions.json`. Production minification is not obfuscation: the complete source remains readable in the repository. Tags are exact numeric SemVer without a `v` prefix. See [RELEASE_POLICY.md](RELEASE_POLICY.md).
The GitHub release artifact contains only Obsidian install files: `manifest.json`, `main.js`, and `styles.css`. `versions.json` stays in the repository for compatibility metadata, but it is not uploaded as a release asset. Production minification is not obfuscation: the complete source remains readable in the repository. Tags are exact numeric SemVer without a `v` prefix. See [RELEASE_POLICY.md](RELEASE_POLICY.md).
### Commands
- **Compress all images in note**: Processes images referenced/used in the active note.

View file

@ -15,7 +15,7 @@ Release самодостаточный. Устанавливать `pngquant`, `
Исходники находятся в `src-ts`. Корневой `main.js` генерируется и игнорируется Git, поэтому в репозитории остаётся читаемый TypeScript, а не compiled output. `npm run build` создаёт production-minified локальный bundle; `npm run test:release` собирает его дважды, проверяет детерминированные байты, inline WASM и точный allowlist релиза.
GitHub release artifact содержит только install-файлы Obsidian: `manifest.json`, `main.js`, `styles.css` и `versions.json`. Минификация не является обфускацией: полный читаемый исходный код остаётся в репозитории. Теги имеют точный numeric SemVer без префикса `v`. См. [RELEASE_POLICY.md](RELEASE_POLICY.md).
GitHub release artifact содержит только install-файлы Obsidian: `manifest.json`, `main.js` и `styles.css`. `versions.json` остаётся в репозитории как compatibility metadata, но не загружается как release asset. Минификация не является обфускацией: полный читаемый исходный код остаётся в репозитории. Теги имеют точный numeric SemVer без префикса `v`. См. [RELEASE_POLICY.md](RELEASE_POLICY.md).
### Возможности
- **Локальное сжатие**: PNG через libimagequant-wasm и PNG WASM decode; JPEG через mozjpeg-wasm.

View file

@ -23,7 +23,7 @@ installed production bundle locally or in CI.
3. Rebuilds production output and requires byte-identical SHA-256 results.
4. Runs `verify:root-ts` to prove root `main.js` equals the generated
production artifact.
5. Recreates `build/` from an explicit four-file allowlist.
5. Recreates `build/` from the explicit Obsidian install-file allowlist.
The production build has no source map and keeps WASM codecs inline, so users
do not need package dependencies, external binaries, or separate `.wasm`
@ -40,7 +40,9 @@ GitHub release assets are exactly:
- `manifest.json`
- `main.js`
- `styles.css`
- `versions.json`
`versions.json` remains tracked in the repository for compatibility metadata,
but it is not a GitHub Release asset.
The ignored `build/` staging directory is recreated on every release. It must
not contain source workspaces, package metadata, caches, settings, backups,

View file

@ -8,8 +8,7 @@
"files": [
"manifest.json",
"main.js",
"styles.css",
"versions.json"
"styles.css"
],
"scripts": {
"lint": "node scripts/class-wide-gates.js",

View file

@ -6,7 +6,7 @@ const { resolveRepositoryLayout } = require("./repository-layout");
const { repositoryRoot: root } = resolveRepositoryLayout();
const buildDir = path.join(root, "build");
const releaseFiles = ["manifest.json", "main.js", "styles.css", "versions.json"];
const releaseFiles = ["manifest.json", "main.js", "styles.css"];
fs.rmSync(buildDir, { recursive: true, force: true });
fs.mkdirSync(buildDir, { recursive: true });

View file

@ -973,12 +973,18 @@ if (isDevLayout) {
assert(rootPackageSource.scripts.build === "npm run build:root", "Standalone package.json build must use the local source build");
assert(rootPackageSource.scripts.test.includes("npm run test:ts") && rootPackageSource.scripts["test:release"].includes("npm run verify:release"), "Standalone package.json test scripts must run local source and release verification");
}
assert(Array.isArray(rootPackageSource.files) && JSON.stringify(rootPackageSource.files) === JSON.stringify(["manifest.json", "main.js", "styles.css", "versions.json"]), "Root package.json files allowlist must contain only Obsidian install artifacts");
assert(Array.isArray(rootPackageSource.files) && JSON.stringify(rootPackageSource.files) === JSON.stringify(["manifest.json", "main.js", "styles.css"]), "Root package.json files allowlist must contain only Obsidian install artifacts");
assert(!releaseWorkflowSource.includes("build/package.json") && !releaseWorkflowSource.includes("build/README.md") && releaseWorkflowSource.includes("npm run test:release"), "Release workflow still ships dev package metadata or bypasses root test:release");
assert(releaseWorkflowSource.includes('"*.*.*"') && releaseWorkflowSource.includes("^[0-9]+\\.[0-9]+\\.[0-9]+$") && !releaseWorkflowSource.includes('"v*"') && !releaseWorkflowSource.includes("GITHUB_REF_NAME#v"), "Release workflow does not combine a dotted tag trigger with exact numeric SemVer validation");
assert((releaseWorkflowSource.match(/actions\/checkout@v6/g) || []).length === 2 && (releaseWorkflowSource.match(/actions\/setup-node@v6/g) || []).length === 2 && (releaseWorkflowSource.match(/node-version:\s*"24"/g) || []).length === 2, "Release workflow must use checkout/setup-node v6 and Node 24 in both jobs");
const releasePrepareCommand = isDevLayout ? "npm --prefix source-recovery run prepare:release" : "npm run prepare:release";
assert(releaseWorkflowSource.includes(releasePrepareCommand) && prepareReleaseSource.includes('["manifest.json", "main.js", "styles.css", "versions.json"]'), "Release workflow does not use the exact install-file staging allowlist");
assert(
releaseWorkflowSource.includes(releasePrepareCommand)
&& prepareReleaseSource.includes('["manifest.json", "main.js", "styles.css"]')
&& !releaseWorkflowSource.includes("build/versions.json")
&& !prepareReleaseSource.includes('"versions.json"'),
"Release workflow does not use the exact supported Obsidian install-file staging allowlist"
);
assert(validateManifestSource.includes("forbiddenReleaseEntries") && validateManifestSource.includes("package.json must declare a files allowlist"), "Manifest validation does not guard release packaging against dev artifact leaks");
assert(validateManifestSource.includes("MIN_API_SURFACE_APP_VERSION") && validateManifestSource.includes("activeWindow/activeDocument/getBasePath"), "Manifest validation does not enforce API-surface minAppVersion");
assert(validateManifestSource.includes("manifest.json authorUrl must be a valid URL") && validateManifestSource.includes("must not point to localhost"), "Manifest validation does not reject malformed or local authorUrl values");

View file

@ -136,8 +136,7 @@ for (const [version, minAppVersion] of Object.entries(versions)) {
const releaseFiles = [
"manifest.json",
"main.js",
"styles.css",
"versions.json"
"styles.css"
];
const forbiddenReleaseEntries = [
"cache-backups",
@ -174,7 +173,9 @@ assert(
assert(releaseWorkflow.includes('"*.*.*"'), "Release workflow must trigger on dotted tag candidates");
assert(releaseWorkflow.includes("^[0-9]+\\.[0-9]+\\.[0-9]+$"), "Release workflow must validate exact numeric SemVer tags");
assert(!releaseWorkflow.includes("GITHUB_REF_NAME#v") && !releaseWorkflow.includes('"v*"'), "Release workflow must reject v-prefixed tags");
assert(releasePrepare.includes('["manifest.json", "main.js", "styles.css", "versions.json"]'), "Release preparation script must use the explicit install-file allowlist");
assert(releasePrepare.includes('["manifest.json", "main.js", "styles.css"]'), "Release preparation script must use the explicit install-file allowlist");
assert(!releaseWorkflow.includes("build/versions.json"), "Release workflow must not upload versions.json as a GitHub Release asset");
assert(!releasePrepare.includes('"versions.json"'), "Release preparation script must not stage versions.json as a GitHub Release asset");
for (const pattern of [
/^node_modules\/$/m,
/^main\.js$/m,

View file

@ -8,7 +8,7 @@ const { resolveRepositoryLayout } = require("./repository-layout");
const { repositoryRoot: root, sourceRoot } = resolveRepositoryLayout();
const rootMainPath = path.join(root, "main.js");
const releaseFiles = ["manifest.json", "main.js", "styles.css", "versions.json"];
const releaseFiles = ["manifest.json", "main.js", "styles.css"];
function sha256(buffer) {
return crypto.createHash("sha256").update(buffer).digest("hex").toUpperCase();