mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-13 20:28:36 +08:00
fix: normalize cursor rule installs
This commit is contained in:
parent
bb96fdc9dc
commit
7374ef6a73
@ -1,11 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
createFlatRuleOperations,
|
||||
createInstallTargetAdapter,
|
||||
createManagedOperation,
|
||||
isForeignPlatformPath,
|
||||
} = require('./helpers');
|
||||
|
||||
function toCursorRuleFileName(fileName, sourceRelativeFile) {
|
||||
if (path.basename(sourceRelativeFile).toLowerCase() === 'readme.md') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fileName.endsWith('.md')
|
||||
? `${fileName.slice(0, -3)}.mdc`
|
||||
: fileName;
|
||||
}
|
||||
|
||||
module.exports = createInstallTargetAdapter({
|
||||
id: 'cursor-project',
|
||||
target: 'cursor',
|
||||
@ -40,14 +52,37 @@ module.exports = createInstallTargetAdapter({
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
destinationNameTransform(fileName) {
|
||||
return fileName.endsWith('.md')
|
||||
? `${fileName.slice(0, -3)}.mdc`
|
||||
: fileName;
|
||||
},
|
||||
destinationNameTransform: toCursorRuleFileName,
|
||||
});
|
||||
}
|
||||
|
||||
if (sourceRelativePath === '.cursor') {
|
||||
const cursorRoot = path.join(repoRoot, '.cursor');
|
||||
if (!fs.existsSync(cursorRoot) || !fs.statSync(cursorRoot).isDirectory()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const childOperations = fs.readdirSync(cursorRoot, { withFileTypes: true })
|
||||
.sort((left, right) => left.name.localeCompare(right.name))
|
||||
.filter(entry => entry.name !== 'rules')
|
||||
.map(entry => createManagedOperation({
|
||||
moduleId: module.id,
|
||||
sourceRelativePath: path.join('.cursor', entry.name),
|
||||
destinationPath: path.join(targetRoot, entry.name),
|
||||
strategy: 'preserve-relative-path',
|
||||
}));
|
||||
|
||||
const ruleOperations = createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath: '.cursor/rules',
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
destinationNameTransform: toCursorRuleFileName,
|
||||
});
|
||||
|
||||
return [...childOperations, ...ruleOperations];
|
||||
}
|
||||
|
||||
return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)];
|
||||
});
|
||||
});
|
||||
|
||||
@ -208,23 +208,31 @@ function createFlatRuleOperations({
|
||||
const relativeFiles = listRelativeFiles(entryPath);
|
||||
for (const relativeFile of relativeFiles) {
|
||||
const defaultFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
||||
const sourceRelativeFile = path.join(normalizedSourcePath, namespace, relativeFile);
|
||||
const flattenedFileName = typeof destinationNameTransform === 'function'
|
||||
? destinationNameTransform(defaultFileName)
|
||||
? destinationNameTransform(defaultFileName, sourceRelativeFile)
|
||||
: defaultFileName;
|
||||
if (!flattenedFileName) {
|
||||
continue;
|
||||
}
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile),
|
||||
sourceRelativePath: sourceRelativeFile,
|
||||
destinationPath: path.join(destinationDir, flattenedFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const sourceRelativeFile = path.join(normalizedSourcePath, entry.name);
|
||||
const destinationFileName = typeof destinationNameTransform === 'function'
|
||||
? destinationNameTransform(entry.name)
|
||||
? destinationNameTransform(entry.name, sourceRelativeFile)
|
||||
: entry.name;
|
||||
if (!destinationFileName) {
|
||||
continue;
|
||||
}
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, entry.name),
|
||||
sourceRelativePath: sourceRelativeFile,
|
||||
destinationPath: path.join(destinationDir, destinationFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
|
||||
@ -90,14 +90,16 @@ function runTests() {
|
||||
assert.strictEqual(plan.targetRoot, path.join(projectRoot, '.cursor'));
|
||||
assert.strictEqual(plan.installStatePath, path.join(projectRoot, '.cursor', 'ecc-install-state.json'));
|
||||
|
||||
const flattened = plan.operations.find(operation => operation.sourceRelativePath === '.cursor');
|
||||
const hooksJson = plan.operations.find(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === '.cursor/hooks.json'
|
||||
));
|
||||
const preserved = plan.operations.find(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md'
|
||||
));
|
||||
|
||||
assert.ok(flattened, 'Should include .cursor scaffold operation');
|
||||
assert.strictEqual(flattened.strategy, 'sync-root-children');
|
||||
assert.strictEqual(flattened.destinationPath, path.join(projectRoot, '.cursor'));
|
||||
assert.ok(hooksJson, 'Should preserve non-rule Cursor platform config files');
|
||||
assert.strictEqual(hooksJson.strategy, 'preserve-relative-path');
|
||||
assert.strictEqual(hooksJson.destinationPath, path.join(projectRoot, '.cursor', 'hooks.json'));
|
||||
|
||||
assert.ok(preserved, 'Should include flattened rules scaffold operations');
|
||||
assert.strictEqual(preserved.strategy, 'flatten-copy');
|
||||
@ -149,6 +151,62 @@ function runTests() {
|
||||
)),
|
||||
'Should not emit .md Cursor rule files'
|
||||
);
|
||||
assert.ok(
|
||||
!plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === 'rules/README.md'
|
||||
)),
|
||||
'Should not install Cursor README docs as runtime rule files'
|
||||
);
|
||||
assert.ok(
|
||||
!plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === 'rules/zh/README.md'
|
||||
)),
|
||||
'Should not flatten localized README docs into Cursor rule files'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans cursor platform rule files as .mdc and excludes rule README docs', () => {
|
||||
const repoRoot = path.join(__dirname, '..', '..');
|
||||
const projectRoot = '/workspace/app';
|
||||
|
||||
const plan = planInstallTargetScaffold({
|
||||
target: 'cursor',
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
modules: [
|
||||
{
|
||||
id: 'platform-configs',
|
||||
paths: ['.cursor'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === '.cursor/rules/common-agents.md'
|
||||
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-agents.mdc')
|
||||
)),
|
||||
'Should rename Cursor platform rule files to .mdc'
|
||||
);
|
||||
assert.ok(
|
||||
!plan.operations.some(operation => (
|
||||
operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-agents.md')
|
||||
)),
|
||||
'Should not preserve .md Cursor platform rule files'
|
||||
);
|
||||
assert.ok(
|
||||
plan.operations.some(operation => (
|
||||
normalizedRelativePath(operation.sourceRelativePath) === '.cursor/hooks.json'
|
||||
&& operation.destinationPath === path.join(projectRoot, '.cursor', 'hooks.json')
|
||||
)),
|
||||
'Should preserve non-rule Cursor platform config files'
|
||||
);
|
||||
assert.ok(
|
||||
!plan.operations.some(operation => (
|
||||
operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'README.mdc')
|
||||
)),
|
||||
'Should not emit Cursor rule README docs as .mdc files'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('plans antigravity remaps for workflows, skills, and flat rules', () => {
|
||||
|
||||
@ -132,6 +132,9 @@ function runTests() {
|
||||
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.mdc')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'typescript-testing.mdc')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.mdc')));
|
||||
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.md')));
|
||||
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'README.mdc')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'agents', 'architect.md')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));
|
||||
@ -304,7 +307,8 @@ function runTests() {
|
||||
});
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.md')));
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.mdc')));
|
||||
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-agents.md')));
|
||||
|
||||
const state = readJson(path.join(projectDir, '.cursor', 'ecc-install-state.json'));
|
||||
assert.strictEqual(state.request.profile, null);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user