mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-04 08:13:30 +08:00
feat: strengthen install lifecycle and target adapters (#512)
* fix: strengthen install lifecycle adapters * fix: restore template content on uninstall
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
const { createInstallTargetAdapter } = require('./helpers');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
createFlatRuleOperations,
|
||||
createInstallTargetAdapter,
|
||||
createManagedScaffoldOperation,
|
||||
} = require('./helpers');
|
||||
|
||||
module.exports = createInstallTargetAdapter({
|
||||
id: 'antigravity-project',
|
||||
@@ -6,4 +12,58 @@ module.exports = createInstallTargetAdapter({
|
||||
kind: 'project',
|
||||
rootSegments: ['.agent'],
|
||||
installStatePathSegments: ['ecc-install-state.json'],
|
||||
planOperations(input, adapter) {
|
||||
const modules = Array.isArray(input.modules)
|
||||
? input.modules
|
||||
: (input.module ? [input.module] : []);
|
||||
const {
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
homeDir,
|
||||
} = input;
|
||||
const planningInput = {
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
homeDir,
|
||||
};
|
||||
const targetRoot = adapter.resolveRoot(planningInput);
|
||||
|
||||
return modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths.flatMap(sourceRelativePath => {
|
||||
if (sourceRelativePath === 'rules') {
|
||||
return createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
});
|
||||
}
|
||||
|
||||
if (sourceRelativePath === 'commands') {
|
||||
return [
|
||||
createManagedScaffoldOperation(
|
||||
module.id,
|
||||
sourceRelativePath,
|
||||
path.join(targetRoot, 'workflows'),
|
||||
'preserve-relative-path'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (sourceRelativePath === 'agents') {
|
||||
return [
|
||||
createManagedScaffoldOperation(
|
||||
module.id,
|
||||
sourceRelativePath,
|
||||
path.join(targetRoot, 'skills'),
|
||||
'preserve-relative-path'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)];
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
const { createInstallTargetAdapter } = require('./helpers');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
createFlatRuleOperations,
|
||||
createInstallTargetAdapter,
|
||||
} = require('./helpers');
|
||||
|
||||
module.exports = createInstallTargetAdapter({
|
||||
id: 'cursor-project',
|
||||
@@ -7,4 +12,36 @@ module.exports = createInstallTargetAdapter({
|
||||
rootSegments: ['.cursor'],
|
||||
installStatePathSegments: ['ecc-install-state.json'],
|
||||
nativeRootRelativePath: '.cursor',
|
||||
planOperations(input, adapter) {
|
||||
const modules = Array.isArray(input.modules)
|
||||
? input.modules
|
||||
: (input.module ? [input.module] : []);
|
||||
const {
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
homeDir,
|
||||
} = input;
|
||||
const planningInput = {
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
homeDir,
|
||||
};
|
||||
const targetRoot = adapter.resolveRoot(planningInput);
|
||||
|
||||
return modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths.flatMap(sourceRelativePath => {
|
||||
if (sourceRelativePath === 'rules') {
|
||||
return createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
});
|
||||
}
|
||||
|
||||
return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)];
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
@@ -24,6 +25,182 @@ function resolveBaseRoot(scope, input = {}) {
|
||||
throw new Error(`Unsupported install target scope: ${scope}`);
|
||||
}
|
||||
|
||||
function buildValidationIssue(severity, code, message, extra = {}) {
|
||||
return {
|
||||
severity,
|
||||
code,
|
||||
message,
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function listRelativeFiles(dirPath, prefix = '') {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true }).sort((left, right) => (
|
||||
left.name.localeCompare(right.name)
|
||||
));
|
||||
const files = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPrefix = prefix ? path.join(prefix, entry.name) : entry.name;
|
||||
const absolutePath = path.join(dirPath, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...listRelativeFiles(absolutePath, entryPrefix));
|
||||
} else if (entry.isFile()) {
|
||||
files.push(normalizeRelativePath(entryPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function createManagedOperation({
|
||||
kind = 'copy-path',
|
||||
moduleId,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy = 'preserve-relative-path',
|
||||
ownership = 'managed',
|
||||
scaffoldOnly = true,
|
||||
...rest
|
||||
}) {
|
||||
return {
|
||||
kind,
|
||||
moduleId,
|
||||
sourceRelativePath: normalizeRelativePath(sourceRelativePath),
|
||||
destinationPath,
|
||||
strategy,
|
||||
ownership,
|
||||
scaffoldOnly,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
function defaultValidateAdapterInput(config, input = {}) {
|
||||
if (config.kind === 'project' && !input.projectRoot && !input.repoRoot) {
|
||||
return [
|
||||
buildValidationIssue(
|
||||
'error',
|
||||
'missing-project-root',
|
||||
'projectRoot or repoRoot is required for project install targets'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (config.kind === 'home' && !input.homeDir && !os.homedir()) {
|
||||
return [
|
||||
buildValidationIssue(
|
||||
'error',
|
||||
'missing-home-dir',
|
||||
'homeDir is required for home install targets'
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function createRemappedOperation(adapter, moduleId, sourceRelativePath, destinationPath, options = {}) {
|
||||
return createManagedOperation({
|
||||
kind: options.kind || 'copy-path',
|
||||
moduleId,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: options.strategy || 'preserve-relative-path',
|
||||
ownership: options.ownership || 'managed',
|
||||
scaffoldOnly: Object.hasOwn(options, 'scaffoldOnly') ? options.scaffoldOnly : true,
|
||||
...options.extra,
|
||||
});
|
||||
}
|
||||
|
||||
function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePath, input = {}) {
|
||||
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
||||
const sourceRoot = path.join(input.repoRoot || '', normalizedSourcePath);
|
||||
|
||||
if (!input.repoRoot || !fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const targetRulesDir = path.join(adapter.resolveRoot(input), 'rules');
|
||||
const operations = [];
|
||||
const entries = fs.readdirSync(sourceRoot, { withFileTypes: true }).sort((left, right) => (
|
||||
left.name.localeCompare(right.name)
|
||||
));
|
||||
|
||||
for (const entry of entries) {
|
||||
const namespace = entry.name;
|
||||
const entryPath = path.join(sourceRoot, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
const relativeFiles = listRelativeFiles(entryPath);
|
||||
for (const relativeFile of relativeFiles) {
|
||||
const flattenedFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
||||
const sourceRelativeFile = path.join(normalizedSourcePath, namespace, relativeFile);
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: sourceRelativeFile,
|
||||
destinationPath: path.join(targetRulesDir, flattenedFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, entry.name),
|
||||
destinationPath: path.join(targetRulesDir, entry.name),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, destinationDir }) {
|
||||
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
||||
const sourceRoot = path.join(repoRoot || '', normalizedSourcePath);
|
||||
|
||||
if (!repoRoot || !fs.existsSync(sourceRoot) || !fs.statSync(sourceRoot).isDirectory()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const operations = [];
|
||||
const entries = fs.readdirSync(sourceRoot, { withFileTypes: true }).sort((left, right) => (
|
||||
left.name.localeCompare(right.name)
|
||||
));
|
||||
|
||||
for (const entry of entries) {
|
||||
const namespace = entry.name;
|
||||
const entryPath = path.join(sourceRoot, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
const relativeFiles = listRelativeFiles(entryPath);
|
||||
for (const relativeFile of relativeFiles) {
|
||||
const flattenedFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile),
|
||||
destinationPath: path.join(destinationDir, flattenedFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, entry.name),
|
||||
destinationPath: path.join(destinationDir, entry.name),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
function createInstallTargetAdapter(config) {
|
||||
const adapter = {
|
||||
id: config.id,
|
||||
@@ -68,15 +245,43 @@ function createInstallTargetAdapter(config) {
|
||||
},
|
||||
createScaffoldOperation(moduleId, sourceRelativePath, input = {}) {
|
||||
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
||||
return {
|
||||
kind: 'copy-path',
|
||||
return createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: normalizedSourcePath,
|
||||
destinationPath: adapter.resolveDestinationPath(normalizedSourcePath, input),
|
||||
strategy: adapter.determineStrategy(normalizedSourcePath),
|
||||
ownership: 'managed',
|
||||
scaffoldOnly: true,
|
||||
};
|
||||
});
|
||||
},
|
||||
planOperations(input = {}) {
|
||||
if (typeof config.planOperations === 'function') {
|
||||
return config.planOperations(input, adapter);
|
||||
}
|
||||
|
||||
if (Array.isArray(input.modules)) {
|
||||
return input.modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths.map(sourceRelativePath => adapter.createScaffoldOperation(
|
||||
module.id,
|
||||
sourceRelativePath,
|
||||
input
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
const module = input.module || {};
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths.map(sourceRelativePath => adapter.createScaffoldOperation(
|
||||
module.id,
|
||||
sourceRelativePath,
|
||||
input
|
||||
));
|
||||
},
|
||||
validate(input = {}) {
|
||||
if (typeof config.validate === 'function') {
|
||||
return config.validate(input, adapter);
|
||||
}
|
||||
|
||||
return defaultValidateAdapterInput(config, input);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -84,6 +289,19 @@ function createInstallTargetAdapter(config) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildValidationIssue,
|
||||
createFlatRuleOperations,
|
||||
createInstallTargetAdapter,
|
||||
createManagedOperation,
|
||||
createManagedScaffoldOperation: (moduleId, sourceRelativePath, destinationPath, strategy) => (
|
||||
createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy,
|
||||
})
|
||||
),
|
||||
createNamespacedFlatRuleOperations,
|
||||
createRemappedOperation,
|
||||
normalizeRelativePath,
|
||||
};
|
||||
|
||||
@@ -34,15 +34,16 @@ function planInstallTargetScaffold(options = {}) {
|
||||
projectRoot: options.projectRoot || options.repoRoot,
|
||||
homeDir: options.homeDir,
|
||||
};
|
||||
const validationIssues = adapter.validate(planningInput);
|
||||
const blockingIssues = validationIssues.filter(issue => issue.severity === 'error');
|
||||
if (blockingIssues.length > 0) {
|
||||
throw new Error(blockingIssues.map(issue => issue.message).join('; '));
|
||||
}
|
||||
const targetRoot = adapter.resolveRoot(planningInput);
|
||||
const installStatePath = adapter.getInstallStatePath(planningInput);
|
||||
const operations = modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths.map(sourceRelativePath => adapter.createScaffoldOperation(
|
||||
module.id,
|
||||
sourceRelativePath,
|
||||
planningInput
|
||||
));
|
||||
const operations = adapter.planOperations({
|
||||
...planningInput,
|
||||
modules,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -53,6 +54,7 @@ function planInstallTargetScaffold(options = {}) {
|
||||
},
|
||||
targetRoot,
|
||||
installStatePath,
|
||||
validationIssues,
|
||||
operations,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user