From b91302d044b39e54891a5c7420da405cacb40fd0 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 23 Jul 2026 09:31:24 -0500 Subject: [PATCH 1/3] test: create temp dirs with fs.mkdtemp Every one of these suites built its temp directory by hand: testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); await fs.mkdir(testDir, { recursive: true }); That check-then-create is what CodeQL's js/insecure-temporary-file flags, and it accounted for 452 of the repo's 467 dismissed code-scanning alerts. Dismissing them one by one is a treadmill: every new suite that copies the idiom mints fresh alerts, and a real finding is easy to lose in that volume. fs.mkdtemp creates the directory atomically at mode 0700 with a random suffix, so there is no window to pre-empt and no name to guess. The suites that already used it are the evidence this silences the rule: 33 of the 34 files calling mkdtemp carry zero alerts. The lone exception, archive.test.ts, was scanned one commit before its own mkdtemp fix landed and is left to that change rather than conflicting with it. The dirs named on Date.now() alone were genuinely predictable; the randomUUID ones were not, but they trained the same copy-paste. Both are gone now. Behavior is unchanged: mkdtemp creates the root the old mkdir created, and no assertion depended on the root being absent. Verified with the full suite (2196 tests, 111 files). Co-Authored-By: Claude Opus 4.8 (1M context) --- test/commands/config-profile.test.ts | 3 +-- test/commands/config.test.ts | 6 ++---- test/commands/schema.test.ts | 6 +----- test/core/artifact-graph/outputs.test.ts | 3 +-- test/core/artifact-graph/resolver.test.ts | 3 +-- test/core/artifact-graph/state.test.ts | 3 +-- test/core/available-tools.test.ts | 4 +--- test/core/commands/change-command.list.test.ts | 2 +- .../commands/change-command.show-validate.test.ts | 2 +- test/core/completions/completion-provider.test.ts | 4 +--- .../completions/installers/bash-installer.test.ts | 7 ++----- .../completions/installers/fish-installer.test.ts | 7 ++----- .../installers/powershell-installer.test.ts | 7 ++----- .../completions/installers/zsh-installer.test.ts | 7 ++----- test/core/global-config.test.ts | 3 +-- test/core/init.test.ts | 13 ++++--------- test/core/legacy-cleanup.test.ts | 4 +--- test/core/list.test.ts | 3 +-- test/core/migration.test.ts | 7 ++----- test/core/profile-sync-drift.test.ts | 2 +- test/core/shared/tool-detection.test.ts | 4 +--- test/core/update.test.ts | 4 +--- test/core/view.test.ts | 3 +-- test/telemetry/config.test.ts | 4 +--- test/telemetry/index.test.ts | 4 +--- test/utils/change-metadata.test.ts | 7 +++---- test/utils/change-utils.test.ts | 4 +--- test/utils/file-system.test.ts | 4 +--- test/utils/marker-updates.test.ts | 3 +-- test/utils/task-progress.test.ts | 2 +- 30 files changed, 41 insertions(+), 94 deletions(-) diff --git a/test/commands/config-profile.test.ts b/test/commands/config-profile.test.ts index ab18e4e6b7..bb130a8f64 100644 --- a/test/commands/config-profile.test.ts +++ b/test/commands/config-profile.test.ts @@ -126,8 +126,7 @@ describe('config profile interactive flow', () => { beforeEach(() => { vi.resetModules(); - tempDir = path.join(os.tmpdir(), `openspec-config-profile-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-config-profile-test-')); originalEnv = { ...process.env }; originalCwd = process.cwd(); diff --git a/test/commands/config.test.ts b/test/commands/config.test.ts index 1e4f7e73d0..1a6c8d8cea 100644 --- a/test/commands/config.test.ts +++ b/test/commands/config.test.ts @@ -20,8 +20,7 @@ describe('config command integration', () => { beforeEach(() => { // Create unique temp directory for each test - tempDir = path.join(os.tmpdir(), `openspec-config-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-config-test-')); // Save original env and set XDG_CONFIG_HOME originalEnv = { ...process.env }; @@ -245,8 +244,7 @@ describe('config profile command', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(() => { - tempDir = path.join(os.tmpdir(), `openspec-profile-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-profile-test-')); originalEnv = { ...process.env }; process.env.XDG_CONFIG_HOME = tempDir; }); diff --git a/test/commands/schema.test.ts b/test/commands/schema.test.ts index c614038aa1..9722b8150b 100644 --- a/test/commands/schema.test.ts +++ b/test/commands/schema.test.ts @@ -12,11 +12,7 @@ describe('schema command', () => { beforeEach(() => { // Create unique temp directory for each test - tempDir = path.join( - os.tmpdir(), - `openspec-schema-test-${Date.now()}-${Math.random().toString(36).slice(2)}` - ); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-schema-test-')); // Create openspec directory structure fs.mkdirSync(path.join(tempDir, 'openspec', 'schemas'), { recursive: true }); diff --git a/test/core/artifact-graph/outputs.test.ts b/test/core/artifact-graph/outputs.test.ts index 988200e2c1..64c3267190 100644 --- a/test/core/artifact-graph/outputs.test.ts +++ b/test/core/artifact-graph/outputs.test.ts @@ -11,8 +11,7 @@ describe('artifact-graph/outputs', () => { const canonical = (targetPath: string): string => FileSystemUtils.canonicalizeExistingPath(targetPath); beforeEach(() => { - tempDir = path.join(os.tmpdir(), `openspec-outputs-test-${Date.now()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-outputs-test-')); }); afterEach(() => { diff --git a/test/core/artifact-graph/resolver.test.ts b/test/core/artifact-graph/resolver.test.ts index 3436151933..b37c745eb9 100644 --- a/test/core/artifact-graph/resolver.test.ts +++ b/test/core/artifact-graph/resolver.test.ts @@ -19,8 +19,7 @@ describe('artifact-graph/resolver', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(() => { - tempDir = path.join(os.tmpdir(), `openspec-resolver-test-${Date.now()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-resolver-test-')); originalEnv = { ...process.env }; }); diff --git a/test/core/artifact-graph/state.test.ts b/test/core/artifact-graph/state.test.ts index 758a7675b8..13eddd348c 100644 --- a/test/core/artifact-graph/state.test.ts +++ b/test/core/artifact-graph/state.test.ts @@ -16,8 +16,7 @@ describe('artifact-graph/state', () => { }); beforeEach(() => { - tempDir = path.join(os.tmpdir(), `openspec-state-test-${Date.now()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-state-test-')); }); afterEach(() => { diff --git a/test/core/available-tools.test.ts b/test/core/available-tools.test.ts index 2556d29a35..ef13effe30 100644 --- a/test/core/available-tools.test.ts +++ b/test/core/available-tools.test.ts @@ -2,15 +2,13 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { getAvailableTools } from '../../src/core/available-tools.js'; describe('available-tools', () => { let testDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); }); afterEach(async () => { diff --git a/test/core/commands/change-command.list.test.ts b/test/core/commands/change-command.list.test.ts index 6bf24420e1..9ec1df5a1d 100644 --- a/test/core/commands/change-command.list.test.ts +++ b/test/core/commands/change-command.list.test.ts @@ -12,7 +12,7 @@ describe('ChangeCommand.list', () => { beforeAll(async () => { cmd = new ChangeCommand(); originalCwd = process.cwd(); - tempRoot = path.join(os.tmpdir(), `openspec-change-command-list-${Date.now()}`); + tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-change-command-list-')); const changeDir = path.join(tempRoot, 'openspec', 'changes', 'demo'); await fs.mkdir(changeDir, { recursive: true }); const proposal = `# Change: Demo\n\n## Why\nTest list.\n\n## What Changes\n- **auth:** Add requirement`; diff --git a/test/core/commands/change-command.show-validate.test.ts b/test/core/commands/change-command.show-validate.test.ts index fcaa00ad53..5442a52cbf 100644 --- a/test/core/commands/change-command.show-validate.test.ts +++ b/test/core/commands/change-command.show-validate.test.ts @@ -13,7 +13,7 @@ describe('ChangeCommand.show/validate', () => { beforeAll(async () => { cmd = new ChangeCommand(); originalCwd = process.cwd(); - tempRoot = path.join(os.tmpdir(), `openspec-change-command-${Date.now()}`); + tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-change-command-')); const changesDir = path.join(tempRoot, 'openspec', 'changes', 'sample-change'); await fs.mkdir(changesDir, { recursive: true }); const proposal = `# Change: Sample Change\n\n## Why\nConsistency in tests.\n\n## What Changes\n- **auth:** Add requirement`; diff --git a/test/core/completions/completion-provider.test.ts b/test/core/completions/completion-provider.test.ts index 2af6dc2437..8f14798675 100644 --- a/test/core/completions/completion-provider.test.ts +++ b/test/core/completions/completion-provider.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { CompletionProvider } from '../../../src/core/completions/completion-provider.js'; describe('CompletionProvider', () => { @@ -10,8 +9,7 @@ describe('CompletionProvider', () => { let provider: CompletionProvider; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); provider = new CompletionProvider(2000, testDir); }); diff --git a/test/core/completions/installers/bash-installer.test.ts b/test/core/completions/installers/bash-installer.test.ts index e289d90e38..a251031ee3 100644 --- a/test/core/completions/installers/bash-installer.test.ts +++ b/test/core/completions/installers/bash-installer.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { BashInstaller } from '../../../../src/core/completions/installers/bash-installer.js'; describe('BashInstaller', () => { @@ -11,8 +10,7 @@ describe('BashInstaller', () => { beforeEach(async () => { // Create a temporary home directory for testing - testHomeDir = path.join(os.tmpdir(), `openspec-bash-test-${randomUUID()}`); - await fs.mkdir(testHomeDir, { recursive: true }); + testHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-bash-test-')); installer = new BashInstaller(testHomeDir); }); @@ -208,8 +206,7 @@ describe('BashInstaller', () => { it('should handle paths with spaces in .bashrc config', async () => { // Create a test home directory with spaces - const testHomeDirWithSpaces = path.join(os.tmpdir(), `openspec bash test ${randomUUID()}`); - await fs.mkdir(testHomeDirWithSpaces, { recursive: true }); + const testHomeDirWithSpaces = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec bash test ')); const installerWithSpaces = new BashInstaller(testHomeDirWithSpaces); try { diff --git a/test/core/completions/installers/fish-installer.test.ts b/test/core/completions/installers/fish-installer.test.ts index d8eb3021e1..35a69b359c 100644 --- a/test/core/completions/installers/fish-installer.test.ts +++ b/test/core/completions/installers/fish-installer.test.ts @@ -3,15 +3,13 @@ import { FishInstaller } from '../../../../src/core/completions/installers/fish- import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; describe('FishInstaller', () => { let testHomeDir: string; let installer: FishInstaller; beforeEach(async () => { - testHomeDir = path.join(os.tmpdir(), `openspec-fish-test-${randomUUID()}`); - await fs.mkdir(testHomeDir, { recursive: true }); + testHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-fish-test-')); installer = new FishInstaller(testHomeDir); }); @@ -179,8 +177,7 @@ complete -c openspec -a 'validate' -d 'Validate specs' }); it('should handle installation with paths containing spaces', async () => { - const spacedHomeDir = path.join(os.tmpdir(), `openspec fish test ${randomUUID()}`); - await fs.mkdir(spacedHomeDir, { recursive: true }); + const spacedHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec fish test ')); const spacedInstaller = new FishInstaller(spacedHomeDir); const result = await spacedInstaller.install(mockCompletionScript); diff --git a/test/core/completions/installers/powershell-installer.test.ts b/test/core/completions/installers/powershell-installer.test.ts index b9e5c7e2f1..a1e90b2cc2 100644 --- a/test/core/completions/installers/powershell-installer.test.ts +++ b/test/core/completions/installers/powershell-installer.test.ts @@ -3,7 +3,6 @@ import { PowerShellInstaller } from '../../../../src/core/completions/installers import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; describe('PowerShellInstaller', () => { let testHomeDir: string; @@ -20,8 +19,7 @@ describe('PowerShellInstaller', () => { }; beforeEach(async () => { - testHomeDir = path.join(os.tmpdir(), `openspec-powershell-test-${randomUUID()}`); - await fs.mkdir(testHomeDir, { recursive: true }); + testHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-powershell-test-')); installer = new PowerShellInstaller(testHomeDir); originalPlatform = process.platform; originalEnv = { ...process.env }; @@ -519,8 +517,7 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter }); it('should handle installation with paths containing spaces', async () => { - const spacedHomeDir = path.join(os.tmpdir(), `openspec powershell test ${randomUUID()}`); - await fs.mkdir(spacedHomeDir, { recursive: true }); + const spacedHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec powershell test ')); const spacedInstaller = new PowerShellInstaller(spacedHomeDir); const result = await spacedInstaller.install(mockCompletionScript); diff --git a/test/core/completions/installers/zsh-installer.test.ts b/test/core/completions/installers/zsh-installer.test.ts index ff84de4a3f..91100d03e2 100644 --- a/test/core/completions/installers/zsh-installer.test.ts +++ b/test/core/completions/installers/zsh-installer.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { ZshInstaller } from '../../../../src/core/completions/installers/zsh-installer.js'; describe('ZshInstaller', () => { @@ -17,8 +16,7 @@ describe('ZshInstaller', () => { delete process.env.ZSH; // Create a temporary home directory for testing - testHomeDir = path.join(os.tmpdir(), `openspec-zsh-test-${randomUUID()}`); - await fs.mkdir(testHomeDir, { recursive: true }); + testHomeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-zsh-test-')); installer = new ZshInstaller(testHomeDir); }); @@ -271,8 +269,7 @@ describe('ZshInstaller', () => { it('should handle paths with spaces in .zshrc config', async () => { // Create a test home directory with spaces - const testHomeDirWithSpaces = path.join(os.tmpdir(), `openspec zsh test ${randomUUID()}`); - await fs.mkdir(testHomeDirWithSpaces, { recursive: true }); + const testHomeDirWithSpaces = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec zsh test ')); const installerWithSpaces = new ZshInstaller(testHomeDirWithSpaces); try { diff --git a/test/core/global-config.test.ts b/test/core/global-config.test.ts index 03310060ef..978b6c5b86 100644 --- a/test/core/global-config.test.ts +++ b/test/core/global-config.test.ts @@ -21,8 +21,7 @@ describe('global-config', () => { beforeEach(() => { // Create temp directory for tests - tempDir = path.join(os.tmpdir(), `openspec-global-config-test-${Date.now()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-global-config-test-')); // Save original env originalEnv = { ...process.env }; diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 839c4a28d5..492a23c6a4 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { promises as fs } from 'fs'; -import { randomUUID } from 'crypto'; import path from 'path'; import os from 'os'; import { InitCommand } from '../../src/core/init.js'; @@ -30,12 +29,10 @@ describe('InitCommand', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-init-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-init-test-')); originalEnv = { ...process.env }; // Use a temp dir for global config to avoid reading real config - configTempDir = path.join(os.tmpdir(), `openspec-config-init-${randomUUID()}`); - await fs.mkdir(configTempDir, { recursive: true }); + configTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-config-init-')); process.env.XDG_CONFIG_HOME = configTempDir; process.env.CODEX_HOME = path.join(testDir, 'codex-home'); @@ -646,12 +643,10 @@ describe('InitCommand - profile and detection features', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-init-profile-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-init-profile-test-')); originalEnv = { ...process.env }; // Use a temp dir for global config to avoid polluting real config - configTempDir = path.join(os.tmpdir(), `openspec-config-test-${randomUUID()}`); - await fs.mkdir(configTempDir, { recursive: true }); + configTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-config-test-')); process.env.XDG_CONFIG_HOME = configTempDir; process.env.CODEX_HOME = path.join(testDir, 'codex-home'); vi.spyOn(console, 'log').mockImplementation(() => {}); diff --git a/test/core/legacy-cleanup.test.ts b/test/core/legacy-cleanup.test.ts index c048edf91c..48dc941a2a 100644 --- a/test/core/legacy-cleanup.test.ts +++ b/test/core/legacy-cleanup.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { detectLegacyArtifacts, detectLegacyConfigFiles, @@ -32,8 +31,7 @@ describe('legacy-cleanup', () => { beforeEach(async () => { originalEnv = { ...process.env }; - testDir = path.join(os.tmpdir(), `openspec-legacy-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-legacy-test-')); process.env.CODEX_HOME = path.join(testDir, 'codex-home'); // Create openspec directory structure await fs.mkdir(path.join(testDir, 'openspec'), { recursive: true }); diff --git a/test/core/list.test.ts b/test/core/list.test.ts index 096e46a1d8..9e4a08c136 100644 --- a/test/core/list.test.ts +++ b/test/core/list.test.ts @@ -11,8 +11,7 @@ describe('ListCommand', () => { beforeEach(async () => { // Create temp directory - tempDir = path.join(os.tmpdir(), `openspec-list-test-${Date.now()}`); - await fs.mkdir(tempDir, { recursive: true }); + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-list-test-')); // Mock console.log to capture output originalLog = console.log; diff --git a/test/core/migration.test.ts b/test/core/migration.test.ts index e1b6f4f7cb..b819400826 100644 --- a/test/core/migration.test.ts +++ b/test/core/migration.test.ts @@ -1,7 +1,6 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import fs from 'node:fs'; import { promises as fsp } from 'node:fs'; import { AI_TOOLS, type AIToolOption } from '../../src/core/config.js'; @@ -65,10 +64,8 @@ describe('migration', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(async () => { - projectDir = path.join(os.tmpdir(), `openspec-migration-project-${randomUUID()}`); - configHome = path.join(os.tmpdir(), `openspec-migration-config-${randomUUID()}`); - await fsp.mkdir(projectDir, { recursive: true }); - await fsp.mkdir(configHome, { recursive: true }); + projectDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'openspec-migration-project-')); + configHome = await fsp.mkdtemp(path.join(os.tmpdir(), 'openspec-migration-config-')); originalEnv = { ...process.env }; process.env.XDG_CONFIG_HOME = configHome; }); diff --git a/test/core/profile-sync-drift.test.ts b/test/core/profile-sync-drift.test.ts index 116a6e5706..39f41ca99b 100644 --- a/test/core/profile-sync-drift.test.ts +++ b/test/core/profile-sync-drift.test.ts @@ -41,7 +41,7 @@ describe('profile sync drift detection', () => { let tempDir: string; beforeEach(() => { - tempDir = path.join(os.tmpdir(), `openspec-profile-sync-drift-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-profile-sync-drift-test-')); fs.mkdirSync(path.join(tempDir, 'openspec'), { recursive: true }); }); diff --git a/test/core/shared/tool-detection.test.ts b/test/core/shared/tool-detection.test.ts index c4ef3bbb6c..73f19bd1c8 100644 --- a/test/core/shared/tool-detection.test.ts +++ b/test/core/shared/tool-detection.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { SKILL_NAMES, getToolsWithSkillsDir, @@ -18,8 +17,7 @@ describe('tool-detection', () => { let testDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); }); afterEach(async () => { diff --git a/test/core/update.test.ts b/test/core/update.test.ts index c58065fea9..8c801b767b 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -7,7 +7,6 @@ import type { GlobalConfig } from '../../src/core/global-config.js'; import path from 'path'; import fs from 'fs/promises'; import os from 'os'; -import { randomUUID } from 'crypto'; // Shared mutable mock config state const mockState = { @@ -46,8 +45,7 @@ describe('UpdateCommand', () => { beforeEach(async () => { originalEnv = { ...process.env }; // Create a temporary test directory - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); process.env.CODEX_HOME = path.join(testDir, 'codex-home'); // Create openspec directory diff --git a/test/core/view.test.ts b/test/core/view.test.ts index 653bb8624e..896f88ed6d 100644 --- a/test/core/view.test.ts +++ b/test/core/view.test.ts @@ -12,8 +12,7 @@ describe('ViewCommand', () => { let logOutput: string[] = []; beforeEach(async () => { - tempDir = path.join(os.tmpdir(), `openspec-view-test-${Date.now()}`); - await fs.mkdir(tempDir, { recursive: true }); + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-view-test-')); originalLog = console.log; console.log = (...args: any[]) => { diff --git a/test/telemetry/config.test.ts b/test/telemetry/config.test.ts index ef5726621e..d22d138d40 100644 --- a/test/telemetry/config.test.ts +++ b/test/telemetry/config.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import { randomUUID } from 'node:crypto'; import { getConfigPath, @@ -35,8 +34,7 @@ describe('telemetry/config', () => { beforeEach(() => { // Create temp directory for tests - tempDir = path.join(os.tmpdir(), `openspec-telemetry-test-${randomUUID()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-telemetry-test-')); // Mock HOME/USERPROFILE to point to temp dir // On POSIX, os.homedir() uses HOME; on Windows it uses USERPROFILE diff --git a/test/telemetry/index.test.ts b/test/telemetry/index.test.ts index 73b050a286..e4c6da6c21 100644 --- a/test/telemetry/index.test.ts +++ b/test/telemetry/index.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; -import { randomUUID } from 'node:crypto'; // Mock posthog-node before importing the module vi.mock('posthog-node', () => { @@ -26,8 +25,7 @@ describe('telemetry/index', () => { beforeEach(() => { // Create unique temp directory for each test using UUID - tempDir = path.join(os.tmpdir(), `openspec-telemetry-test-${randomUUID()}`); - fs.mkdirSync(tempDir, { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-telemetry-test-')); // Save original env originalEnv = { ...process.env }; diff --git a/test/utils/change-metadata.test.ts b/test/utils/change-metadata.test.ts index 6d920465ee..0082d03d73 100644 --- a/test/utils/change-metadata.test.ts +++ b/test/utils/change-metadata.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { writeChangeMetadata, readChangeMetadata, @@ -141,7 +140,7 @@ describe('writeChangeMetadata', () => { let changeDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); changeDir = path.join(testDir, 'openspec', 'changes', 'test-change'); await fs.mkdir(changeDir, { recursive: true }); }); @@ -178,7 +177,7 @@ describe('readChangeMetadata', () => { let changeDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); changeDir = path.join(testDir, 'openspec', 'changes', 'test-change'); await fs.mkdir(changeDir, { recursive: true }); }); @@ -255,7 +254,7 @@ describe('resolveSchemaForChange', () => { let changeDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); changeDir = path.join(testDir, 'openspec', 'changes', 'test-change'); await fs.mkdir(changeDir, { recursive: true }); }); diff --git a/test/utils/change-utils.test.ts b/test/utils/change-utils.test.ts index 587a7b75f6..f76a800e7a 100644 --- a/test/utils/change-utils.test.ts +++ b/test/utils/change-utils.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { validateChangeName, createChange } from '../../src/utils/change-utils.js'; describe('validateChangeName', () => { @@ -113,8 +112,7 @@ describe('createChange', () => { const originalTimeZone = process.env.TZ; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); }); afterEach(async () => { diff --git a/test/utils/file-system.test.ts b/test/utils/file-system.test.ts index 5cc670e90b..509030f6aa 100644 --- a/test/utils/file-system.test.ts +++ b/test/utils/file-system.test.ts @@ -3,15 +3,13 @@ import * as nodeFs from 'fs'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; -import { randomUUID } from 'crypto'; import { FileSystemUtils } from '../../src/utils/file-system.js'; describe('FileSystemUtils', () => { let testDir: string; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-test-')); }); afterEach(async () => { diff --git a/test/utils/marker-updates.test.ts b/test/utils/marker-updates.test.ts index da9a06b6e9..75476aef96 100644 --- a/test/utils/marker-updates.test.ts +++ b/test/utils/marker-updates.test.ts @@ -10,8 +10,7 @@ describe('FileSystemUtils.updateFileWithMarkers', () => { const END_MARKER = ''; beforeEach(async () => { - testDir = path.join(os.tmpdir(), `openspec-marker-test-${Date.now()}`); - await fs.mkdir(testDir, { recursive: true }); + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-marker-test-')); }); afterEach(async () => { diff --git a/test/utils/task-progress.test.ts b/test/utils/task-progress.test.ts index 33f89794a9..7f714b546b 100644 --- a/test/utils/task-progress.test.ts +++ b/test/utils/task-progress.test.ts @@ -36,7 +36,7 @@ describe('getTaskProgressForChange (#1202 tracked-tasks resolution)', () => { ].join('\n'); beforeEach(async () => { - projectRoot = path.join(os.tmpdir(), `openspec-taskprogress-${Date.now()}-${Math.round(performance.now())}`); + projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-taskprogress-')); changesDir = path.join(projectRoot, 'openspec', 'changes'); await fs.mkdir(changesDir, { recursive: true }); }); From bca10fb186fc9c892272de2beba9a47ff5540bce Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 23 Jul 2026 10:50:32 -0500 Subject: [PATCH 2/3] chore(deps): override postcss and sharp in the website MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two advisories on the docs site have no Dependabot PR and never will: Next pins postcss at 8.4.31 as a direct dependency and sharp at ^0.34.5 as an optional one, so Dependabot cannot raise either without a Next release that does it first. 16.2.11 does not — it still pins both. Left alone these sit open indefinitely. postcss 8.4.31 -> 8.5.22 GHSA-qx2v-qp2m-jg93 (XSS via unescaped ) sharp 0.34.5 -> 0.35.3 GHSA-f88m-g3jw-g9cj (4 libvips CVEs) A version-ranged selector (`postcss@<8.5.10`) was the first instinct, since it lapses on its own once Next moves past it. It is the wrong tool: it pins the override to one advisory's floor, and silently stops applying when the next advisory raises that floor. GHSA-6g55-p6wh-862q landed while this branch was open and moved postcss's patched floor to 8.5.12 — under the ranged selector a dependency pinning 8.5.11 would have resolved to 8.5.11 and stayed vulnerable. A plain floor cannot under-match, so that is what this uses. The floor also covers the new advisory: 8.5.22 is past 8.5.12. postcss dedupes to the single copy the site already had for Tailwind. These are the last two open Dependabot alerts on the repo. `pnpm audit` on website/ goes from 2 advisories to "No known vulnerabilities found". The site builds clean, OG image generation included, and the package set grows by exactly two platform-gated wasm32 binaries that never install on CI. Co-Authored-By: Claude Opus 4.8 (1M context) --- website/package.json | 6 + website/pnpm-lock.yaml | 321 ++++++++++++++++++++++------------------- 2 files changed, 177 insertions(+), 150 deletions(-) diff --git a/website/package.json b/website/package.json index e9ee6c89d0..eaf7f1159d 100644 --- a/website/package.json +++ b/website/package.json @@ -31,5 +31,11 @@ "serve": "^14.2.6", "tailwindcss": "^4.3.1", "typescript": "^6.0.3" + }, + "pnpm": { + "overrides": { + "postcss": "^8.5.22", + "sharp": "^0.35.3" + } } } diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index eda1bb3bda..97fcd609eb 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -4,6 +4,10 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + postcss: ^8.5.22 + sharp: ^0.35.3 + importers: .: @@ -13,19 +17,19 @@ importers: version: 3.1.18 fumadocs-core: specifier: ^16.10.7 - version: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) + version: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) fumadocs-mdx: specifier: ^15.0.13 - version: 15.2.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) + version: 15.2.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) fumadocs-ui: specifier: ^16.10.7 - version: 16.11.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3) + version: 16.11.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3) lucide-react: specifier: ^1.22.0 version: 1.25.0(react@19.2.8) next: specifier: 16.2.11 - version: 16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: specifier: ^19.2.7 version: 19.2.8 @@ -52,7 +56,7 @@ importers: specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.17) postcss: - specifier: ^8.5.15 + specifier: ^8.5.22 version: 8.5.22 serve: specifier: ^14.2.6 @@ -266,136 +270,145 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-arm64@0.35.3': + resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-darwin-x64@0.35.3': + resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-freebsd-wasm32@0.35.3': + resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.2': + resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.3.2': + resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.3.2': + resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.3.2': + resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.3.2': + resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + '@img/sharp-libvips-linux-riscv64@1.3.2': + resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} cpu: [riscv64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.3.2': + resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.3.2': + resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm64@0.35.3': + resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-arm@0.35.3': + resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-ppc64@0.35.3': + resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-riscv64@0.35.3': + resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-s390x@0.35.3': + resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linux-x64@0.35.3': + resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-arm64@0.35.3': + resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-linuxmusl-x64@0.35.3': + resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-wasm32@0.35.3': + resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.3': + resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.3': + resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-ia32@0.35.3': + resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.3': + resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -1949,10 +1962,6 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.22: resolution: {integrity: sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==} engines: {node: ^10 || ^12 || >=14} @@ -2091,9 +2100,14 @@ packages: engines: {node: '>= 14'} hasBin: true - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.3: + resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -2418,98 +2432,108 @@ snapshots: '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@img/sharp-darwin-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.3.2 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.3.2 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.3.2': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.3.2': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.3.2': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.3.2': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.3.2': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-riscv64@1.3.2': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-s390x@1.3.2': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linux-x64@1.3.2': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.3.2': optional: true - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.3.2 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.3.2 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-ppc64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.3.2 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-riscv64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.3.2 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linux-s390x@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.3.2 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linux-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.3.2 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': + '@img/sharp-linuxmusl-arm64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.35.3': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 optional: true - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.35.3': dependencies: '@emnapi/runtime': 1.11.2 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-webcontainers-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 + optional: true + + '@img/sharp-win32-arm64@0.35.3': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-ia32@0.35.3': optional: true - '@img/sharp-win32-x64@0.34.5': + '@img/sharp-win32-x64@0.35.3': optional: true '@jridgewell/gen-mapping@0.3.13': @@ -3434,7 +3458,7 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3): + fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 @@ -3460,21 +3484,21 @@ snapshots: '@types/mdast': 4.0.4 '@types/react': 19.2.17 lucide-react: 1.25.0(react@19.2.8) - next: 16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next: 16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) zod: 4.4.3 transitivePeerDependencies: - supports-color - fumadocs-mdx@15.2.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): + fumadocs-mdx@15.2.0(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.1 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) + fumadocs-core: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) github-slugger: 2.0.0 magic-string: 0.30.21 mdast-util-mdx: 3.0.0 @@ -3493,12 +3517,12 @@ snapshots: '@types/mdast': 4.0.4 '@types/mdx': 2.0.14 '@types/react': 19.2.17 - next: 16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next: 16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 transitivePeerDependencies: - supports-color - fumadocs-ui@16.11.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3): + fumadocs-ui@16.11.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.3.3): dependencies: '@fuma-translate/react': 1.0.2(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@fumadocs/tailwind': 0.1.1(tailwindcss@4.3.3) @@ -3514,7 +3538,7 @@ snapshots: '@radix-ui/react-tabs': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) class-variance-authority: 0.7.1 cnfast: 0.0.8 - fumadocs-core: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) + fumadocs-core: 16.11.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.25.0(react@19.2.8))(next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3) lucide-react: 1.25.0(react@19.2.8) motion: 12.42.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) next-themes: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -3528,7 +3552,7 @@ snapshots: optionalDependencies: '@types/mdx': 2.0.14 '@types/react': 19.2.17 - next: 16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next: 16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) transitivePeerDependencies: - '@emotion/is-prop-valid' - '@types/react-dom' @@ -4226,13 +4250,13 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - next@16.2.11(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@16.2.11(@types/node@26.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@next/env': 16.2.11 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.11.1 caniuse-lite: 1.0.30001806 - postcss: 8.4.31 + postcss: 8.5.22 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) styled-jsx: 5.1.6(react@19.2.8) @@ -4245,9 +4269,10 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.11 '@next/swc-win32-arm64-msvc': 16.2.11 '@next/swc-win32-x64-msvc': 16.2.11 - sharp: 0.34.5 + sharp: 0.35.3(@types/node@26.1.1) transitivePeerDependencies: - '@babel/core' + - '@types/node' - babel-plugin-macros npm-run-path@4.0.1: @@ -4292,12 +4317,6 @@ snapshots: picomatch@4.0.5: {} - postcss@8.4.31: - dependencies: - nanoid: 3.3.16 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.22: dependencies: nanoid: 3.3.16 @@ -4502,36 +4521,38 @@ snapshots: transitivePeerDependencies: - supports-color - sharp@0.34.5: + sharp@0.35.3(@types/node@26.1.1): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.35.3 + '@img/sharp-darwin-x64': 0.35.3 + '@img/sharp-freebsd-wasm32': 0.35.3 + '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-linux-arm': 0.35.3 + '@img/sharp-linux-arm64': 0.35.3 + '@img/sharp-linux-ppc64': 0.35.3 + '@img/sharp-linux-riscv64': 0.35.3 + '@img/sharp-linux-s390x': 0.35.3 + '@img/sharp-linux-x64': 0.35.3 + '@img/sharp-linuxmusl-arm64': 0.35.3 + '@img/sharp-linuxmusl-x64': 0.35.3 + '@img/sharp-webcontainers-wasm32': 0.35.3 + '@img/sharp-win32-arm64': 0.35.3 + '@img/sharp-win32-ia32': 0.35.3 + '@img/sharp-win32-x64': 0.35.3 + '@types/node': 26.1.1 optional: true shebang-command@2.0.0: From 3980e6f86836b069756f1df0134d031ffea955ac Mon Sep 17 00:00:00 2001 From: Clay Good Date: Thu, 23 Jul 2026 10:50:32 -0500 Subject: [PATCH 3/3] ci(security): audit the docs site too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both `pnpm audit` steps run at the repo root. The docs site keeps its own lockfile and is not a workspace member, so neither step could see it — the root audit passed all the way through while postcss and sharp sat open in website/. That blind spot is why they needed a manual override to find. Blocking rule copied from the published-dependency audit: advisory on pull requests, blocking on the weekly schedule and on pushes to main. An always-advisory step would only relocate the blind spot — the sweep would stay green with a live advisory and someone would have to read the log of a passing run to notice. `!cancelled()` because the two audits above can fail hard. Without it a root advisory would skip this step entirely, in exactly the situation where the site's own state matters most. Verified against the pre-fix lockfile — the step reports the two advisories it would have caught: 2 vulnerabilities found Severity: 1 moderate | 1 high and reports "No known vulnerabilities found" against the fixed one. Confirmed it reads website/pnpm-lock.yaml and not the root: with a vulnerable website lockfile and a clean root, it exits 1; a missing website lockfile is a hard ERR_PNPM_AUDIT_NO_LOCKFILE rather than a false clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/security.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 1b31bb8394..2d57c2807f 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -73,3 +73,18 @@ jobs: - name: Audit build and test tooling continue-on-error: true run: pnpm audit --audit-level high + + # The docs site keeps its own lockfile and is not a workspace member, so + # neither audit above can see it. Without this step a website advisory is + # invisible — which is how two of them sat open long enough to need a + # manual override. + # + # Same blocking rule as the published-dependency audit: advisory on pull + # requests, blocking on the weekly schedule and on pushes to main. Green + # here has to mean the site is clean, or the step just relocates the blind + # spot into a passing log. `!cancelled()` because the two audits above can + # fail hard, and a root advisory must not silently skip this one. + - name: Audit documentation site + if: ${{ !cancelled() }} + continue-on-error: ${{ github.event_name == 'pull_request' }} + run: pnpm audit --audit-level high --dir website