Skip to content

Commit 0cc9a9c

Browse files
committed
Update deletion guard configs
1 parent c5482b8 commit 0cc9a9c

16 files changed

Lines changed: 414 additions & 175 deletions

cli/src/ProtectedDeletionGuard.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as os from 'node:os'
44
import * as path from 'node:path'
55
import process from 'node:process'
66
import glob from 'fast-glob'
7+
import {collectKnownPublicConfigDefinitionPaths} from './public-config-paths'
78

89
interface DirPathLike {
910
readonly path: string
@@ -67,22 +68,6 @@ export class ProtectedDeletionGuardError extends Error {
6768
}
6869
}
6970

70-
const KNOWN_AINDEX_INPUT_CONFIG_RELATIVE_PATHS = [
71-
'.editorconfig',
72-
'.vscode/settings.json',
73-
'.vscode/extensions.json',
74-
'.idea/codeStyles/Project.xml',
75-
'.idea/codeStyles/codeStyleConfig.xml',
76-
'.idea/.gitignore',
77-
'.qoderignore',
78-
'.cursorignore',
79-
'.warpindexignore',
80-
'.aiignore',
81-
'.codeiumignore',
82-
'.kiroignore',
83-
'.traeignore'
84-
] as const
85-
8671
const CONFIGURED_AINDEX_DIRECTORY_KEYS = [
8772
'skills',
8873
'commands',
@@ -323,7 +308,7 @@ function collectResolvedAindexRules(aindexDir: string): ProtectedPathRule[] {
323308
}
324309

325310
export function collectKnownAindexInputConfigPaths(aindexDir: string): string[] {
326-
return KNOWN_AINDEX_INPUT_CONFIG_RELATIVE_PATHS.map(relativePath => path.join(aindexDir, relativePath))
311+
return collectKnownPublicConfigDefinitionPaths(aindexDir)
327312
}
328313

329314
export function collectConfiguredAindexInputRules(

cli/src/commands/CleanupUtils.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ function createMockOutputPlugin(name: string, outputs: readonly string[], cleanu
6666

6767
describe('collectDeletionTargets', () => {
6868
it('throws when an output path matches a protected input source file', async () => {
69-
const editorSource = path.resolve('tmp-aindex/.editorconfig')
70-
const ignoreSource = path.resolve('tmp-aindex/.cursorignore')
69+
const editorSource = path.resolve('tmp-aindex/public/.editorconfig')
70+
const ignoreSource = path.resolve('tmp-aindex/public/.cursorignore')
7171

7272
const ctx = createCleanContext({
7373
editorConfigFiles: [{
@@ -107,7 +107,7 @@ describe('collectDeletionTargets', () => {
107107

108108
it('throws when an output path matches a known aindex protected config file', async () => {
109109
const aindexDir = path.resolve('tmp-aindex')
110-
const editorConfigOutput = path.resolve(aindexDir, '.editorconfig')
110+
const editorConfigOutput = path.resolve(aindexDir, 'public', '.editorconfig')
111111
const ctx = createCleanContext({aindexDir})
112112
const plugin = createMockOutputPlugin('MockOutputPlugin', [editorConfigOutput])
113113

cli/src/commands/HelpCommand.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ USAGE:
1212
${CLI_NAME} Run the sync pipeline (default)
1313
${CLI_NAME} help Show this help message
1414
${CLI_NAME} version Show version information
15-
${CLI_NAME} init Initialize directories and files
15+
${CLI_NAME} init Deprecated; no longer initializes aindex
1616
${CLI_NAME} dry-run Preview what would be written
1717
${CLI_NAME} clean Remove all generated files
1818
${CLI_NAME} clean --dry-run Preview what would be cleaned
@@ -21,7 +21,7 @@ USAGE:
2121
SUBCOMMANDS:
2222
help Show this help message
2323
version Show version information
24-
init Initialize directory structure based on configuration
24+
init Deprecated; keep public target-relative definitions manually
2525
dry-run Preview changes without writing files
2626
clean Remove all generated output files and directories
2727
config Set configuration values in global config file (~/.aindex/.tnmsc.json)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type {CommandContext} from './Command'
2+
import * as fs from 'node:fs'
3+
import * as path from 'node:path'
4+
import glob from 'fast-glob'
5+
import {describe, expect, it} from 'vitest'
6+
import {mergeConfig} from '../config'
7+
import {createLogger, FilePathKind} from '../plugins/plugin-core'
8+
import {InitCommand} from './InitCommand'
9+
10+
function createCommandContext(): CommandContext {
11+
const workspaceDir = path.resolve('tmp-init-command')
12+
const userConfigOptions = mergeConfig({workspaceDir})
13+
14+
return {
15+
logger: createLogger('InitCommandTest', 'error'),
16+
outputPlugins: [],
17+
userConfigOptions,
18+
collectedOutputContext: {
19+
workspace: {
20+
directory: {
21+
pathKind: FilePathKind.Absolute,
22+
path: workspaceDir,
23+
getDirectoryName: () => path.basename(workspaceDir),
24+
getAbsolutePath: () => workspaceDir
25+
},
26+
projects: []
27+
}
28+
},
29+
createCleanContext: dryRun => ({
30+
logger: createLogger('InitCommandTest', 'error'),
31+
fs,
32+
path,
33+
glob,
34+
dryRun,
35+
collectedOutputContext: {
36+
workspace: {
37+
directory: {
38+
pathKind: FilePathKind.Absolute,
39+
path: workspaceDir,
40+
getDirectoryName: () => path.basename(workspaceDir),
41+
getAbsolutePath: () => workspaceDir
42+
},
43+
projects: []
44+
}
45+
}
46+
}) as CommandContext['createCleanContext'] extends (dryRun: boolean) => infer T ? T : never,
47+
createWriteContext: dryRun => ({
48+
logger: createLogger('InitCommandTest', 'error'),
49+
fs,
50+
path,
51+
glob,
52+
dryRun,
53+
collectedOutputContext: {
54+
workspace: {
55+
directory: {
56+
pathKind: FilePathKind.Absolute,
57+
path: workspaceDir,
58+
getDirectoryName: () => path.basename(workspaceDir),
59+
getAbsolutePath: () => workspaceDir
60+
},
61+
projects: []
62+
}
63+
}
64+
}) as CommandContext['createWriteContext'] extends (dryRun: boolean) => infer T ? T : never
65+
}
66+
}
67+
68+
describe('init command', () => {
69+
it('returns a deprecation failure without creating files', async () => {
70+
const result = await new InitCommand().execute(createCommandContext())
71+
72+
expect(result.success).toBe(false)
73+
expect(result.filesAffected).toBe(0)
74+
expect(result.dirsAffected).toBe(0)
75+
expect(result.message).toContain('deprecated')
76+
expect(result.message).toContain('~/workspace/aindex/public/')
77+
})
78+
})

cli/src/commands/InitCommand.ts

Lines changed: 7 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,20 @@
11
import type {Command, CommandContext, CommandResult} from './Command'
2-
import * as os from 'node:os'
3-
import * as path from 'node:path'
4-
import process from 'node:process'
5-
import {generateAindex} from '@/Aindex'
6-
import {DEFAULT_CONFIG_FILE_NAME, ensureConfigLink, getGlobalConfigPath} from '@/ConfigLoader'
72

8-
function resolveWorkspacePath(workspaceDir: string): string {
9-
if (workspaceDir === '~') return os.homedir()
10-
if (workspaceDir.startsWith('~/') || workspaceDir.startsWith('~\\')) return path.join(os.homedir(), workspaceDir.slice(2))
11-
return path.normalize(workspaceDir)
12-
}
13-
14-
function linkCwdConfig(logger: CommandContext['logger']): void {
15-
const globalConfigPath = getGlobalConfigPath()
16-
const cwdConfigPath = path.join(process.cwd(), DEFAULT_CONFIG_FILE_NAME)
17-
ensureConfigLink(cwdConfigPath, globalConfigPath, logger)
18-
}
3+
const INIT_DEPRECATION_MESSAGE = '`tnmsc init` is deprecated and no longer initializes aindex. Maintain the public target-relative definitions manually under `~/workspace/aindex/public/`.'
194

205
export class InitCommand implements Command {
216
readonly name = 'init'
227

238
async execute(ctx: CommandContext): Promise<CommandResult> {
24-
const {logger, userConfigOptions} = ctx
25-
26-
logger.info('initializing aindex structure', {command: 'init'})
27-
28-
const workspaceDir = resolveWorkspacePath(userConfigOptions.workspaceDir)
29-
const aindexDir = path.join(workspaceDir, userConfigOptions.aindex.dir)
30-
31-
const result = generateAindex(aindexDir, {logger, config: userConfigOptions.aindex})
32-
try {
33-
linkCwdConfig(logger)
34-
}
35-
catch (error) {
36-
const errorMessage = error instanceof Error ? error.message : String(error)
37-
return {
38-
success: false,
39-
filesAffected: result.createdFiles.length,
40-
dirsAffected: result.createdDirs.length,
41-
message: errorMessage
42-
}
43-
}
44-
45-
const message = result.createdDirs.length === 0 && result.createdFiles.length === 0
46-
? `All ${result.existedDirs.length} directories and ${result.existedFiles.length} files already exist`
47-
: `Created ${result.createdDirs.length} directories and ${result.createdFiles.length} files (${result.existedDirs.length} dirs, ${result.existedFiles.length} files already existed)`
9+
const {logger} = ctx
4810

49-
logger.info('initialization complete', {
50-
dirsCreated: result.createdDirs.length,
51-
filesCreated: result.createdFiles.length,
52-
dirsExisted: result.existedDirs.length,
53-
filesExisted: result.existedFiles.length
54-
})
11+
logger.warn('deprecated init command invoked', {command: 'init'})
5512

5613
return {
57-
success: result.success,
58-
filesAffected: result.createdFiles.length,
59-
dirsAffected: result.createdDirs.length,
60-
message
14+
success: false,
15+
filesAffected: 0,
16+
dirsAffected: 0,
17+
message: INIT_DEPRECATION_MESSAGE
6118
}
6219
}
6320
}

cli/src/inputs/input-editorconfig.ts

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
1-
import type {InputCollectedContext, InputPluginContext, ProjectIDEConfigFile} from '../plugins/plugin-core'
2-
import {AbstractInputPlugin, FilePathKind, IDEKind} from '../plugins/plugin-core'
3-
4-
function readIdeConfigFile<T extends IDEKind>(
5-
type: T,
6-
relativePath: string,
7-
aindexDir: string,
8-
fs: typeof import('node:fs'),
9-
path: typeof import('node:path')
10-
): ProjectIDEConfigFile<T> | undefined {
11-
const absPath = path.join(aindexDir, relativePath)
12-
if (!(fs.existsSync(absPath) && fs.statSync(absPath).isFile())) return void 0
13-
14-
const content = fs.readFileSync(absPath, 'utf8')
15-
return {
16-
type,
17-
content,
18-
length: content.length,
19-
filePathKind: FilePathKind.Absolute,
20-
dir: {
21-
pathKind: FilePathKind.Absolute,
22-
path: absPath,
23-
getDirectoryName: () => path.basename(absPath)
24-
}
25-
}
26-
}
1+
import type {InputCollectedContext} from '../plugins/plugin-core'
2+
import {readPublicIdeConfigDefinitionFile} from '../public-config-paths'
3+
import {AbstractInputPlugin, IDEKind, type InputPluginContext, type ProjectIDEConfigFile} from '../plugins/plugin-core'
274

285
export class EditorConfigInputPlugin extends AbstractInputPlugin {
296
constructor() {
307
super('EditorConfigInputPlugin')
318
}
329

3310
collect(ctx: InputPluginContext): Partial<InputCollectedContext> {
34-
const {userConfigOptions, fs, path} = ctx
11+
const {userConfigOptions, fs} = ctx
3512
const {aindexDir} = this.resolveBasePaths(userConfigOptions)
3613

3714
const editorConfigFiles: ProjectIDEConfigFile<IDEKind.EditorConfig>[] = []
38-
const file = readIdeConfigFile(IDEKind.EditorConfig, '.editorconfig', aindexDir, fs, path)
15+
const file = readPublicIdeConfigDefinitionFile(IDEKind.EditorConfig, '.editorconfig', aindexDir, fs)
3916
if (file != null) editorConfigFiles.push(file)
4017

4118
return {editorConfigFiles}

cli/src/inputs/input-git-exclude.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {InputCollectedContext, InputPluginContext} from '../plugins/plugin-core'
2-
import * as path from 'node:path'
2+
import {PUBLIC_GIT_EXCLUDE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
33
import {AbstractInputPlugin} from '../plugins/plugin-core'
44

55
export class GitExcludeInputPlugin extends AbstractInputPlugin {
@@ -9,7 +9,7 @@ export class GitExcludeInputPlugin extends AbstractInputPlugin {
99

1010
collect(ctx: InputPluginContext): Partial<InputCollectedContext> {
1111
const {aindexDir} = this.resolveBasePaths(ctx.userConfigOptions)
12-
const filePath = path.join(aindexDir, 'public', 'exclude')
12+
const filePath = resolvePublicDefinitionPath(aindexDir, PUBLIC_GIT_EXCLUDE_TARGET_RELATIVE_PATH)
1313

1414
if (!ctx.fs.existsSync(filePath)) {
1515
this.log.debug({action: 'collect', message: 'File not found', path: filePath})

cli/src/inputs/input-gitignore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {InputCollectedContext, InputPluginContext} from '../plugins/plugin-core'
2-
import * as path from 'node:path'
2+
import {PUBLIC_GIT_IGNORE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
33
import {AbstractInputPlugin} from '../plugins/plugin-core'
44

55
export class GitIgnoreInputPlugin extends AbstractInputPlugin {
@@ -9,7 +9,7 @@ export class GitIgnoreInputPlugin extends AbstractInputPlugin {
99

1010
collect(ctx: InputPluginContext): Partial<InputCollectedContext> {
1111
const {aindexDir} = this.resolveBasePaths(ctx.userConfigOptions)
12-
const filePath = path.join(aindexDir, 'public', 'gitignore')
12+
const filePath = resolvePublicDefinitionPath(aindexDir, PUBLIC_GIT_IGNORE_TARGET_RELATIVE_PATH)
1313

1414
if (!ctx.fs.existsSync(filePath)) {
1515
this.log.debug({action: 'collect', message: 'File not found', path: filePath})

cli/src/inputs/input-jetbrains-config.ts

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,14 @@
1-
import type {InputCollectedContext, InputPluginContext, ProjectIDEConfigFile} from '../plugins/plugin-core'
2-
import {AbstractInputPlugin, FilePathKind, IDEKind} from '../plugins/plugin-core'
3-
4-
function readIdeConfigFile<T extends IDEKind>(
5-
type: T,
6-
relativePath: string,
7-
aindexDir: string,
8-
fs: typeof import('node:fs'),
9-
path: typeof import('node:path')
10-
): ProjectIDEConfigFile<T> | undefined {
11-
const absPath = path.join(aindexDir, relativePath)
12-
if (!(fs.existsSync(absPath) && fs.statSync(absPath).isFile())) return void 0
13-
14-
const content = fs.readFileSync(absPath, 'utf8')
15-
return {
16-
type,
17-
content,
18-
length: content.length,
19-
filePathKind: FilePathKind.Absolute,
20-
dir: {
21-
pathKind: FilePathKind.Absolute,
22-
path: absPath,
23-
getDirectoryName: () => path.basename(absPath)
24-
}
25-
}
26-
}
1+
import type {InputCollectedContext} from '../plugins/plugin-core'
2+
import {readPublicIdeConfigDefinitionFile} from '../public-config-paths'
3+
import {AbstractInputPlugin, IDEKind, type InputPluginContext, type ProjectIDEConfigFile} from '../plugins/plugin-core'
274

285
export class JetBrainsConfigInputPlugin extends AbstractInputPlugin {
296
constructor() {
307
super('JetBrainsConfigInputPlugin')
318
}
329

3310
collect(ctx: InputPluginContext): Partial<InputCollectedContext> {
34-
const {userConfigOptions, fs, path} = ctx
11+
const {userConfigOptions, fs} = ctx
3512
const {aindexDir} = this.resolveBasePaths(userConfigOptions)
3613

3714
const files = [
@@ -42,7 +19,7 @@ export class JetBrainsConfigInputPlugin extends AbstractInputPlugin {
4219
const jetbrainsConfigFiles: ProjectIDEConfigFile<IDEKind.IntellijIDEA>[] = []
4320

4421
for (const relativePath of files) {
45-
const file = readIdeConfigFile(IDEKind.IntellijIDEA, relativePath, aindexDir, fs, path)
22+
const file = readPublicIdeConfigDefinitionFile(IDEKind.IntellijIDEA, relativePath, aindexDir, fs)
4623
if (file != null) jetbrainsConfigFiles.push(file)
4724
}
4825

0 commit comments

Comments
 (0)