mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-14 13:53:29 +08:00
feat: add auto-update command
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Everything Claude Code (ECC) — Agent Instructions
|
# Everything Claude Code (ECC) — Agent Instructions
|
||||||
|
|
||||||
This is a **production-ready AI coding plugin** providing 47 specialized agents, 181 skills, 79 commands, and automated hook workflows for software development.
|
This is a **production-ready AI coding plugin** providing 47 specialized agents, 181 skills, 80 commands, and automated hook workflows for software development.
|
||||||
|
|
||||||
**Version:** 1.10.0
|
**Version:** 1.10.0
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
|||||||
```
|
```
|
||||||
agents/ — 47 specialized subagents
|
agents/ — 47 specialized subagents
|
||||||
skills/ — 181 workflow skills and domain knowledge
|
skills/ — 181 workflow skills and domain knowledge
|
||||||
commands/ — 79 slash commands
|
commands/ — 80 slash commands
|
||||||
hooks/ — Trigger-based automations
|
hooks/ — Trigger-based automations
|
||||||
rules/ — Always-follow guidelines (common + per-language)
|
rules/ — Always-follow guidelines (common + per-language)
|
||||||
scripts/ — Cross-platform Node.js utilities
|
scripts/ — Cross-platform Node.js utilities
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ For manual install instructions see the README in the `rules/` folder. When copy
|
|||||||
/plugin list ecc@ecc
|
/plugin list ecc@ecc
|
||||||
```
|
```
|
||||||
|
|
||||||
**That's it!** You now have access to 47 agents, 181 skills, and 79 legacy command shims.
|
**That's it!** You now have access to 47 agents, 181 skills, and 80 legacy command shims.
|
||||||
|
|
||||||
### Multi-model commands require additional setup
|
### Multi-model commands require additional setup
|
||||||
|
|
||||||
@@ -1158,7 +1158,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
|||||||
| Feature | Claude Code | OpenCode | Status |
|
| Feature | Claude Code | OpenCode | Status |
|
||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| Agents | PASS: 47 agents | PASS: 12 agents | **Claude Code leads** |
|
| Agents | PASS: 47 agents | PASS: 12 agents | **Claude Code leads** |
|
||||||
| Commands | PASS: 79 commands | PASS: 31 commands | **Claude Code leads** |
|
| Commands | PASS: 80 commands | PASS: 31 commands | **Claude Code leads** |
|
||||||
| Skills | PASS: 181 skills | PASS: 37 skills | **Claude Code leads** |
|
| Skills | PASS: 181 skills | PASS: 37 skills | **Claude Code leads** |
|
||||||
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
||||||
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
||||||
@@ -1267,7 +1267,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
|||||||
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
||||||
|---------|------------|------------|-----------|----------|
|
|---------|------------|------------|-----------|----------|
|
||||||
| **Agents** | 47 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
| **Agents** | 47 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||||
| **Commands** | 79 | Shared | Instruction-based | 31 |
|
| **Commands** | 80 | Shared | Instruction-based | 31 |
|
||||||
| **Skills** | 181 | Shared | 10 (native format) | 37 |
|
| **Skills** | 181 | Shared | 10 (native format) | 37 |
|
||||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
||||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ npx ecc-install typescript
|
|||||||
/plugin list ecc@ecc
|
/plugin list ecc@ecc
|
||||||
```
|
```
|
||||||
|
|
||||||
**完成!** 你现在可以使用 47 个代理、181 个技能和 79 个命令。
|
**完成!** 你现在可以使用 47 个代理、181 个技能和 80 个命令。
|
||||||
|
|
||||||
### multi-* 命令需要额外配置
|
### multi-* 命令需要额外配置
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ skills:
|
|||||||
commands:
|
commands:
|
||||||
- agent-sort
|
- agent-sort
|
||||||
- aside
|
- aside
|
||||||
|
- auto-update
|
||||||
- build-fix
|
- build-fix
|
||||||
- checkpoint
|
- checkpoint
|
||||||
- claw
|
- claw
|
||||||
|
|||||||
28
commands/auto-update.md
Normal file
28
commands/auto-update.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
description: Pull the latest ECC repo changes and reinstall the current managed targets.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Auto Update
|
||||||
|
|
||||||
|
Update ECC from its upstream repo and regenerate the current context's managed install using the original install-state request.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Preview the update without mutating anything
|
||||||
|
ECC_ROOT="${CLAUDE_PLUGIN_ROOT:-$(node -e "var r=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var s of [['ecc'],['ecc@ecc'],['marketplace','ecc'],['everything-claude-code'],['everything-claude-code@everything-claude-code'],['marketplace','everything-claude-code']]){var l=p.join(d,'plugins',...s);if(f.existsSync(p.join(l,q)))return l}try{for(var g of ['ecc','everything-claude-code']){var b=p.join(d,'plugins','cache',g);for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}}catch(x){}return d})();console.log(r)")}"
|
||||||
|
node "$ECC_ROOT/scripts/auto-update.js" --dry-run
|
||||||
|
|
||||||
|
# Update only Cursor-managed files in the current project
|
||||||
|
node "$ECC_ROOT/scripts/auto-update.js" --target cursor
|
||||||
|
|
||||||
|
# Override the ECC repo root explicitly
|
||||||
|
node "$ECC_ROOT/scripts/auto-update.js" --repo-root /path/to/everything-claude-code
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This command uses the recorded install-state request and reruns `install-apply.js` after pulling the latest repo changes.
|
||||||
|
- Reinstall is intentional: it handles upstream renames and deletions that `repair.js` cannot safely reconstruct from stale operations alone.
|
||||||
|
- Use `--dry-run` first if you want to see the reconstructed reinstall plan before mutating anything.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Everything Claude Code (ECC) — 智能体指令
|
# Everything Claude Code (ECC) — 智能体指令
|
||||||
|
|
||||||
这是一个**生产就绪的 AI 编码插件**,提供 47 个专业代理、181 项技能、79 条命令以及自动化钩子工作流,用于软件开发。
|
这是一个**生产就绪的 AI 编码插件**,提供 47 个专业代理、181 项技能、80 条命令以及自动化钩子工作流,用于软件开发。
|
||||||
|
|
||||||
**版本:** 1.10.0
|
**版本:** 1.10.0
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
```
|
```
|
||||||
agents/ — 47 个专业子代理
|
agents/ — 47 个专业子代理
|
||||||
skills/ — 181 个工作流技能和领域知识
|
skills/ — 181 个工作流技能和领域知识
|
||||||
commands/ — 79 个斜杠命令
|
commands/ — 80 个斜杠命令
|
||||||
hooks/ — 基于触发的自动化
|
hooks/ — 基于触发的自动化
|
||||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||||
scripts/ — 跨平台 Node.js 实用工具
|
scripts/ — 跨平台 Node.js 实用工具
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ npx ecc-install typescript
|
|||||||
/plugin list ecc@ecc
|
/plugin list ecc@ecc
|
||||||
```
|
```
|
||||||
|
|
||||||
**搞定!** 你现在可以使用 47 个智能体、181 项技能和 79 个命令了。
|
**搞定!** 你现在可以使用 47 个智能体、181 项技能和 80 个命令了。
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -1095,7 +1095,7 @@ opencode
|
|||||||
| 功能特性 | Claude Code | OpenCode | 状态 |
|
| 功能特性 | Claude Code | OpenCode | 状态 |
|
||||||
|---------|-------------|----------|--------|
|
|---------|-------------|----------|--------|
|
||||||
| 智能体 | PASS: 47 个 | PASS: 12 个 | **Claude Code 领先** |
|
| 智能体 | PASS: 47 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||||
| 命令 | PASS: 79 个 | PASS: 31 个 | **Claude Code 领先** |
|
| 命令 | PASS: 80 个 | PASS: 31 个 | **Claude Code 领先** |
|
||||||
| 技能 | PASS: 181 项 | PASS: 37 项 | **Claude Code 领先** |
|
| 技能 | PASS: 181 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||||
@@ -1207,7 +1207,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
|||||||
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
||||||
|---------|------------|------------|-----------|----------|
|
|---------|------------|------------|-----------|----------|
|
||||||
| **智能体** | 47 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
| **智能体** | 47 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||||
| **命令** | 79 | 共享 | 基于指令 | 31 |
|
| **命令** | 80 | 共享 | 基于指令 | 31 |
|
||||||
| **技能** | 181 | 共享 | 10 (原生格式) | 37 |
|
| **技能** | 181 | 共享 | 10 (原生格式) | 37 |
|
||||||
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
|
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
|
||||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
|
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
".gemini",
|
".gemini",
|
||||||
".opencode",
|
".opencode",
|
||||||
"mcp-configs",
|
"mcp-configs",
|
||||||
|
"scripts/auto-update.js",
|
||||||
"scripts/setup-package-manager.js"
|
"scripts/setup-package-manager.js"
|
||||||
],
|
],
|
||||||
"targets": [
|
"targets": [
|
||||||
|
|||||||
361
scripts/auto-update.js
Normal file
361
scripts/auto-update.js
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const { discoverInstalledStates } = require('./lib/install-lifecycle');
|
||||||
|
const { SUPPORTED_INSTALL_TARGETS } = require('./lib/install-manifests');
|
||||||
|
|
||||||
|
function showHelp(exitCode = 0) {
|
||||||
|
console.log(`
|
||||||
|
Usage: node scripts/auto-update.js [--target <${SUPPORTED_INSTALL_TARGETS.join('|')}>] [--repo-root <path>] [--dry-run] [--json]
|
||||||
|
|
||||||
|
Pull the latest ECC repo changes and reinstall the current context's managed targets
|
||||||
|
using the original install-state request.
|
||||||
|
`);
|
||||||
|
process.exit(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const args = argv.slice(2);
|
||||||
|
const parsed = {
|
||||||
|
targets: [],
|
||||||
|
repoRoot: null,
|
||||||
|
dryRun: false,
|
||||||
|
json: false,
|
||||||
|
help: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let index = 0; index < args.length; index += 1) {
|
||||||
|
const arg = args[index];
|
||||||
|
|
||||||
|
if (arg === '--target') {
|
||||||
|
parsed.targets.push(args[index + 1] || null);
|
||||||
|
index += 1;
|
||||||
|
} else if (arg === '--repo-root') {
|
||||||
|
parsed.repoRoot = args[index + 1] || null;
|
||||||
|
index += 1;
|
||||||
|
} else if (arg === '--dry-run') {
|
||||||
|
parsed.dryRun = true;
|
||||||
|
} else if (arg === '--json') {
|
||||||
|
parsed.json = true;
|
||||||
|
} else if (arg === '--help' || arg === '-h') {
|
||||||
|
parsed.help = true;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown argument: ${arg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveRepoRootFromState(state) {
|
||||||
|
const operations = Array.isArray(state && state.operations) ? state.operations : [];
|
||||||
|
|
||||||
|
for (const operation of operations) {
|
||||||
|
if (typeof operation.sourcePath !== 'string' || !operation.sourcePath.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof operation.sourceRelativePath !== 'string' || !operation.sourceRelativePath.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeParts = operation.sourceRelativePath
|
||||||
|
.split(/[\\/]+/)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (relativeParts.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let repoRoot = path.resolve(operation.sourcePath);
|
||||||
|
for (let index = 0; index < relativeParts.length; index += 1) {
|
||||||
|
repoRoot = path.dirname(repoRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unable to infer ECC repo root from install-state operations');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInstallApplyArgs(record) {
|
||||||
|
const state = record.state;
|
||||||
|
const target = state.target.target || record.adapter.target;
|
||||||
|
const request = state.request || {};
|
||||||
|
const args = [];
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
args.push('--target', target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.profile) {
|
||||||
|
args.push('--profile', request.profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(request.modules) && request.modules.length > 0) {
|
||||||
|
args.push('--modules', request.modules.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const componentId of Array.isArray(request.includeComponents) ? request.includeComponents : []) {
|
||||||
|
args.push('--with', componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const componentId of Array.isArray(request.excludeComponents) ? request.excludeComponents : []) {
|
||||||
|
args.push('--without', componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const language of Array.isArray(request.legacyLanguages) ? request.legacyLanguages : []) {
|
||||||
|
args.push(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineInstallCwd(record, repoRoot) {
|
||||||
|
if (record.adapter.kind === 'project') {
|
||||||
|
return path.dirname(record.state.target.root);
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRepoRoot(repoRoot) {
|
||||||
|
const normalized = path.resolve(repoRoot);
|
||||||
|
const packageJsonPath = path.join(normalized, 'package.json');
|
||||||
|
const installApplyPath = path.join(normalized, 'scripts', 'install-apply.js');
|
||||||
|
|
||||||
|
if (!fs.existsSync(packageJsonPath)) {
|
||||||
|
throw new Error(`Invalid ECC repo root: missing package.json at ${packageJsonPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(installApplyPath)) {
|
||||||
|
throw new Error(`Invalid ECC repo root: missing install script at ${installApplyPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runExternalCommand(command, args, options = {}) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: options.cwd,
|
||||||
|
env: options.env || process.env,
|
||||||
|
encoding: 'utf8',
|
||||||
|
maxBuffer: 10 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof result.status === 'number' && result.status !== 0) {
|
||||||
|
const errorOutput = (result.stderr || result.stdout || '').trim();
|
||||||
|
throw new Error(`${command} ${args.join(' ')} failed${errorOutput ? `: ${errorOutput}` : ''}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAutoUpdate(options = {}, dependencies = {}) {
|
||||||
|
const discover = dependencies.discoverInstalledStates || discoverInstalledStates;
|
||||||
|
const execute = dependencies.runExternalCommand || runExternalCommand;
|
||||||
|
const homeDir = options.homeDir || process.env.HOME || os.homedir();
|
||||||
|
const projectRoot = options.projectRoot || process.cwd();
|
||||||
|
const requestedRepoRoot = options.repoRoot ? validateRepoRoot(options.repoRoot) : null;
|
||||||
|
const records = discover({
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
targets: options.targets,
|
||||||
|
}).filter(record => record.exists);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
if (records.length === 0) {
|
||||||
|
return {
|
||||||
|
dryRun: Boolean(options.dryRun),
|
||||||
|
repoRoot: requestedRepoRoot,
|
||||||
|
results,
|
||||||
|
summary: {
|
||||||
|
checkedCount: 0,
|
||||||
|
updatedCount: 0,
|
||||||
|
errorCount: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const validRecords = [];
|
||||||
|
const inferredRepoRoots = [];
|
||||||
|
for (const record of records) {
|
||||||
|
if (record.error || !record.state) {
|
||||||
|
results.push({
|
||||||
|
adapter: record.adapter,
|
||||||
|
installStatePath: record.installStatePath,
|
||||||
|
status: 'error',
|
||||||
|
error: record.error || 'No valid install-state available',
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordRepoRoot = requestedRepoRoot || validateRepoRoot(deriveRepoRootFromState(record.state));
|
||||||
|
inferredRepoRoots.push(recordRepoRoot);
|
||||||
|
validRecords.push({
|
||||||
|
record,
|
||||||
|
repoRoot: recordRepoRoot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestedRepoRoot) {
|
||||||
|
const uniqueRepoRoots = [...new Set(inferredRepoRoots)];
|
||||||
|
if (uniqueRepoRoots.length > 1) {
|
||||||
|
throw new Error(`Multiple ECC repo roots detected: ${uniqueRepoRoots.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoRoot = requestedRepoRoot || inferredRepoRoots[0] || null;
|
||||||
|
if (!repoRoot) {
|
||||||
|
return {
|
||||||
|
dryRun: Boolean(options.dryRun),
|
||||||
|
repoRoot,
|
||||||
|
results,
|
||||||
|
summary: {
|
||||||
|
checkedCount: results.length,
|
||||||
|
updatedCount: 0,
|
||||||
|
errorCount: results.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
HOME: homeDir,
|
||||||
|
USERPROFILE: homeDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!options.dryRun) {
|
||||||
|
execute('git', ['fetch', '--all', '--prune'], { cwd: repoRoot, env });
|
||||||
|
execute('git', ['pull', '--ff-only'], { cwd: repoRoot, env });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of validRecords) {
|
||||||
|
const installArgs = buildInstallApplyArgs(entry.record);
|
||||||
|
const args = [
|
||||||
|
path.join(repoRoot, 'scripts', 'install-apply.js'),
|
||||||
|
...installArgs,
|
||||||
|
'--json',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.dryRun) {
|
||||||
|
args.push('--dry-run');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const commandResult = execute(process.execPath, args, {
|
||||||
|
cwd: determineInstallCwd(entry.record, repoRoot),
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
|
||||||
|
let payload = null;
|
||||||
|
if (commandResult.stdout && commandResult.stdout.trim()) {
|
||||||
|
payload = JSON.parse(commandResult.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
adapter: entry.record.adapter,
|
||||||
|
installStatePath: entry.record.installStatePath,
|
||||||
|
repoRoot,
|
||||||
|
cwd: determineInstallCwd(entry.record, repoRoot),
|
||||||
|
installArgs,
|
||||||
|
status: options.dryRun ? 'planned' : 'updated',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
results.push({
|
||||||
|
adapter: entry.record.adapter,
|
||||||
|
installStatePath: entry.record.installStatePath,
|
||||||
|
repoRoot,
|
||||||
|
installArgs,
|
||||||
|
status: 'error',
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dryRun: Boolean(options.dryRun),
|
||||||
|
repoRoot,
|
||||||
|
results,
|
||||||
|
summary: {
|
||||||
|
checkedCount: results.length,
|
||||||
|
updatedCount: results.filter(result => result.status === 'updated' || result.status === 'planned').length,
|
||||||
|
errorCount: results.filter(result => result.status === 'error').length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHuman(result) {
|
||||||
|
if (result.results.length === 0) {
|
||||||
|
console.log('No ECC install-state files found for the current home/project context.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${result.dryRun ? 'Auto-update dry run' : 'Auto-update summary'}:\n`);
|
||||||
|
if (result.repoRoot) {
|
||||||
|
console.log(`Repo root: ${result.repoRoot}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of result.results) {
|
||||||
|
console.log(`- ${entry.adapter.id}`);
|
||||||
|
console.log(` Status: ${entry.status.toUpperCase()}`);
|
||||||
|
console.log(` Install-state: ${entry.installStatePath}`);
|
||||||
|
if (entry.error) {
|
||||||
|
console.log(` Error: ${entry.error}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` Reinstall args: ${entry.installArgs.join(' ') || '(none)'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\nSummary: checked=${result.summary.checkedCount}, ${result.dryRun ? 'planned' : 'updated'}=${result.summary.updatedCount}, errors=${result.summary.errorCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
try {
|
||||||
|
const options = parseArgs(process.argv);
|
||||||
|
if (options.help) {
|
||||||
|
showHelp(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = runAutoUpdate({
|
||||||
|
homeDir: process.env.HOME || os.homedir(),
|
||||||
|
projectRoot: process.cwd(),
|
||||||
|
targets: options.targets,
|
||||||
|
repoRoot: options.repoRoot,
|
||||||
|
dryRun: options.dryRun,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.json) {
|
||||||
|
console.log(JSON.stringify(result, null, 2));
|
||||||
|
} else {
|
||||||
|
printHuman(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exitCode = result.summary.errorCount > 0 ? 1 : 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parseArgs,
|
||||||
|
deriveRepoRootFromState,
|
||||||
|
buildInstallApplyArgs,
|
||||||
|
determineInstallCwd,
|
||||||
|
runAutoUpdate,
|
||||||
|
};
|
||||||
@@ -33,6 +33,10 @@ const COMMANDS = {
|
|||||||
script: 'repair.js',
|
script: 'repair.js',
|
||||||
description: 'Restore drifted or missing ECC-managed files',
|
description: 'Restore drifted or missing ECC-managed files',
|
||||||
},
|
},
|
||||||
|
'auto-update': {
|
||||||
|
script: 'auto-update.js',
|
||||||
|
description: 'Pull latest ECC changes and reinstall the current managed targets',
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
script: 'status.js',
|
script: 'status.js',
|
||||||
description: 'Query the ECC SQLite state store status summary',
|
description: 'Query the ECC SQLite state store status summary',
|
||||||
@@ -58,6 +62,7 @@ const PRIMARY_COMMANDS = [
|
|||||||
'list-installed',
|
'list-installed',
|
||||||
'doctor',
|
'doctor',
|
||||||
'repair',
|
'repair',
|
||||||
|
'auto-update',
|
||||||
'status',
|
'status',
|
||||||
'sessions',
|
'sessions',
|
||||||
'session-inspect',
|
'session-inspect',
|
||||||
@@ -90,6 +95,7 @@ Examples:
|
|||||||
ecc list-installed --json
|
ecc list-installed --json
|
||||||
ecc doctor --target cursor
|
ecc doctor --target cursor
|
||||||
ecc repair --dry-run
|
ecc repair --dry-run
|
||||||
|
ecc auto-update --dry-run
|
||||||
ecc status --json
|
ecc status --json
|
||||||
ecc sessions
|
ecc sessions
|
||||||
ecc sessions session-active --json
|
ecc sessions session-active --json
|
||||||
|
|||||||
392
tests/scripts/auto-update.test.js
Normal file
392
tests/scripts/auto-update.test.js
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
/**
|
||||||
|
* Tests for scripts/auto-update.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const {
|
||||||
|
parseArgs,
|
||||||
|
deriveRepoRootFromState,
|
||||||
|
buildInstallApplyArgs,
|
||||||
|
determineInstallCwd,
|
||||||
|
runAutoUpdate,
|
||||||
|
} = require('../../scripts/auto-update');
|
||||||
|
const {
|
||||||
|
createInstallState,
|
||||||
|
} = require('../../scripts/lib/install-state');
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
console.log(` \u2713 ${name}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` \u2717 ${name}`);
|
||||||
|
console.log(` Error: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTempDir(prefix) {
|
||||||
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(dirPath) {
|
||||||
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRecord({ repoRoot, homeDir, projectRoot, adapter, request, resolution, operations }) {
|
||||||
|
const targetRoot = adapter.kind === 'project'
|
||||||
|
? path.join(projectRoot, `.${adapter.target}`)
|
||||||
|
: path.join(homeDir, '.claude');
|
||||||
|
const installStatePath = adapter.kind === 'project'
|
||||||
|
? path.join(targetRoot, 'ecc-install-state.json')
|
||||||
|
: path.join(targetRoot, 'ecc', 'install-state.json');
|
||||||
|
|
||||||
|
const state = createInstallState({
|
||||||
|
adapter,
|
||||||
|
targetRoot,
|
||||||
|
installStatePath,
|
||||||
|
request,
|
||||||
|
resolution,
|
||||||
|
operations,
|
||||||
|
source: {
|
||||||
|
repoVersion: '1.10.0',
|
||||||
|
repoCommit: 'abc123',
|
||||||
|
manifestVersion: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
adapter,
|
||||||
|
targetRoot,
|
||||||
|
installStatePath,
|
||||||
|
exists: true,
|
||||||
|
state,
|
||||||
|
error: null,
|
||||||
|
repoRoot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureFakeRepo(repoRoot) {
|
||||||
|
fs.mkdirSync(path.join(repoRoot, 'scripts'), { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(repoRoot, 'package.json'),
|
||||||
|
JSON.stringify({ name: 'everything-claude-code', version: '1.10.0' }, null, 2)
|
||||||
|
);
|
||||||
|
fs.writeFileSync(path.join(repoRoot, 'scripts', 'install-apply.js'), '#!/usr/bin/env node\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
console.log('\n=== Testing auto-update.js ===\n');
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
if (test('parseArgs reads repo-root, target, dry-run, and json flags', () => {
|
||||||
|
const parsed = parseArgs([
|
||||||
|
'node',
|
||||||
|
'scripts/auto-update.js',
|
||||||
|
'--target',
|
||||||
|
'cursor',
|
||||||
|
'--repo-root',
|
||||||
|
'/tmp/ecc',
|
||||||
|
'--dry-run',
|
||||||
|
'--json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(parsed.targets, ['cursor']);
|
||||||
|
assert.strictEqual(parsed.repoRoot, '/tmp/ecc');
|
||||||
|
assert.strictEqual(parsed.dryRun, true);
|
||||||
|
assert.strictEqual(parsed.json, true);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('parseArgs rejects unknown arguments', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => parseArgs(['node', 'scripts/auto-update.js', '--bogus']),
|
||||||
|
/Unknown argument: --bogus/
|
||||||
|
);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('deriveRepoRootFromState uses sourcePath and sourceRelativePath', () => {
|
||||||
|
const state = {
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
sourcePath: path.join('/tmp', 'ecc', 'scripts', 'setup-package-manager.js'),
|
||||||
|
sourceRelativePath: path.join('scripts', 'setup-package-manager.js'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.strictEqual(deriveRepoRootFromState(state), path.join('/tmp', 'ecc'));
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('deriveRepoRootFromState fails when source metadata is unavailable', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => deriveRepoRootFromState({ operations: [{ destinationPath: '/tmp/file' }] }),
|
||||||
|
/Unable to infer ECC repo root/
|
||||||
|
);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('buildInstallApplyArgs reconstructs legacy installs', () => {
|
||||||
|
const record = {
|
||||||
|
adapter: { target: 'claude', kind: 'home' },
|
||||||
|
state: {
|
||||||
|
target: { target: 'claude' },
|
||||||
|
request: {
|
||||||
|
profile: null,
|
||||||
|
modules: [],
|
||||||
|
includeComponents: [],
|
||||||
|
excludeComponents: [],
|
||||||
|
legacyLanguages: ['typescript', 'python'],
|
||||||
|
legacyMode: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepStrictEqual(buildInstallApplyArgs(record), [
|
||||||
|
'--target', 'claude',
|
||||||
|
'typescript',
|
||||||
|
'python',
|
||||||
|
]);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('buildInstallApplyArgs reconstructs manifest installs', () => {
|
||||||
|
const record = {
|
||||||
|
adapter: { target: 'cursor', kind: 'project' },
|
||||||
|
state: {
|
||||||
|
target: { target: 'cursor' },
|
||||||
|
request: {
|
||||||
|
profile: 'developer',
|
||||||
|
modules: ['platform-configs'],
|
||||||
|
includeComponents: ['component:alpha'],
|
||||||
|
excludeComponents: ['component:beta'],
|
||||||
|
legacyLanguages: [],
|
||||||
|
legacyMode: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepStrictEqual(buildInstallApplyArgs(record), [
|
||||||
|
'--target', 'cursor',
|
||||||
|
'--profile', 'developer',
|
||||||
|
'--modules', 'platform-configs',
|
||||||
|
'--with', 'component:alpha',
|
||||||
|
'--without', 'component:beta',
|
||||||
|
]);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('determineInstallCwd uses the project root for project installs', () => {
|
||||||
|
const record = {
|
||||||
|
adapter: { kind: 'project' },
|
||||||
|
state: {
|
||||||
|
target: {
|
||||||
|
root: path.join('/tmp', 'project', '.cursor'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.strictEqual(determineInstallCwd(record, '/tmp/ecc'), path.join('/tmp', 'project'));
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('runAutoUpdate reports when no install-state files are present', () => {
|
||||||
|
const result = runAutoUpdate(
|
||||||
|
{
|
||||||
|
homeDir: '/tmp/home',
|
||||||
|
projectRoot: '/tmp/project',
|
||||||
|
dryRun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
discoverInstalledStates: () => [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.results.length, 0);
|
||||||
|
assert.strictEqual(result.summary.checkedCount, 0);
|
||||||
|
assert.strictEqual(result.summary.errorCount, 0);
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('runAutoUpdate rejects mixed inferred repo roots', () => {
|
||||||
|
const homeDir = createTempDir('auto-update-home-');
|
||||||
|
const projectRoot = createTempDir('auto-update-project-');
|
||||||
|
const repoOne = createTempDir('auto-update-repo-');
|
||||||
|
const repoTwo = createTempDir('auto-update-repo-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureFakeRepo(repoOne);
|
||||||
|
ensureFakeRepo(repoTwo);
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
makeRecord({
|
||||||
|
repoRoot: repoOne,
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
adapter: { id: 'claude-home', target: 'claude', kind: 'home' },
|
||||||
|
request: {
|
||||||
|
profile: null,
|
||||||
|
modules: [],
|
||||||
|
includeComponents: [],
|
||||||
|
excludeComponents: [],
|
||||||
|
legacyLanguages: ['typescript'],
|
||||||
|
legacyMode: true,
|
||||||
|
},
|
||||||
|
resolution: { selectedModules: ['legacy-claude-rules'], skippedModules: [] },
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
kind: 'copy-file',
|
||||||
|
moduleId: 'legacy-claude-rules',
|
||||||
|
sourcePath: path.join(repoOne, 'rules', 'common', 'coding-style.md'),
|
||||||
|
sourceRelativePath: path.join('rules', 'common', 'coding-style.md'),
|
||||||
|
destinationPath: path.join(homeDir, '.claude', 'rules', 'common', 'coding-style.md'),
|
||||||
|
strategy: 'preserve-relative-path',
|
||||||
|
ownership: 'managed',
|
||||||
|
scaffoldOnly: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
makeRecord({
|
||||||
|
repoRoot: repoTwo,
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
|
||||||
|
request: {
|
||||||
|
profile: 'core',
|
||||||
|
modules: [],
|
||||||
|
includeComponents: [],
|
||||||
|
excludeComponents: [],
|
||||||
|
legacyLanguages: [],
|
||||||
|
legacyMode: false,
|
||||||
|
},
|
||||||
|
resolution: { selectedModules: ['rules-core'], skippedModules: [] },
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
kind: 'copy-file',
|
||||||
|
moduleId: 'rules-core',
|
||||||
|
sourcePath: path.join(repoTwo, '.cursor', 'mcp.json'),
|
||||||
|
sourceRelativePath: path.join('.cursor', 'mcp.json'),
|
||||||
|
destinationPath: path.join(projectRoot, '.cursor', 'mcp.json'),
|
||||||
|
strategy: 'sync-root-children',
|
||||||
|
ownership: 'managed',
|
||||||
|
scaffoldOnly: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => runAutoUpdate(
|
||||||
|
{
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
dryRun: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
discoverInstalledStates: () => records,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
/Multiple ECC repo roots detected/
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
cleanup(homeDir);
|
||||||
|
cleanup(projectRoot);
|
||||||
|
cleanup(repoOne);
|
||||||
|
cleanup(repoTwo);
|
||||||
|
}
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
if (test('runAutoUpdate fetches, pulls, and reinstalls using reconstructed args', () => {
|
||||||
|
const homeDir = createTempDir('auto-update-home-');
|
||||||
|
const projectRoot = createTempDir('auto-update-project-');
|
||||||
|
const repoRoot = createTempDir('auto-update-repo-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureFakeRepo(repoRoot);
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
makeRecord({
|
||||||
|
repoRoot,
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
|
||||||
|
request: {
|
||||||
|
profile: 'developer',
|
||||||
|
modules: [],
|
||||||
|
includeComponents: ['component:alpha'],
|
||||||
|
excludeComponents: ['component:beta'],
|
||||||
|
legacyLanguages: [],
|
||||||
|
legacyMode: false,
|
||||||
|
},
|
||||||
|
resolution: { selectedModules: ['rules-core'], skippedModules: [] },
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
kind: 'copy-file',
|
||||||
|
moduleId: 'platform-configs',
|
||||||
|
sourcePath: path.join(repoRoot, '.cursor', 'mcp.json'),
|
||||||
|
sourceRelativePath: path.join('.cursor', 'mcp.json'),
|
||||||
|
destinationPath: path.join(projectRoot, '.cursor', 'mcp.json'),
|
||||||
|
strategy: 'sync-root-children',
|
||||||
|
ownership: 'managed',
|
||||||
|
scaffoldOnly: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const commands = [];
|
||||||
|
const result = runAutoUpdate(
|
||||||
|
{
|
||||||
|
homeDir,
|
||||||
|
projectRoot,
|
||||||
|
dryRun: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
discoverInstalledStates: () => records,
|
||||||
|
runExternalCommand: (command, args, options) => {
|
||||||
|
commands.push({ command, args, options });
|
||||||
|
if (command === process.execPath) {
|
||||||
|
return {
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
dryRun: false,
|
||||||
|
result: {
|
||||||
|
installStatePath: path.join(projectRoot, '.cursor', 'ecc-install-state.json'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
stderr: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { stdout: '', stderr: '' };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.summary.checkedCount, 1);
|
||||||
|
assert.strictEqual(result.summary.updatedCount, 1);
|
||||||
|
assert.deepStrictEqual(commands.map(entry => [entry.command, entry.args[0]]), [
|
||||||
|
['git', 'fetch'],
|
||||||
|
['git', 'pull'],
|
||||||
|
[process.execPath, path.join(repoRoot, 'scripts', 'install-apply.js')],
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(commands[2].args.slice(1), [
|
||||||
|
'--target', 'cursor',
|
||||||
|
'--profile', 'developer',
|
||||||
|
'--with', 'component:alpha',
|
||||||
|
'--without', 'component:beta',
|
||||||
|
'--json',
|
||||||
|
]);
|
||||||
|
assert.strictEqual(commands[2].options.cwd, projectRoot);
|
||||||
|
} finally {
|
||||||
|
cleanup(homeDir);
|
||||||
|
cleanup(projectRoot);
|
||||||
|
cleanup(repoRoot);
|
||||||
|
}
|
||||||
|
})) passed += 1; else failed += 1;
|
||||||
|
|
||||||
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests();
|
||||||
@@ -68,6 +68,7 @@ function main() {
|
|||||||
assert.match(result.stdout, /catalog/);
|
assert.match(result.stdout, /catalog/);
|
||||||
assert.match(result.stdout, /list-installed/);
|
assert.match(result.stdout, /list-installed/);
|
||||||
assert.match(result.stdout, /doctor/);
|
assert.match(result.stdout, /doctor/);
|
||||||
|
assert.match(result.stdout, /auto-update/);
|
||||||
}],
|
}],
|
||||||
['delegates explicit install command', () => {
|
['delegates explicit install command', () => {
|
||||||
const result = runCli(['install', '--dry-run', '--json', 'typescript']);
|
const result = runCli(['install', '--dry-run', '--json', 'typescript']);
|
||||||
@@ -112,6 +113,17 @@ function main() {
|
|||||||
const payload = parseJson(result.stdout);
|
const payload = parseJson(result.stdout);
|
||||||
assert.deepStrictEqual(payload.records, []);
|
assert.deepStrictEqual(payload.records, []);
|
||||||
}],
|
}],
|
||||||
|
['delegates auto-update command', () => {
|
||||||
|
const homeDir = createTempDir('ecc-cli-home-');
|
||||||
|
const projectRoot = createTempDir('ecc-cli-project-');
|
||||||
|
const result = runCli(['auto-update', '--dry-run', '--json'], {
|
||||||
|
cwd: projectRoot,
|
||||||
|
env: { HOME: homeDir },
|
||||||
|
});
|
||||||
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
|
const payload = parseJson(result.stdout);
|
||||||
|
assert.deepStrictEqual(payload.results, []);
|
||||||
|
}],
|
||||||
['delegates session-inspect command', () => {
|
['delegates session-inspect command', () => {
|
||||||
const homeDir = createTempDir('ecc-cli-home-');
|
const homeDir = createTempDir('ecc-cli-home-');
|
||||||
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
const sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
||||||
@@ -135,6 +147,11 @@ function main() {
|
|||||||
assert.strictEqual(result.status, 0, result.stderr);
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
assert.match(result.stdout, /Usage: node scripts\/repair\.js/);
|
assert.match(result.stdout, /Usage: node scripts\/repair\.js/);
|
||||||
}],
|
}],
|
||||||
|
['supports help for the auto-update subcommand', () => {
|
||||||
|
const result = runCli(['help', 'auto-update']);
|
||||||
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
|
assert.match(result.stdout, /Usage: node scripts\/auto-update\.js/);
|
||||||
|
}],
|
||||||
['supports help for the catalog subcommand', () => {
|
['supports help for the catalog subcommand', () => {
|
||||||
const result = runCli(['help', 'catalog']);
|
const result = runCli(['help', 'catalog']);
|
||||||
assert.strictEqual(result.status, 0, result.stderr);
|
assert.strictEqual(result.status, 0, result.stderr);
|
||||||
|
|||||||
Reference in New Issue
Block a user