Skip to content

Commit ffa2742

Browse files
committed
Rename workspace wording to project in docs
1 parent 674b97d commit ffa2742

36 files changed

Lines changed: 1721 additions & 880 deletions

cli/scripts/benchmark-cleanup.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ delete process.env['VITEST']
1010
delete process.env['VITEST_WORKER_ID']
1111

1212
const cleanupModule = await import('../src/commands/CleanupUtils')
13-
const fallbackModule = await import('../src/commands/CleanupUtils.fallback')
1413
const pluginCore = await import('../src/plugins/plugin-core')
1514

1615
function createMockLogger(): ILogger {
@@ -136,15 +135,9 @@ async function main(): Promise<void> {
136135
const iterations = 25
137136

138137
process.stdout.write(`cleanup benchmark iterations=${iterations}\n`)
139-
const fallbackAvg = await measure('fallback-plan', iterations, async () => {
140-
await fallbackModule.collectDeletionTargets([plugin], cleanCtx)
141-
})
142-
const nativeAvg = await measure('native-plan', iterations, async () => {
138+
await measure('native-plan', iterations, async () => {
143139
await cleanupModule.collectDeletionTargets([plugin], cleanCtx)
144140
})
145-
146-
const delta = nativeAvg - fallbackAvg
147-
process.stdout.write(`delta=${delta.toFixed(2)}ms (${((delta / fallbackAvg) * 100).toFixed(2)}%)\n`)
148141
}
149142
finally {
150143
fs.rmSync(tempDir, {recursive: true, force: true})

cli/scripts/cleanup-native-smoke.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ delete process.env['VITEST']
99
delete process.env['VITEST_WORKER_ID']
1010

1111
const cleanupModule = await import('../src/commands/CleanupUtils')
12-
const fallbackModule = await import('../src/commands/CleanupUtils.fallback')
1312
const pluginCore = await import('../src/plugins/plugin-core')
1413

1514
function createMockLogger(): ILogger {
@@ -106,21 +105,18 @@ async function main(): Promise<void> {
106105
const cleanCtx = createCleanContext(workspaceDir)
107106

108107
const nativePlan = await cleanupModule.collectDeletionTargets([plugin], cleanCtx)
109-
const fallbackPlan = await fallbackModule.collectDeletionTargets([plugin], cleanCtx)
110-
111-
const sortPaths = (value: {filesToDelete: string[], dirsToDelete: string[], excludedScanGlobs: string[]}) => ({
112-
...value,
113-
filesToDelete: [...value.filesToDelete].sort(),
114-
dirsToDelete: [...value.dirsToDelete].sort(),
115-
excludedScanGlobs: [...value.excludedScanGlobs].sort()
116-
})
117-
118-
if (JSON.stringify(sortPaths(nativePlan)) !== JSON.stringify(sortPaths(fallbackPlan))) {
119-
throw new Error(`Native cleanup plan mismatch.\nNative: ${JSON.stringify(nativePlan, null, 2)}\nFallback: ${JSON.stringify(fallbackPlan, null, 2)}`)
108+
expectSetEqual(nativePlan.filesToDelete, [rootOutput, childOutput], 'native cleanup plan files')
109+
expectSetEqual(nativePlan.dirsToDelete, [
110+
legacySkillDir,
111+
path.join(workspaceDir, 'project-a', 'commands'),
112+
path.join(workspaceDir, 'project-a')
113+
], 'native cleanup plan directories')
114+
if (nativePlan.violations.length > 0 || nativePlan.conflicts.length > 0) {
115+
throw new Error(`Unexpected native cleanup plan: ${JSON.stringify(nativePlan, null, 2)}`)
120116
}
121117

122118
const result = await cleanupModule.performCleanup([plugin], cleanCtx, createMockLogger())
123-
if (result.deletedFiles !== 2 || result.deletedDirs !== 1 || result.errors.length > 0) {
119+
if (result.deletedFiles !== 2 || result.deletedDirs !== 3 || result.errors.length > 0) {
124120
throw new Error(`Unexpected native cleanup result: ${JSON.stringify(result, null, 2)}`)
125121
}
126122

@@ -138,4 +134,12 @@ async function main(): Promise<void> {
138134
}
139135
}
140136

137+
function expectSetEqual(actual: readonly string[], expected: readonly string[], label: string): void {
138+
const actualSorted = [...actual].sort()
139+
const expectedSorted = [...expected].sort()
140+
if (JSON.stringify(actualSorted) !== JSON.stringify(expectedSorted)) {
141+
throw new Error(`Unexpected ${label}: ${JSON.stringify(actualSorted)} !== ${JSON.stringify(expectedSorted)}`)
142+
}
143+
}
144+
141145
await main()
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type * as fs from 'node:fs'
2+
import {resolveAbsolutePath} from '../ProtectedDeletionGuard'
3+
4+
const EMPTY_DIRECTORY_SCAN_EXCLUDED_BASENAMES = new Set([
5+
'.git',
6+
'node_modules',
7+
'dist',
8+
'target',
9+
'.next',
10+
'.turbo',
11+
'coverage',
12+
'.nyc_output',
13+
'.cache',
14+
'.vite',
15+
'.vite-temp',
16+
'.pnpm-store',
17+
'.yarn',
18+
'.idea',
19+
'.vscode'
20+
])
21+
22+
export interface WorkspaceEmptyDirectoryPlan {
23+
readonly emptyDirsToDelete: string[]
24+
}
25+
26+
export interface WorkspaceEmptyDirectoryPlannerOptions {
27+
readonly fs: typeof import('node:fs')
28+
readonly path: typeof import('node:path')
29+
readonly workspaceDir: string
30+
readonly filesToDelete: readonly string[]
31+
readonly dirsToDelete: readonly string[]
32+
}
33+
34+
function shouldSkipEmptyDirectoryTree(
35+
nodePath: typeof import('node:path'),
36+
workspaceDir: string,
37+
currentDir: string
38+
): boolean {
39+
if (currentDir === workspaceDir) return false
40+
return EMPTY_DIRECTORY_SCAN_EXCLUDED_BASENAMES.has(nodePath.basename(currentDir))
41+
}
42+
43+
export function planWorkspaceEmptyDirectoryCleanup(
44+
options: WorkspaceEmptyDirectoryPlannerOptions
45+
): WorkspaceEmptyDirectoryPlan {
46+
const workspaceDir = resolveAbsolutePath(options.workspaceDir)
47+
const filesToDelete = new Set(options.filesToDelete.map(resolveAbsolutePath))
48+
const dirsToDelete = new Set(options.dirsToDelete.map(resolveAbsolutePath))
49+
const emptyDirsToDelete = new Set<string>()
50+
51+
const collectEmptyDirectories = (currentDir: string): boolean => {
52+
if (dirsToDelete.has(currentDir)) return true
53+
if (shouldSkipEmptyDirectoryTree(options.path, workspaceDir, currentDir)) return false
54+
55+
let entries: fs.Dirent[]
56+
try {
57+
entries = options.fs.readdirSync(currentDir, {withFileTypes: true})
58+
}
59+
catch {
60+
return false
61+
}
62+
63+
let hasRetainedEntries = false
64+
65+
for (const entry of entries) {
66+
const entryPath = resolveAbsolutePath(options.path.join(currentDir, entry.name))
67+
68+
if (dirsToDelete.has(entryPath)) continue
69+
70+
if (entry.isDirectory()) {
71+
if (shouldSkipEmptyDirectoryTree(options.path, workspaceDir, entryPath)) {
72+
hasRetainedEntries = true
73+
continue
74+
}
75+
76+
if (collectEmptyDirectories(entryPath)) {
77+
emptyDirsToDelete.add(entryPath)
78+
continue
79+
}
80+
81+
hasRetainedEntries = true
82+
continue
83+
}
84+
85+
if (filesToDelete.has(entryPath)) continue
86+
hasRetainedEntries = true
87+
}
88+
89+
return !hasRetainedEntries
90+
}
91+
92+
collectEmptyDirectories(workspaceDir)
93+
94+
return {
95+
emptyDirsToDelete: [...emptyDirsToDelete].sort((a, b) => a.localeCompare(b))
96+
}
97+
}

cli/src/commands/CleanupUtils.adapter.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const nativeBindingMocks = vi.hoisted(() => ({
1212

1313
vi.mock('../core/native-binding', () => ({
1414
getNativeBinding: () => ({
15+
...globalThis.__TNMSC_TEST_NATIVE_BINDING__,
1516
planCleanup: nativeBindingMocks.planCleanup,
1617
performCleanup: nativeBindingMocks.performCleanup
1718
})
@@ -88,18 +89,20 @@ describe('cleanupUtils native adapter', () => {
8889
nativeBindingMocks.planCleanup.mockReturnValue(JSON.stringify({
8990
filesToDelete: ['/tmp/project-a/AGENTS.md'],
9091
dirsToDelete: ['/tmp/.codex/skills/legacy'],
92+
emptyDirsToDelete: ['/tmp/.codex/skills'],
9193
violations: [],
9294
conflicts: [],
9395
excludedScanGlobs: ['**/.git/**']
9496
}))
9597
nativeBindingMocks.performCleanup.mockReturnValue(JSON.stringify({
9698
deletedFiles: 1,
97-
deletedDirs: 1,
99+
deletedDirs: 2,
98100
errors: [],
99101
violations: [],
100102
conflicts: [],
101103
filesToDelete: ['/tmp/project-a/AGENTS.md'],
102104
dirsToDelete: ['/tmp/.codex/skills/legacy'],
105+
emptyDirsToDelete: ['/tmp/.codex/skills'],
103106
excludedScanGlobs: ['**/.git/**']
104107
}))
105108

@@ -113,7 +116,7 @@ describe('cleanupUtils native adapter', () => {
113116
const plan = await collectDeletionTargets([plugin], cleanCtx)
114117
expect(plan).toEqual({
115118
filesToDelete: ['/tmp/project-a/AGENTS.md'],
116-
dirsToDelete: ['/tmp/.codex/skills/legacy'],
119+
dirsToDelete: ['/tmp/.codex/skills', '/tmp/.codex/skills/legacy'],
117120
violations: [],
118121
conflicts: [],
119122
excludedScanGlobs: ['**/.git/**']
@@ -136,7 +139,7 @@ describe('cleanupUtils native adapter', () => {
136139
const result = await performCleanup([plugin], cleanCtx, createMockLogger())
137140
expect(result).toEqual({
138141
deletedFiles: 1,
139-
deletedDirs: 1,
142+
deletedDirs: 2,
140143
errors: [],
141144
violations: [],
142145
conflicts: []

0 commit comments

Comments
 (0)