Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
055046b
chore(repo): target ESNext for native Explicit Resource Management emit
YusukeHirao Aug 14, 2026
1596170
feat(shared): add Explicit Resource Management helpers and require No…
YusukeHirao Aug 14, 2026
a75c24b
chore(readtext): require Node >=24.11
YusukeHirao Aug 14, 2026
bf01563
fix(fs): destroy streams and register error listeners before finalize…
YusukeHirao Aug 14, 2026
111df3a
chore(html-distiller): require Node >=24.11
YusukeHirao Aug 14, 2026
7ffe953
fix(google-auth): consolidate waitForAuthCode cleanup via AsyncDispos…
YusukeHirao Aug 14, 2026
7cd8dd8
chore(puppeteer-general-actions): require Node >=24.11
YusukeHirao Aug 14, 2026
42f1f1f
chore(roar): require Node >=24.11
YusukeHirao Aug 14, 2026
d1645aa
feat(cli-core): add unwrapSuppressedError and require Node >=24.11
YusukeHirao Aug 14, 2026
92a2528
feat(dealer): add Symbol.dispose to Display/Lanes and fix deal() clea…
YusukeHirao Aug 14, 2026
92be937
chore(notion): require Node >=24.11
YusukeHirao Aug 14, 2026
d8b59ed
fix(proc-talk): make close() idempotent and add Symbol.asyncDispose
YusukeHirao Aug 14, 2026
3166328
chore(puppeteer-scroll): require Node >=24.11 and align puppeteer at …
YusukeHirao Aug 14, 2026
3a82da7
feat(google-sheets): add Symbol.asyncDispose to Sheet and SheetTable
YusukeHirao Aug 14, 2026
b20cd7b
fix(filematch): destroy both streams when one errors during comparison
YusukeHirao Aug 14, 2026
c8b83b9
chore(puppeteer-page-scan): require Node >=24.11 and align puppeteer …
YusukeHirao Aug 14, 2026
df4ff28
fix(puppeteer-dealer): dispose ChildProcessManager via await using
YusukeHirao Aug 14, 2026
8f34112
feat(page-cluster): use `using` for Lanes cleanup and unwrap Suppress…
YusukeHirao Aug 14, 2026
52de496
fix(remote-inspector): unwrap SuppressedError in CLI error output
YusukeHirao Aug 14, 2026
eb54edf
chore(backlog-projects): raise Node requirement to >=24.11 (was >=22.…
YusukeHirao Aug 14, 2026
26e7bba
chore(puppeteer-screenshot): require Node >=24.11 and align puppeteer…
YusukeHirao Aug 14, 2026
c47a97b
fix(beholder): dispose anchor ElementHandles via AsyncDisposableStack
YusukeHirao Aug 14, 2026
d427aaf
chore(a11y-check-core): require Node >=24.11 and bump puppeteer to 25…
YusukeHirao Aug 14, 2026
53a72ac
fix(replicator): release response listener via disposableListener
YusukeHirao Aug 14, 2026
0c6a5e4
chore(archaeologist): require Node >=24.11 and bump puppeteer to 25.5.0
YusukeHirao Aug 14, 2026
2a8b76a
chore(print): require Node >=24.11 and bump puppeteer to 25.5.0
YusukeHirao Aug 14, 2026
d8d7beb
fix(anatomist): dispose browser/page via await using and unwrap Suppr…
YusukeHirao Aug 14, 2026
90f2c79
chore(a11y-check-axe-scenario): require Node >=24.11 and align puppet…
YusukeHirao Aug 14, 2026
8b6088b
fix(a11y-check-scenarios): release console listener each loop iteration
YusukeHirao Aug 14, 2026
cdf3c41
feat(a11y-check): add Symbol.asyncDispose to SpreadsheetReporter
YusukeHirao Aug 14, 2026
f4034d6
chore(deps): update yarn.lock for engines/puppeteer changes
YusukeHirao Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/@d-zero/a11y-check-axe-scenario/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand All @@ -30,11 +33,11 @@
},
"devDependencies": {
"axe-core": "4.12.1",
"puppeteer": "25.3.0"
"puppeteer": "25.5.0"
},
"peerDependencies": {
"axe-core": "4.12.1",
"puppeteer": "25.2.1"
"puppeteer": "25.5.0"
},
"repository": {
"type": "git",
Expand Down
5 changes: 4 additions & 1 deletion packages/@d-zero/a11y-check-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand All @@ -27,7 +30,7 @@
"@d-zero/shared": "0.22.5",
"ansi-colors": "4.1.3",
"color-contrast-checker": "2.1.0",
"puppeteer": "25.3.0"
"puppeteer": "25.5.0"
},
"devDependencies": {
"@d-zero/dealer": "1.10.4",
Expand Down
7 changes: 5 additions & 2 deletions packages/@d-zero/a11y-check-scenarios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand All @@ -28,10 +31,10 @@
"ansi-colors": "4.1.3"
},
"devDependencies": {
"puppeteer": "25.3.0"
"puppeteer": "25.5.0"
},
"peerDependencies": {
"puppeteer": "25.2.1"
"puppeteer": "25.5.0"
},
"repository": {
"type": "git",
Expand Down
34 changes: 22 additions & 12 deletions packages/@d-zero/a11y-check-scenarios/src/scenario2.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { ScenarioOptions } from './types.js';
import type { NeedAnalysis } from '@d-zero/a11y-check-core';
import type { ConsoleMessage } from 'puppeteer';

import { createScenario } from '@d-zero/a11y-check-core';
import { Cache } from '@d-zero/shared/cache';
import { disposableListener } from '@d-zero/shared/disposable-listener';
import c from 'ansi-colors';

const scenarioId = 'a11y-check/scenario02';
Expand Down Expand Up @@ -41,19 +43,27 @@ export default createScenario((options?: ScenarioOptions) => {
for (const selector of navigations) {
const logBase = `Finding "${selector}"`;
logger(`Finding "${selector}"`);
page.on('console', (msg) => {
const msgType = msg.type();
switch (msgType) {
case 'error': {
logger(`${logBase}: ${c.red(msg.text())}`);
break;
// `using` により、各ループ反復の終わりで確実にリスナーが解除される。
// ループ内で page.on('console', ...) するため、解除しないと
// selector の数だけリスナーが累積する。
using _consoleListener = disposableListener(
page,
'console',
(msg: ConsoleMessage) => {
const msgType = msg.type();
switch (msgType) {
case 'error': {
logger(`${logBase}: ${c.red(msg.text())}`);
break;
}
default: {
logger(`${logBase}: ${c.gray(msg.text())}`);
break;
}
}
default: {
logger(`${logBase}: ${c.gray(msg.text())}`);
break;
}
}
});
},
);
void _consoleListener;
const outerHTML = await page.evaluate((selector) => {
return [...document.querySelectorAll(selector)].map((el) => el.outerHTML);
}, selector);
Expand Down
3 changes: 3 additions & 0 deletions packages/@d-zero/a11y-check/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand Down
17 changes: 17 additions & 0 deletions packages/@d-zero/a11y-check/src/spreadsheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ export class SpreadsheetReporter {
// eslint-disable-next-line no-restricted-syntax
private constructor() {}

/**
* `await using` 宣言のスコープ脱出時に呼ばれ、内部の {@link SheetTable} を
* フラッシュする。バッファに未送信行が残ったままスコープを抜けてデータが
* 欠損するのを防ぐ。
* @example
* ```ts
* {
* await using reporter = await SpreadsheetReporter.setup(sheetUrl, sheetName);
* await reporter.report(violations);
* } // スコープ脱出時に自動で内部 SheetTable の未送信バッファが flush される
* ```
*/
async [Symbol.asyncDispose]() {
if (this.#table) {
await this.#table[Symbol.asyncDispose]();
}
}
async report(results: readonly Violation[]) {
if (!this.#table) {
throw new Error('Table is not created');
Expand Down
6 changes: 5 additions & 1 deletion packages/@d-zero/anatomist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand Down Expand Up @@ -36,10 +39,11 @@
},
"dependencies": {
"@d-zero/beholder": "4.2.2",
"@d-zero/cli-core": "1.3.16",
"@d-zero/dealer": "1.10.4",
"@d-zero/puppeteer-page-scan": "4.6.8",
"@d-zero/shared": "0.22.5",
"puppeteer": "25.3.0"
"puppeteer": "25.5.0"
},
"repository": {
"type": "git",
Expand Down
3 changes: 3 additions & 0 deletions packages/@d-zero/anatomist/src/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ describe('runCli', () => {
return true;
}),
end: mockEnd,
// cli.ts の `await using outFile` が要求する Symbol.asyncDispose のスタブ
[Symbol.asyncDispose]: vi.fn(async () => {}),
} as never);
vi.mocked(runBatch).mockImplementation((_urls, options?: RunBatchOptions) => {
options?.onResult?.({
Expand Down Expand Up @@ -249,6 +251,7 @@ describe('runCli', () => {
end: vi.fn((callback: (error: Error) => void) => {
callback(new Error('disk full'));
}),
[Symbol.asyncDispose]: vi.fn(async () => {}),
} as never);

const stderr = new PassThrough();
Expand Down
39 changes: 23 additions & 16 deletions packages/@d-zero/anatomist/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { createWriteStream } from 'node:fs';
import { readFile } from 'node:fs/promises';
import process from 'node:process';

import { unwrapSuppressedError } from '@d-zero/cli-core';

import { formatResultLine } from './format-output.js';
import { parseArgs } from './parse-args.js';
import { parseUrlList } from './parse-url-list.js';
Expand Down Expand Up @@ -134,9 +136,12 @@ export async function runCli(options: {
return 1;
}

const outStream: NodeJS.WritableStream = args.out
? createWriteStream(args.out)
: options.stdout;
// `await using` により、runBatch() が想定外の例外を投げてスコープを抜けても
// --out で開いたファイル記述子が確実に閉じられる(stdout の場合は outFile が
// undefined のままなので dispose は no-op — process.stdout を誤って
// close してしまうことはない)。
await using outFile = args.out ? createWriteStream(args.out) : undefined;
const outStream: NodeJS.WritableStream = outFile ?? options.stdout;

let hadError = false;
await runBatch(urls, {
Expand All @@ -158,24 +163,26 @@ export async function runCli(options: {
},
onError: (url, error) => {
hadError = true;
options.stderr.write(
`anatomist: failed to analyze ${url}: ${(error as Error).message}\n`,
);
// SuppressedError(using スコープ内で本体と dispose の両方が例外を投げた
// 場合)を分解し、定型メッセージの裏に隠れる根本原因を両方とも出力する
for (const cause of unwrapSuppressedError(error)) {
options.stderr.write(
`anatomist: failed to analyze ${url}: ${cause instanceof Error ? cause.message : String(cause)}\n`,
);
}
},
});

if (args.out) {
if (outFile) {
try {
await new Promise<void>((resolve, reject) => {
(outStream as ReturnType<typeof createWriteStream>).end(
(error?: Error | null) => {
if (error) {
reject(error);
} else {
resolve();
}
},
);
outFile.end((error?: Error | null) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
} catch (error) {
// Consistent with every other failure path here: report to stderr
Expand Down
28 changes: 25 additions & 3 deletions packages/@d-zero/anatomist/src/run-batch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,37 @@ function makeSequentialDealMock() {
}

describe('runBatch', () => {
let mockPage: { close: ReturnType<typeof vi.fn> };
let mockBrowser: { newPage: ReturnType<typeof vi.fn>; close: ReturnType<typeof vi.fn> };
let mockPage: {
close: ReturnType<typeof vi.fn>;
[Symbol.asyncDispose]: ReturnType<typeof vi.fn>;
};
let mockBrowser: {
newPage: ReturnType<typeof vi.fn>;
close: ReturnType<typeof vi.fn>;
[Symbol.asyncDispose]: ReturnType<typeof vi.fn>;
};

beforeEach(() => {
mockPage = { close: vi.fn().mockResolvedValue() };
// `run-batch.ts` now uses `await using`, which requires a real
// `Symbol.asyncDispose` implementation — delegate to the existing
// `close` mock so assertions on `close` call counts stay meaningful.
mockPage = {
close: vi.fn().mockResolvedValue(),
[Symbol.asyncDispose]: vi.fn(),
};
mockPage[Symbol.asyncDispose].mockImplementation(async () => {
await mockPage.close();
});

mockBrowser = {
newPage: vi.fn().mockResolvedValue(mockPage),
close: vi.fn().mockResolvedValue(),
[Symbol.asyncDispose]: vi.fn(),
};
mockBrowser[Symbol.asyncDispose].mockImplementation(async () => {
await mockBrowser.close();
});

vi.mocked(launch).mockResolvedValue(mockBrowser as never);
vi.mocked(deal).mockImplementation(makeSequentialDealMock() as never);
vi.mocked(analyzePageLayout).mockReset().mockResolvedValue([]);
Expand Down
67 changes: 32 additions & 35 deletions packages/@d-zero/anatomist/src/run-batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,41 +43,38 @@ export async function runBatch(
urls: readonly string[],
options: RunBatchOptions = {},
): Promise<void> {
const browser = await launch({ headless: true });
try {
const items: UrlItem[] = urls.map((url, index) => ({ index, url }));
// `await using` により、deal() が例外を投げてもブラウザが確実に閉じられる。
// puppeteer の Browser/Page は Symbol.asyncDispose をネイティブ実装している
// (型定義上の対応は puppeteer 25.5.0 以降)。
await using browser = await launch({ headless: true });
const items: UrlItem[] = urls.map((url, index) => ({ index, url }));

await deal(
items,
({ url }, update) => {
return async () => {
update(`analyzing ${url}`);
const page = await browser.newPage();
try {
const results = await analyzePageLayout(page, url, options);
for (const result of results) {
options.onResult?.(result);
}
} catch (error) {
options.onError?.(url, error);
} finally {
await page.close();
await deal(
items,
({ url }, update) => {
return async () => {
update(`analyzing ${url}`);
await using page = await browser.newPage();
try {
const results = await analyzePageLayout(page, url, options);
for (const result of results) {
options.onResult?.(result);
}
};
},
{
header: HEADER,
// Clamp to 1: `Dealer`'s worker loop (`while (this.#workers.size <
// this.#limit)`) never launches a worker when `limit` is `0`, and
// `?? 1` alone doesn't catch that — nullish coalescing only
// replaces `null`/`undefined`, not an explicit `0` such as
// `--concurrency 0` would parse to. Without this clamp, a `0`
// hangs the whole run with no output and no error.
limit: Math.max(1, options.concurrency ?? 1),
stream: options.stderr,
},
);
} finally {
await browser.close();
}
} catch (error) {
options.onError?.(url, error);
}
};
},
{
header: HEADER,
// Clamp to 1: `Dealer`'s worker loop (`while (this.#workers.size <
// this.#limit)`) never launches a worker when `limit` is `0`, and
// `?? 1` alone doesn't catch that — nullish coalescing only
// replaces `null`/`undefined`, not an explicit `0` such as
// `--concurrency 0` would parse to. Without this clamp, a `0`
// hangs the whole run with no output and no error.
limit: Math.max(1, options.concurrency ?? 1),
stream: options.stderr,
},
);
}
5 changes: 4 additions & 1 deletion packages/@d-zero/archaeologist/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=24.11.0"
},
"type": "module",
"exports": {
".": {
Expand Down Expand Up @@ -41,7 +44,7 @@
"parse-diff": "0.12.0",
"pixelmatch": "7.2.0",
"pngjs": "7.0.0",
"puppeteer": "25.3.0",
"puppeteer": "25.5.0",
"strip-ansi": "7.2.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/@d-zero/backlog-projects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"access": "public"
},
"engines": {
"node": ">=22.1.0"
"node": ">=24.11.0"
},
"type": "module",
"bin": "dist/cli.js",
Expand Down
Loading
Loading