Compare commits

..

9 Commits

Author SHA1 Message Date
vorotamoroz 9265a74578 Merge pull request #1007 from apple-ouyang/codex/cache-cli-ignore-matchers
Compile CLI ignore patterns once
2026-07-13 13:26:21 +09:00
Ouyang Xingyuan f28d886603 perf(cli): compile ignore patterns once
Long-running CLI processes rebuilt the same minimatch ASTs for every checked path.

Compile patterns while loading ignore rules and reuse them until the rules are reloaded.

Refs #1006
2026-07-13 10:47:35 +08:00
vorotamoroz 9c375bd6fd Deploy QR aggregator with GitHub Pages 2026-07-12 21:06:56 +09:00
vorotamoroz 42a333a571 Record P2P validation infrastructure 2026-07-12 20:40:38 +09:00
vorotamoroz a6a5f7af53 Use merged storage capability contracts 2026-07-12 20:40:38 +09:00
vorotamoroz 4df87cc96a Explain portable storage path policy 2026-07-12 20:40:38 +09:00
vorotamoroz d16e66c67a Track unreleased storage contract changes 2026-07-12 20:40:38 +09:00
vorotamoroz 57e26f1079 Enforce rooted storage path contracts 2026-07-12 20:40:38 +09:00
vorotamoroz dbb8d2be22 test: establish storage adapter contracts 2026-07-12 20:40:38 +09:00
3 changed files with 111 additions and 5 deletions
+67
View File
@@ -0,0 +1,67 @@
name: Deploy GitHub Pages
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'aggregator.html'
- '.github/workflows/deploy-pages.yml'
pull_request:
paths:
- 'aggregator.html'
- '.github/workflows/deploy-pages.yml'
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
name: Validate and package Pages site
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate aggregator
run: |
test -s aggregator.html
grep -Fq '<!DOCTYPE html>' aggregator.html
grep -Fq 'obsidian://setuplivesync?settingsQR=' aggregator.html
sed -n '/<script>/,/<\/script>/p' aggregator.html | sed '1d;$d' > aggregator.js
node --check aggregator.js
rm aggregator.js
- name: Prepare Pages site
run: |
mkdir -p _site
cp aggregator.html _site/aggregator.html
touch _site/.nojekyll
- name: Configure GitHub Pages
uses: actions/configure-pages@v5
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site
deploy:
name: Deploy GitHub Pages
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+4 -4
View File
@@ -1,4 +1,4 @@
import { minimatch } from "minimatch"; import { Minimatch } from "minimatch";
import { fsPromises as fs, path } from "@/apps/cli/node-compat"; import { fsPromises as fs, path } from "@/apps/cli/node-compat";
/** /**
@@ -17,7 +17,7 @@ import { fsPromises as fs, path } from "@/apps/cli/node-compat";
* Missing files (`.livesync/ignore` or `.gitignore`) are silently skipped. * Missing files (`.livesync/ignore` or `.gitignore`) are silently skipped.
*/ */
export class IgnoreRules { export class IgnoreRules {
private patterns: string[] = []; private patterns: Minimatch[] = [];
constructor(private vaultPath: string) {} constructor(private vaultPath: string) {}
@@ -108,7 +108,7 @@ export class IgnoreRules {
`Remove it from .livesync/ignore or use a separate include/exclude file.` `Remove it from .livesync/ignore or use a separate include/exclude file.`
); );
} }
this.patterns.push(this._normalisePattern(raw)); this.patterns.push(new Minimatch(this._normalisePattern(raw), { dot: true }));
} }
/** /**
@@ -124,6 +124,6 @@ export class IgnoreRules {
} }
// Normalise to forward slashes for minimatch. // Normalise to forward slashes for minimatch.
const normalised = relativePath.replace(/\\/g, "/"); const normalised = relativePath.replace(/\\/g, "/");
return this.patterns.some((p) => minimatch(normalised, p, { dot: true })); return this.patterns.some((pattern) => pattern.match(normalised));
} }
} }
@@ -1,7 +1,28 @@
import * as fs from "node:fs/promises"; import * as fs from "node:fs/promises";
import * as os from "node:os"; import * as os from "node:os";
import * as path from "node:path"; import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const minimatchStats = vi.hoisted(() => ({ constructions: 0 }));
vi.mock("minimatch", async (importOriginal) => {
const actual = await importOriginal<typeof import("minimatch")>();
class CountingMinimatch extends actual.Minimatch {
constructor(pattern: string, options?: import("minimatch").MinimatchOptions) {
super(pattern, options);
minimatchStats.constructions++;
}
}
return {
...actual,
Minimatch: CountingMinimatch,
minimatch: (path: string, pattern: string, options?: import("minimatch").MinimatchOptions) =>
new CountingMinimatch(pattern, options).match(path),
};
});
import { IgnoreRules } from "./IgnoreRules"; import { IgnoreRules } from "./IgnoreRules";
describe("IgnoreRules", () => { describe("IgnoreRules", () => {
@@ -19,6 +40,10 @@ describe("IgnoreRules", () => {
await fs.writeFile(path.join(ignoreDir, "ignore"), content, "utf-8"); await fs.writeFile(path.join(ignoreDir, "ignore"), content, "utf-8");
} }
beforeEach(() => {
minimatchStats.constructions = 0;
});
afterEach(async () => { afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
}); });
@@ -55,6 +80,20 @@ describe("IgnoreRules", () => {
}); });
describe("shouldIgnore", () => { describe("shouldIgnore", () => {
it("compiles loaded patterns once", async () => {
const vaultPath = await createVault();
await writeIgnoreFile(vaultPath, "*.tmp\nbuild/\n");
const rules = new IgnoreRules(vaultPath);
await rules.load();
expect(rules.shouldIgnore("notes/readme.md")).toBe(false);
expect(rules.shouldIgnore("notes/scratch.tmp")).toBe(true);
expect(rules.shouldIgnore("build/output.js")).toBe(true);
expect(rules.shouldIgnore("other.md")).toBe(false);
expect(minimatchStats.constructions).toBe(2);
});
it("matches **/*.tmp against notes/scratch.tmp", async () => { it("matches **/*.tmp against notes/scratch.tmp", async () => {
const vaultPath = await createVault(); const vaultPath = await createVault();
await writeIgnoreFile(vaultPath, "*.tmp\n"); await writeIgnoreFile(vaultPath, "*.tmp\n");