mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-31 04:09:51 +02:00
The guided installer asks how ECC hooks should run, but that consent lived only in the wizard path. Running install-apply directly with a profile that includes hooks-runtime still materialized the hook runtime with no disclosure and no decision. Gate the apply layer instead, so every entry point is covered: - disclose the six hook capability groups when a plan would materialize the hook runtime, and refuse to apply until the caller decides - --enable-hooks confirms the hook runtime; --no-hooks installs the rest of the selection without it and records the reduced module closure in install-state - surface the pending decision as a dry-run warning - show the same capability disclosure in the guided installer's plan preview, so the wizard's hook question states what it is asking about Plans that never materialize hooks (Kimi, --profile minimal, --without baseline:hooks) are unaffected and need no flag. Repair and uninstall operate on already-recorded state and stay unchanged. The capability taxonomy and the held-materialization behavior come from Samarjeet Singh Tomar's PR #2634, reworked to fit the single-decision consent model that shipped with the guided installer in #2649. Co-Authored-By: Samarjeet Singh Tomar <samar_tomar@hotmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
572 lines
18 KiB
JavaScript
572 lines
18 KiB
JavaScript
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const { toCursorAgentRelativePath } = require('./cursor-agent-names');
|
|
const { LEGACY_INSTALL_TARGETS, parseInstallArgs } = require('./install/request');
|
|
const {
|
|
buildCopyFileOperation,
|
|
createManifestInstallPlan,
|
|
createStatePreview,
|
|
dedupeCopyFileOperations,
|
|
getManifestVersion,
|
|
getPackageVersion,
|
|
getRepoCommit,
|
|
getSourceRoot,
|
|
listFilesRecursive,
|
|
readJsonObject,
|
|
} = require('./install/plan');
|
|
const { SUPPORTED_INSTALL_TARGETS, listLegacyCompatibilityLanguages, resolveLegacyCompatibilitySelection } = require('./install-manifests');
|
|
const { getInstallTargetAdapter } = require('./install-targets/registry');
|
|
const { resolveInvocationEnvironment } = require('./invocation-environment');
|
|
|
|
const LANGUAGE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
const CLAUDE_ECC_NAMESPACE = 'ecc';
|
|
|
|
function readDirectoryNames(dirPath) {
|
|
if (!fs.existsSync(dirPath)) {
|
|
return [];
|
|
}
|
|
|
|
return fs
|
|
.readdirSync(dirPath, { withFileTypes: true })
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => entry.name)
|
|
.sort();
|
|
}
|
|
|
|
function listAvailableLanguages(sourceRoot = getSourceRoot()) {
|
|
return [...new Set([...listLegacyCompatibilityLanguages(), ...readDirectoryNames(path.join(sourceRoot, 'rules')).filter(name => name !== 'common')])].sort();
|
|
}
|
|
|
|
function validateLegacyTarget(target) {
|
|
if (LEGACY_INSTALL_TARGETS.includes(target)) {
|
|
return;
|
|
}
|
|
// A target can be fully supported yet not installable via the bare-language
|
|
// positional syntax (which is legacy-only). Guide the user to the right mode
|
|
// instead of implying the target is unknown (#2282).
|
|
if (SUPPORTED_INSTALL_TARGETS.includes(target)) {
|
|
throw new Error(
|
|
`Target '${target}' is supported, but the bare-language install syntax only accepts ${LEGACY_INSTALL_TARGETS.join(', ')}. ` +
|
|
`Install '${target}' with a component selection instead, e.g. \`install.sh --target ${target} --profile full\` ` +
|
|
`(or --modules <id,...> / --skills <id,...>).`
|
|
);
|
|
}
|
|
throw new Error(`Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}`);
|
|
}
|
|
|
|
function applyInstallPlan(plan, dependencies = {}) {
|
|
const { applyInstallPlan: applyPlan } = require('./install/apply');
|
|
return applyPlan(plan, dependencies);
|
|
}
|
|
|
|
function previewInstallPlan(plan) {
|
|
const { previewInstallPlan: previewPlan } = require('./install/apply');
|
|
return previewPlan(plan);
|
|
}
|
|
|
|
function addRecursiveCopyOperations(operations, options) {
|
|
const sourceDir = path.join(options.sourceRoot, options.sourceRelativeDir);
|
|
if (!fs.existsSync(sourceDir)) {
|
|
return 0;
|
|
}
|
|
|
|
const relativeFiles = listFilesRecursive(sourceDir);
|
|
|
|
for (const relativeFile of relativeFiles) {
|
|
const sourceRelativePath = path.join(options.sourceRelativeDir, relativeFile);
|
|
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
|
const destinationRelativePath = typeof options.destinationRelativePathTransform === 'function' ? options.destinationRelativePathTransform(relativeFile, sourceRelativePath) : relativeFile;
|
|
if (!destinationRelativePath) {
|
|
continue;
|
|
}
|
|
const destinationPath = path.join(options.destinationDir, destinationRelativePath);
|
|
operations.push(
|
|
buildCopyFileOperation({
|
|
moduleId: options.moduleId,
|
|
sourcePath,
|
|
sourceRelativePath,
|
|
destinationPath,
|
|
strategy: options.strategy || 'preserve-relative-path',
|
|
contentTransform: options.contentTransform,
|
|
})
|
|
);
|
|
}
|
|
|
|
return relativeFiles.length;
|
|
}
|
|
|
|
function addFileCopyOperation(operations, options) {
|
|
const sourcePath = path.join(options.sourceRoot, options.sourceRelativePath);
|
|
if (!fs.existsSync(sourcePath)) {
|
|
return false;
|
|
}
|
|
|
|
operations.push(
|
|
buildCopyFileOperation({
|
|
moduleId: options.moduleId,
|
|
sourcePath,
|
|
sourceRelativePath: options.sourceRelativePath,
|
|
destinationPath: options.destinationPath,
|
|
strategy: options.strategy || 'preserve-relative-path'
|
|
})
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
function addCursorAgentDataScaffoldOperations(operations, options) {
|
|
const scaffoldRoot = path.join(options.sourceRoot, 'scaffolds', 'cursor');
|
|
if (!fs.existsSync(scaffoldRoot)) {
|
|
return;
|
|
}
|
|
|
|
addFileCopyOperation(operations, {
|
|
moduleId: options.moduleId,
|
|
sourceRoot: options.sourceRoot,
|
|
sourceRelativePath: path.join('scaffolds', 'cursor', 'ecc-agent-data.json'),
|
|
destinationPath: path.join(options.targetRoot, 'ecc-agent-data.json'),
|
|
strategy: 'preserve-relative-path'
|
|
});
|
|
|
|
addFileCopyOperation(operations, {
|
|
moduleId: options.moduleId,
|
|
sourceRoot: options.sourceRoot,
|
|
sourceRelativePath: path.join('scaffolds', 'cursor', 'rules', 'ecc-agent-data-home.mdc'),
|
|
destinationPath: path.join(options.targetRoot, 'rules', 'ecc-agent-data-home.mdc'),
|
|
strategy: 'preserve-relative-path'
|
|
});
|
|
|
|
addJsonMergeOperation(operations, {
|
|
moduleId: options.moduleId,
|
|
sourceRoot: options.sourceRoot,
|
|
sourceRelativePath: path.join('scaffolds', 'cursor', 'hooks.json'),
|
|
destinationPath: path.join(options.targetRoot, 'hooks.json')
|
|
});
|
|
|
|
const cursorSessionHookDeps = [path.join('scripts', 'hooks', 'cursor-session-env.js'), path.join('scripts', 'lib', 'agent-data-home.js'), path.join('scripts', 'lib', 'utils.js')];
|
|
|
|
for (const sourceRelativePath of cursorSessionHookDeps) {
|
|
addFileCopyOperation(operations, {
|
|
moduleId: options.moduleId,
|
|
sourceRoot: options.sourceRoot,
|
|
sourceRelativePath,
|
|
destinationPath: path.join(options.targetRoot, sourceRelativePath),
|
|
strategy: 'preserve-relative-path'
|
|
});
|
|
}
|
|
}
|
|
|
|
function addJsonMergeOperation(operations, options) {
|
|
const sourcePath = path.join(options.sourceRoot, options.sourceRelativePath);
|
|
if (!fs.existsSync(sourcePath)) {
|
|
return false;
|
|
}
|
|
|
|
operations.push({
|
|
kind: 'merge-json',
|
|
moduleId: options.moduleId,
|
|
sourceRelativePath: options.sourceRelativePath,
|
|
destinationPath: options.destinationPath,
|
|
strategy: 'merge-json',
|
|
ownership: 'managed',
|
|
scaffoldOnly: false,
|
|
mergePayload: readJsonObject(sourcePath, options.sourceRelativePath)
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
function addMatchingRuleOperations(operations, options) {
|
|
const sourceDir = path.join(options.sourceRoot, options.sourceRelativeDir);
|
|
if (!fs.existsSync(sourceDir)) {
|
|
return 0;
|
|
}
|
|
|
|
const files = fs
|
|
.readdirSync(sourceDir, { withFileTypes: true })
|
|
.filter(entry => entry.isFile() && options.matcher(entry.name))
|
|
.map(entry => entry.name)
|
|
.sort();
|
|
|
|
for (const fileName of files) {
|
|
const sourceRelativePath = path.join(options.sourceRelativeDir, fileName);
|
|
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
|
const destinationPath = path.join(options.destinationDir, options.rename ? options.rename(fileName) : fileName);
|
|
|
|
operations.push(
|
|
buildCopyFileOperation({
|
|
moduleId: options.moduleId,
|
|
sourcePath,
|
|
sourceRelativePath,
|
|
destinationPath,
|
|
strategy: options.strategy || 'flatten-copy'
|
|
})
|
|
);
|
|
}
|
|
|
|
return files.length;
|
|
}
|
|
|
|
function isDirectoryNonEmpty(dirPath) {
|
|
return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory() && fs.readdirSync(dirPath).length > 0;
|
|
}
|
|
|
|
function planClaudeStyleLegacyInstall(context, { adapterId, adapterRootInput, rulesDir: rulesDirOverride }) {
|
|
const adapter = getInstallTargetAdapter(adapterId);
|
|
const targetRoot = adapter.resolveRoot(adapterRootInput);
|
|
const rulesDir = rulesDirOverride || path.join(targetRoot, 'rules', CLAUDE_ECC_NAMESPACE);
|
|
const installStatePath = adapter.getInstallStatePath(adapterRootInput);
|
|
const operations = [];
|
|
const warnings = [];
|
|
|
|
if (isDirectoryNonEmpty(rulesDir)) {
|
|
warnings.push(`Destination ${rulesDir}/ already exists and files may be overwritten`);
|
|
}
|
|
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-claude-rules',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('rules', 'common'),
|
|
destinationDir: path.join(rulesDir, 'common')
|
|
});
|
|
|
|
for (const language of context.languages) {
|
|
if (!LANGUAGE_NAME_PATTERN.test(language)) {
|
|
warnings.push(`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`);
|
|
continue;
|
|
}
|
|
|
|
const sourceDir = path.join(context.sourceRoot, 'rules', language);
|
|
if (!fs.existsSync(sourceDir)) {
|
|
warnings.push(`rules/${language}/ does not exist, skipping`);
|
|
continue;
|
|
}
|
|
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-claude-rules',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('rules', language),
|
|
destinationDir: path.join(rulesDir, language)
|
|
});
|
|
}
|
|
|
|
return {
|
|
mode: 'legacy',
|
|
sourceRoot: context.sourceRoot,
|
|
adapter,
|
|
target: adapterId,
|
|
targetRoot,
|
|
installRoot: rulesDir,
|
|
installStatePath,
|
|
operations,
|
|
warnings,
|
|
selectedModules: ['legacy-claude-rules']
|
|
};
|
|
}
|
|
|
|
function planClaudeLegacyInstall(context) {
|
|
return planClaudeStyleLegacyInstall(context, {
|
|
adapterId: 'claude',
|
|
adapterRootInput: { homeDir: context.homeDir },
|
|
rulesDir: context.claudeRulesDir || null
|
|
});
|
|
}
|
|
|
|
function planClaudeProjectLegacyInstall(context) {
|
|
return planClaudeStyleLegacyInstall(context, {
|
|
adapterId: 'claude-project',
|
|
adapterRootInput: { repoRoot: context.projectRoot },
|
|
rulesDir: null
|
|
});
|
|
}
|
|
|
|
function planCursorLegacyInstall(context) {
|
|
const adapter = getInstallTargetAdapter('cursor');
|
|
const targetRoot = adapter.resolveRoot({ repoRoot: context.projectRoot });
|
|
const installStatePath = adapter.getInstallStatePath({ repoRoot: context.projectRoot });
|
|
const operations = [];
|
|
const warnings = [];
|
|
|
|
addMatchingRuleOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'rules'),
|
|
destinationDir: path.join(targetRoot, 'rules'),
|
|
matcher: fileName => /^common-.*\.md$/.test(fileName)
|
|
});
|
|
|
|
for (const language of context.languages) {
|
|
if (!LANGUAGE_NAME_PATTERN.test(language)) {
|
|
warnings.push(`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`);
|
|
continue;
|
|
}
|
|
|
|
const matches = addMatchingRuleOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'rules'),
|
|
destinationDir: path.join(targetRoot, 'rules'),
|
|
matcher: fileName => fileName.startsWith(`${language}-`) && fileName.endsWith('.md')
|
|
});
|
|
|
|
if (matches === 0) {
|
|
warnings.push(`No Cursor rules for '${language}' found, skipping`);
|
|
}
|
|
}
|
|
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'agents'),
|
|
destinationDir: path.join(targetRoot, 'agents'),
|
|
destinationRelativePathTransform: toCursorAgentRelativePath
|
|
});
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'skills'),
|
|
destinationDir: path.join(targetRoot, 'skills')
|
|
});
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'commands'),
|
|
destinationDir: path.join(targetRoot, 'commands')
|
|
});
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('.cursor', 'hooks'),
|
|
destinationDir: path.join(targetRoot, 'hooks')
|
|
});
|
|
|
|
addFileCopyOperation(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativePath: path.join('.cursor', 'hooks.json'),
|
|
destinationPath: path.join(targetRoot, 'hooks.json')
|
|
});
|
|
addJsonMergeOperation(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativePath: '.mcp.json',
|
|
destinationPath: path.join(targetRoot, 'mcp.json')
|
|
});
|
|
|
|
addCursorAgentDataScaffoldOperations(operations, {
|
|
moduleId: 'legacy-cursor-install',
|
|
sourceRoot: context.sourceRoot,
|
|
targetRoot
|
|
});
|
|
|
|
return {
|
|
mode: 'legacy',
|
|
adapter,
|
|
target: 'cursor',
|
|
targetRoot,
|
|
installRoot: targetRoot,
|
|
installStatePath,
|
|
operations,
|
|
warnings,
|
|
selectedModules: ['legacy-cursor-install']
|
|
};
|
|
}
|
|
|
|
function planAntigravityLegacyInstall(context) {
|
|
const adapter = getInstallTargetAdapter('antigravity');
|
|
const targetRoot = adapter.resolveRoot({ repoRoot: context.projectRoot });
|
|
const installStatePath = adapter.getInstallStatePath({ repoRoot: context.projectRoot });
|
|
const operations = [];
|
|
const warnings = [];
|
|
|
|
if (isDirectoryNonEmpty(path.join(targetRoot, 'rules'))) {
|
|
warnings.push(`Destination ${path.join(targetRoot, 'rules')}/ already exists and files may be overwritten`);
|
|
}
|
|
|
|
addMatchingRuleOperations(operations, {
|
|
moduleId: 'legacy-antigravity-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('rules', 'common'),
|
|
destinationDir: path.join(targetRoot, 'rules'),
|
|
matcher: fileName => fileName.endsWith('.md'),
|
|
rename: fileName => `common-${fileName}`
|
|
});
|
|
|
|
for (const language of context.languages) {
|
|
if (!LANGUAGE_NAME_PATTERN.test(language)) {
|
|
warnings.push(`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`);
|
|
continue;
|
|
}
|
|
|
|
const sourceDir = path.join(context.sourceRoot, 'rules', language);
|
|
if (!fs.existsSync(sourceDir)) {
|
|
warnings.push(`rules/${language}/ does not exist, skipping`);
|
|
continue;
|
|
}
|
|
|
|
addMatchingRuleOperations(operations, {
|
|
moduleId: 'legacy-antigravity-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: path.join('rules', language),
|
|
destinationDir: path.join(targetRoot, 'rules'),
|
|
matcher: fileName => fileName.endsWith('.md'),
|
|
rename: fileName => `${language}-${fileName}`
|
|
});
|
|
}
|
|
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-antigravity-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: 'commands',
|
|
destinationDir: path.join(targetRoot, 'workflows')
|
|
});
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-antigravity-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: 'agents',
|
|
destinationDir: path.join(targetRoot, 'agents'),
|
|
contentTransform: 'antigravity-agent-frontmatter'
|
|
});
|
|
addRecursiveCopyOperations(operations, {
|
|
moduleId: 'legacy-antigravity-install',
|
|
sourceRoot: context.sourceRoot,
|
|
sourceRelativeDir: 'skills',
|
|
destinationDir: path.join(targetRoot, 'skills')
|
|
});
|
|
|
|
return {
|
|
mode: 'legacy',
|
|
adapter,
|
|
target: 'antigravity',
|
|
targetRoot,
|
|
installRoot: targetRoot,
|
|
installStatePath,
|
|
operations,
|
|
warnings,
|
|
selectedModules: ['legacy-antigravity-install']
|
|
};
|
|
}
|
|
|
|
function createLegacyInstallPlan(options = {}) {
|
|
const sourceRoot = options.sourceRoot || getSourceRoot();
|
|
const projectRoot = options.projectRoot || process.cwd();
|
|
const homeDir = options.homeDir || process.env.HOME || os.homedir();
|
|
const target = options.target || 'claude';
|
|
|
|
validateLegacyTarget(target);
|
|
|
|
const context = {
|
|
sourceRoot,
|
|
projectRoot,
|
|
homeDir,
|
|
languages: Array.isArray(options.languages) ? options.languages : [],
|
|
claudeRulesDir: options.claudeRulesDir || process.env.CLAUDE_RULES_DIR || null
|
|
};
|
|
|
|
let plan;
|
|
if (target === 'claude') {
|
|
plan = planClaudeLegacyInstall(context);
|
|
} else if (target === 'claude-project') {
|
|
plan = planClaudeProjectLegacyInstall(context);
|
|
} else if (target === 'cursor') {
|
|
plan = planCursorLegacyInstall(context);
|
|
} else {
|
|
plan = planAntigravityLegacyInstall(context);
|
|
}
|
|
|
|
const source = {
|
|
repoVersion: getPackageVersion(sourceRoot),
|
|
repoCommit: getRepoCommit(sourceRoot),
|
|
manifestVersion: getManifestVersion(sourceRoot)
|
|
};
|
|
|
|
const statePreview = createStatePreview({
|
|
adapter: plan.adapter,
|
|
targetRoot: plan.targetRoot,
|
|
installStatePath: plan.installStatePath,
|
|
request: {
|
|
profile: null,
|
|
modules: [],
|
|
legacyLanguages: context.languages,
|
|
legacyMode: true
|
|
},
|
|
resolution: {
|
|
selectedModules: plan.selectedModules,
|
|
skippedModules: []
|
|
},
|
|
operations: plan.operations,
|
|
source
|
|
});
|
|
|
|
return {
|
|
mode: 'legacy',
|
|
sourceRoot,
|
|
target: plan.target,
|
|
adapter: {
|
|
id: plan.adapter.id,
|
|
target: plan.adapter.target,
|
|
kind: plan.adapter.kind
|
|
},
|
|
targetRoot: plan.targetRoot,
|
|
installRoot: plan.installRoot,
|
|
installStatePath: plan.installStatePath,
|
|
warnings: plan.warnings,
|
|
languages: context.languages,
|
|
operations: plan.operations,
|
|
statePreview
|
|
};
|
|
}
|
|
|
|
function createLegacyCompatInstallPlan(options = {}) {
|
|
const sourceRoot = options.sourceRoot || getSourceRoot();
|
|
const projectRoot = options.projectRoot || process.cwd();
|
|
const target = options.target || 'claude';
|
|
const includeComponentIds = Array.isArray(options.includeComponentIds) ? [...options.includeComponentIds] : [];
|
|
const excludeComponentIds = Array.isArray(options.excludeComponentIds) ? [...options.excludeComponentIds] : [];
|
|
|
|
validateLegacyTarget(target);
|
|
|
|
const selection = resolveLegacyCompatibilitySelection({
|
|
repoRoot: sourceRoot,
|
|
target,
|
|
legacyLanguages: options.legacyLanguages || []
|
|
});
|
|
|
|
return createManifestInstallPlan({
|
|
sourceRoot,
|
|
projectRoot,
|
|
homeDir: options.homeDir,
|
|
env: resolveInvocationEnvironment(options),
|
|
target,
|
|
profileId: null,
|
|
moduleIds: selection.moduleIds,
|
|
includeComponentIds,
|
|
excludeComponentIds,
|
|
legacyLanguages: selection.legacyLanguages,
|
|
ruleLanguages: selection.ruleLanguages,
|
|
legacyMode: true,
|
|
exemptValidationCodes: options.exemptValidationCodes || [],
|
|
requestProfileId: null,
|
|
requestModuleIds: [],
|
|
requestIncludeComponentIds: includeComponentIds,
|
|
requestExcludeComponentIds: excludeComponentIds,
|
|
mode: 'legacy-compat'
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
SUPPORTED_INSTALL_TARGETS,
|
|
LEGACY_INSTALL_TARGETS,
|
|
applyInstallPlan,
|
|
previewInstallPlan,
|
|
createLegacyCompatInstallPlan,
|
|
createManifestInstallPlan,
|
|
createLegacyInstallPlan,
|
|
dedupeCopyFileOperations,
|
|
getSourceRoot,
|
|
listAvailableLanguages,
|
|
parseInstallArgs
|
|
};
|