@sushanglewis/lincoln 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.d.ts +3 -0
- package/dist/main.js +1231 -906
- package/dist/main.js.map +1 -1
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.js +18 -0
- package/dist/postinstall.js.map +1 -0
- package/framework/.claude/agents/_contract.md +79 -0
- package/framework/.claude/agents/architect.md +31 -0
- package/framework/.claude/agents/default.md +151 -0
- package/framework/.claude/agents/designer.md +33 -0
- package/framework/.claude/agents/engineer.md +42 -0
- package/framework/.claude/agents/external/NOTICES.md +12 -0
- package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
- package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
- package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
- package/framework/.claude/agents/frontend-engineer.md +32 -0
- package/framework/.claude/agents/knowledge-curator.md +28 -0
- package/framework/.claude/agents/pm.md +38 -0
- package/framework/.claude/agents/qa.md +28 -0
- package/framework/.claude/agents/release-coordinator.md +25 -0
- package/framework/.claude/agents/researcher.md +41 -0
- package/framework/.claude/harnesses/claude-code.yaml +15 -0
- package/framework/.claude/harnesses/codex.yaml +40 -0
- package/framework/.claude/harnesses/command-map.yaml +255 -0
- package/framework/.claude/harnesses/opencode.yaml +32 -0
- package/framework/.claude/harnesses/scenarios.yaml +45 -0
- package/framework/.claude/hooks/on-pr-event.sh +69 -0
- package/framework/.claude/hooks/on-session-start.sh +427 -0
- package/framework/.claude/hooks/on-stop.sh +104 -0
- package/framework/.claude/hooks/post-tool-use.sh +161 -0
- package/framework/.claude/hooks/pre-tool-use.sh +246 -0
- package/framework/.claude/schemas/agent-definition.schema.json +35 -0
- package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
- package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
- package/framework/.claude/schemas/stage-definition.schema.json +233 -0
- package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
- package/framework/.claude/schemas/workflow-template.schema.json +88 -0
- package/framework/.claude/settings.json +14 -0
- package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
- package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
- package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
- package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
- package/framework/.claude/skills/dependencies.yaml +139 -0
- package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
- package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
- package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
- package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
- package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
- package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
- package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
- package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
- package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
- package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
- package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
- package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
- package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
- package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
- package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
- package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
- package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
- package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
- package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
- package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
- package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
- package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
- package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
- package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
- package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
- package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
- package/framework/.claude/skills/lc-status/SKILL.md +25 -0
- package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
- package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
- package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
- package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
- package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
- package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
- package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
- package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
- package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
- package/framework/.claude/skills/process-interview/SKILL.md +32 -0
- package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
- package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
- package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
- package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
- package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
- package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
- package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
- package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
- package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
- package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
- package/framework/.claude/stages/clarify.yaml +59 -0
- package/framework/.claude/stages/explore-opensource.yaml +40 -0
- package/framework/.claude/stages/implement.yaml +60 -0
- package/framework/.claude/stages/ingest.yaml +40 -0
- package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
- package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
- package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
- package/framework/.claude/stages/lc-first-principles.yaml +37 -0
- package/framework/.claude/stages/lc-market-research.yaml +37 -0
- package/framework/.claude/stages/lc-product-research.yaml +37 -0
- package/framework/.claude/stages/lc-research-report.yaml +38 -0
- package/framework/.claude/stages/lc-research-scope.yaml +38 -0
- package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
- package/framework/.claude/stages/lc-storytelling.yaml +37 -0
- package/framework/.claude/stages/product-design-docs.yaml +66 -0
- package/framework/.claude/stages/product-prototype.yaml +53 -0
- package/framework/.claude/stages/propose.yaml +45 -0
- package/framework/.claude/stages/split.yaml +41 -0
- package/framework/.claude/stages/sync-knowledge.yaml +49 -0
- package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
- package/framework/.claude/stages/workflow-router.yaml +45 -0
- package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
- package/framework/.claude/workflows/README.md +93 -0
- package/framework/.claude/workflows/bug-fix.yaml +81 -0
- package/framework/.claude/workflows/design-spike.yaml +55 -0
- package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
- package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
- package/framework/.claude/workflows/oss-first-design.yaml +100 -0
- package/framework/.claude/workflows/pm-research.yaml +115 -0
- package/framework/.claude-plugin/hooks.json +6 -0
- package/framework/.claude-plugin/marketplace.json +32 -0
- package/framework/.claude-plugin/plugin.json +57 -0
- package/framework/.version-bump.json +43 -0
- package/framework/CLAUDE.md +140 -0
- package/framework/CONTRIBUTING.md +219 -0
- package/framework/LICENSE +21 -0
- package/framework/README.en.md +139 -0
- package/framework/README.md +139 -0
- package/framework/RELEASE.md +334 -0
- package/framework/SKILL.md +24 -0
- package/framework/USAGE.md +356 -0
- package/framework/manifest.json +1214 -0
- package/framework/requirements.txt +4 -0
- package/framework/scripts/__init__.py +0 -0
- package/framework/scripts/build_codebase_knowledge.py +76 -0
- package/framework/scripts/bump_version.py +181 -0
- package/framework/scripts/check-dependency-drift.py +69 -0
- package/framework/scripts/check-harness-drift.sh +57 -0
- package/framework/scripts/check-main-merge-hygiene.py +128 -0
- package/framework/scripts/check-skill-dependencies.sh +124 -0
- package/framework/scripts/init-lincoln-branch.sh +300 -0
- package/framework/scripts/init-project.sh +184 -0
- package/framework/scripts/lc-benchmark-cli.py +66 -0
- package/framework/scripts/lincoln-audit.py +288 -0
- package/framework/scripts/lincoln-setup.py +651 -0
- package/framework/scripts/lincoln-status.py +448 -0
- package/framework/scripts/lincoln_benchmark.py +224 -0
- package/framework/scripts/lincoln_benchmark_eval.py +104 -0
- package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
- package/framework/scripts/lincoln_benchmark_report.py +141 -0
- package/framework/scripts/lincoln_command_map.py +289 -0
- package/framework/scripts/lincoln_dependency_manager.py +335 -0
- package/framework/scripts/lincoln_documents.py +175 -0
- package/framework/scripts/lincoln_harness_adapter.py +319 -0
- package/framework/scripts/lincoln_index.py +255 -0
- package/framework/scripts/lincoln_paths.py +198 -0
- package/framework/scripts/lincoln_prd.py +227 -0
- package/framework/scripts/lincoln_role.py +46 -0
- package/framework/scripts/lincoln_scenario.py +59 -0
- package/framework/scripts/lincoln_session_start_metrics.py +71 -0
- package/framework/scripts/lincoln_skill_prompt.py +48 -0
- package/framework/scripts/lincoln_trace.py +335 -0
- package/framework/scripts/lincoln_workflow.py +199 -0
- package/framework/scripts/list-active-lincoln-branches.sh +87 -0
- package/framework/scripts/package-lincoln-plugin.py +340 -0
- package/framework/scripts/run-infrastructure-tests.py +84 -0
- package/framework/scripts/stage_loader.py +1071 -0
- package/framework/scripts/static-check.sh +356 -0
- package/framework/scripts/sync-external-agents.sh +210 -0
- package/framework/scripts/sync-framework-package.mjs +218 -0
- package/framework/scripts/task_tool_guard.py +213 -0
- package/framework/scripts/track-artifacts.py +140 -0
- package/framework/scripts/validate_stage.py +350 -0
- package/{README.md → framework/tools/lincoln/README.md} +4 -4
- package/framework/tools/lincoln/package-lock.json +3349 -0
- package/framework/tools/lincoln/package.json +48 -0
- package/framework/tools/lincoln/src/cli.tsx +60 -0
- package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
- package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
- package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
- package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
- package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
- package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
- package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
- package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
- package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
- package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
- package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
- package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
- package/framework/tools/lincoln/src/config/args.ts +35 -0
- package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
- package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
- package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
- package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
- package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
- package/framework/tools/lincoln/src/main.tsx +4 -0
- package/framework/tools/lincoln/src/models/models.ts +34 -0
- package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
- package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
- package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
- package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
- package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
- package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
- package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
- package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
- package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
- package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
- package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
- package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
- package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
- package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
- package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
- package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
- package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
- package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
- package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
- package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
- package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
- package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
- package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
- package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
- package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
- package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
- package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
- package/framework/tools/lincoln/tsconfig.json +22 -0
- package/framework/tools/lincoln/tsconfig.tests.json +10 -0
- package/framework/tools/lincoln/tsup.config.ts +17 -0
- package/framework/tools/lincoln/vitest.config.ts +20 -0
- package/framework/tools/lincoln-installer/package-lock.json +3611 -0
- package/framework/tools/lincoln-installer/package.json +53 -0
- package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
- package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
- package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
- package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
- package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
- package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
- package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
- package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
- package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
- package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
- package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
- package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
- package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
- package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
- package/framework/tools/lincoln-installer/src/main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/types.ts +19 -0
- package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
- package/framework/tools/lincoln-installer/src/update.tsx +90 -0
- package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
- package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
- package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
- package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
- package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
- package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
- package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
- package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
- package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
- package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
- package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
- package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
- package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
- package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
- package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
- package/framework/tools/lincoln-installer/tsconfig.json +22 -0
- package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
- package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
- package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
- package/framework/tools/lincoln-record/Cargo.lock +1896 -0
- package/framework/tools/lincoln-record/Cargo.toml +24 -0
- package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
- package/framework/tools/lincoln-record/diarize/merge.py +55 -0
- package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
- package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
- package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
- package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
- package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
- package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
- package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
- package/framework/tools/lincoln-record/src/cli.rs +96 -0
- package/framework/tools/lincoln-record/src/config.rs +135 -0
- package/framework/tools/lincoln-record/src/lib.rs +6 -0
- package/framework/tools/lincoln-record/src/main.rs +242 -0
- package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
- package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
- package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
- package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
- package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
- package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
- package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
- package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
- package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
- package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
- package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
- package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
- package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
- package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
- package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
- package/package.json +19 -13
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs'
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
} from 'node:fs'
|
|
10
|
+
import { dirname, join, relative, resolve, sep } from 'node:path'
|
|
11
|
+
import { extract, ReadEntry } from 'tar'
|
|
12
|
+
|
|
13
|
+
export interface ReleaseInfo {
|
|
14
|
+
version: string
|
|
15
|
+
assetUrl: string
|
|
16
|
+
publishedAt: string
|
|
17
|
+
checksumUrl?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MergeResult {
|
|
21
|
+
updated: string[]
|
|
22
|
+
preserved: string[]
|
|
23
|
+
backupDir: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UpdateOptions {
|
|
27
|
+
root: string
|
|
28
|
+
repo?: string
|
|
29
|
+
dryRun?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UpdateResult {
|
|
33
|
+
success: boolean
|
|
34
|
+
fromVersion?: string
|
|
35
|
+
toVersion?: string
|
|
36
|
+
updated: string[]
|
|
37
|
+
preserved: string[]
|
|
38
|
+
backupDir?: string
|
|
39
|
+
error?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const GITHUB_API_BASE = 'https://api.github.com/repos'
|
|
43
|
+
const DEFAULT_REPO = 'sushanglewis/Lincoln'
|
|
44
|
+
const FETCH_TIMEOUT_MS = 15_000
|
|
45
|
+
const DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024 // 100 MB
|
|
46
|
+
const ALLOWED_ASSET_HOSTS = new Set([
|
|
47
|
+
'github.com',
|
|
48
|
+
'objects.githubusercontent.com',
|
|
49
|
+
'release-assets.githubusercontent.com',
|
|
50
|
+
])
|
|
51
|
+
const REPO_SLUG_REGEX = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/
|
|
52
|
+
|
|
53
|
+
export const ALLOWLIST_DIRS = ['.claude', '.claude-plugin', 'scripts', 'tools']
|
|
54
|
+
export const ALLOWLIST_FILES = [
|
|
55
|
+
'README.md',
|
|
56
|
+
'CLAUDE.md',
|
|
57
|
+
'LICENSE',
|
|
58
|
+
'RELEASE.md',
|
|
59
|
+
'.version-bump.json',
|
|
60
|
+
'requirements.txt',
|
|
61
|
+
'SKILL.md',
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export const PRESERVE_PATHS = [
|
|
65
|
+
'.context',
|
|
66
|
+
'.github/openspec-config.yml',
|
|
67
|
+
'recordings',
|
|
68
|
+
'issue-',
|
|
69
|
+
'.venv',
|
|
70
|
+
'venv',
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
export function validateRepoSlug(repo: string): void {
|
|
74
|
+
if (!REPO_SLUG_REGEX.test(repo)) {
|
|
75
|
+
throw new Error(`Invalid repository slug: ${repo}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getCurrentVersion(root: string): string | undefined {
|
|
80
|
+
const path = join(root, '.version-bump.json')
|
|
81
|
+
if (!existsSync(path)) {
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'))
|
|
86
|
+
if (typeof data.version === 'string' && data.version) {
|
|
87
|
+
return data.version
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseVersion(version: string): number[] {
|
|
96
|
+
return version.split('.').map((part) => {
|
|
97
|
+
const num = Number(part)
|
|
98
|
+
return Number.isNaN(num) ? 0 : num
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isUpdateNeeded(current: string, latest: string): boolean {
|
|
103
|
+
const currentParts = parseVersion(current)
|
|
104
|
+
const latestParts = parseVersion(latest)
|
|
105
|
+
const maxLength = Math.max(currentParts.length, latestParts.length)
|
|
106
|
+
for (let i = 0; i < maxLength; i++) {
|
|
107
|
+
const currentPart = currentParts[i] ?? 0
|
|
108
|
+
const latestPart = latestParts[i] ?? 0
|
|
109
|
+
if (latestPart > currentPart) {
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
if (latestPart < currentPart) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isSemverTag(tag: string): boolean {
|
|
120
|
+
return /^v?\d+(\.\d+)*$/.test(tag)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function fetchLatestRelease(repo: string): Promise<ReleaseInfo> {
|
|
124
|
+
validateRepoSlug(repo)
|
|
125
|
+
const url = `${GITHUB_API_BASE}/${repo}/releases/latest`
|
|
126
|
+
const response = await fetch(url, {
|
|
127
|
+
headers: { Accept: 'application/vnd.github+json' },
|
|
128
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
129
|
+
})
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(`GitHub API error ${response.status}: ${response.statusText}`)
|
|
132
|
+
}
|
|
133
|
+
const data = (await response.json()) as {
|
|
134
|
+
tag_name: string
|
|
135
|
+
published_at: string
|
|
136
|
+
assets: Array<{ name: string; browser_download_url: string }>
|
|
137
|
+
}
|
|
138
|
+
const version = data.tag_name.replace(/^v/, '')
|
|
139
|
+
if (!isSemverTag(data.tag_name)) {
|
|
140
|
+
throw new Error(`Unexpected release tag: ${data.tag_name}`)
|
|
141
|
+
}
|
|
142
|
+
const asset = data.assets.find((a) => a.name.endsWith('.tar.gz'))
|
|
143
|
+
if (!asset) {
|
|
144
|
+
throw new Error(`No tar.gz asset found for release ${data.tag_name}`)
|
|
145
|
+
}
|
|
146
|
+
const checksumAsset = data.assets.find((a) => a.name === `${asset.name}.sha256`)
|
|
147
|
+
return {
|
|
148
|
+
version,
|
|
149
|
+
assetUrl: asset.browser_download_url,
|
|
150
|
+
publishedAt: data.published_at,
|
|
151
|
+
checksumUrl: checksumAsset?.browser_download_url,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validateAssetUrl(url: string): void {
|
|
156
|
+
const parsed = new URL(url)
|
|
157
|
+
if (parsed.protocol !== 'https:') {
|
|
158
|
+
throw new Error(`Download URL must use HTTPS: ${url}`)
|
|
159
|
+
}
|
|
160
|
+
if (!ALLOWED_ASSET_HOSTS.has(parsed.hostname)) {
|
|
161
|
+
throw new Error(`Unexpected download host: ${parsed.hostname}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function fetchAsset(url: string, redirectCount = 0): Promise<Response> {
|
|
166
|
+
if (redirectCount > 5) {
|
|
167
|
+
throw new Error('Too many download redirects')
|
|
168
|
+
}
|
|
169
|
+
validateAssetUrl(url)
|
|
170
|
+
const response = await fetch(url, {
|
|
171
|
+
redirect: 'manual',
|
|
172
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
173
|
+
})
|
|
174
|
+
if (response.status >= 300 && response.status < 400) {
|
|
175
|
+
const location = response.headers.get('location')
|
|
176
|
+
if (!location) {
|
|
177
|
+
throw new Error(`Redirect without Location header from ${url}`)
|
|
178
|
+
}
|
|
179
|
+
return fetchAsset(new URL(location, url).toString(), redirectCount + 1)
|
|
180
|
+
}
|
|
181
|
+
return response
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function writeBodyWithLimit(
|
|
185
|
+
body: ReadableStream<Uint8Array> | null,
|
|
186
|
+
dest: string,
|
|
187
|
+
maxBytes: number
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
if (!body) {
|
|
190
|
+
throw new Error('Download response has no body')
|
|
191
|
+
}
|
|
192
|
+
mkdirSync(dirname(dest), { recursive: true })
|
|
193
|
+
const reader = body.getReader()
|
|
194
|
+
const fileStream = createWriteStream(dest)
|
|
195
|
+
let received = 0
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
while (true) {
|
|
199
|
+
const { done, value } = await reader.read()
|
|
200
|
+
if (done) break
|
|
201
|
+
received += value.byteLength
|
|
202
|
+
if (received > maxBytes) {
|
|
203
|
+
throw new Error(`Download exceeds maximum size of ${maxBytes} bytes`)
|
|
204
|
+
}
|
|
205
|
+
fileStream.write(value)
|
|
206
|
+
}
|
|
207
|
+
} finally {
|
|
208
|
+
fileStream.end()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await new Promise<void>((resolve, reject) => {
|
|
212
|
+
fileStream.on('finish', resolve)
|
|
213
|
+
fileStream.on('error', reject)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function downloadAsset(url: string, dest: string): Promise<void> {
|
|
218
|
+
const response = await fetchAsset(url)
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
throw new Error(`Download failed ${response.status}: ${response.statusText}`)
|
|
221
|
+
}
|
|
222
|
+
await writeBodyWithLimit(response.body, dest, DOWNLOAD_MAX_BYTES)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function verifyChecksum(archivePath: string, checksumUrl: string): Promise<void> {
|
|
226
|
+
const response = await fetchAsset(checksumUrl)
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new Error(`Checksum download failed ${response.status}: ${response.statusText}`)
|
|
229
|
+
}
|
|
230
|
+
const text = await response.text()
|
|
231
|
+
const expected = text.trim().split(/\s+/)[0]?.toLowerCase()
|
|
232
|
+
if (!expected) {
|
|
233
|
+
throw new Error('Checksum file is empty')
|
|
234
|
+
}
|
|
235
|
+
const actual = await computeSha256(archivePath)
|
|
236
|
+
if (actual !== expected) {
|
|
237
|
+
throw new Error(`Checksum mismatch: expected ${expected}, got ${actual}`)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function computeSha256(filePath: string): Promise<string> {
|
|
242
|
+
const { createHash } = await import('node:crypto')
|
|
243
|
+
const hash = createHash('sha256')
|
|
244
|
+
const { readFile } = await import('node:fs/promises')
|
|
245
|
+
hash.update(await readFile(filePath))
|
|
246
|
+
return hash.digest('hex').toLowerCase()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function extractArchive(archivePath: string, dest: string): Promise<void> {
|
|
250
|
+
mkdirSync(dest, { recursive: true })
|
|
251
|
+
await extract({
|
|
252
|
+
file: archivePath,
|
|
253
|
+
cwd: dest,
|
|
254
|
+
filter: (_path: string, entry: ReadEntry | import('node:fs').Stats) => {
|
|
255
|
+
const readEntry = entry as ReadEntry
|
|
256
|
+
return readEntry.type === 'File' || readEntry.type === 'Directory'
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function normalizeRelativePath(relativePath: string): string {
|
|
262
|
+
return relativePath.replace(/\\/g, '/')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function isPreserved(relativePath: string): boolean {
|
|
266
|
+
const posix = normalizeRelativePath(relativePath)
|
|
267
|
+
return PRESERVE_PATHS.some((prefix) => {
|
|
268
|
+
if (posix === prefix) return true
|
|
269
|
+
if (prefix.endsWith('-')) {
|
|
270
|
+
return posix.startsWith(prefix)
|
|
271
|
+
}
|
|
272
|
+
return posix.startsWith(`${prefix}/`)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function backupPath(projectRoot: string, relativePath: string): string {
|
|
277
|
+
return resolve(projectRoot, '.context', 'lincoln-update-backup', relativePath)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function backupIfExists(projectRoot: string, relativePath: string): void {
|
|
281
|
+
const target = resolve(projectRoot, relativePath)
|
|
282
|
+
if (!existsSync(target)) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
const backup = backupPath(projectRoot, relativePath)
|
|
286
|
+
mkdirSync(dirname(backup), { recursive: true })
|
|
287
|
+
copyFileSync(target, backup)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function restoreBackups(projectRoot: string, relativePaths: string[]): void {
|
|
291
|
+
for (const relativePath of relativePaths) {
|
|
292
|
+
const backup = backupPath(projectRoot, relativePath)
|
|
293
|
+
const target = resolve(projectRoot, relativePath)
|
|
294
|
+
if (existsSync(backup)) {
|
|
295
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
296
|
+
copyFileSync(backup, target)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function resolveStagingRoot(stagingRoot: string): string {
|
|
302
|
+
const entries = readdirSync(stagingRoot, { withFileTypes: true })
|
|
303
|
+
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
304
|
+
return resolve(stagingRoot, entries[0].name)
|
|
305
|
+
}
|
|
306
|
+
return stagingRoot
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function mergeRelease(
|
|
310
|
+
stagingRoot: string,
|
|
311
|
+
projectRoot: string
|
|
312
|
+
): Promise<MergeResult> {
|
|
313
|
+
const updated: string[] = []
|
|
314
|
+
const backedUp: string[] = []
|
|
315
|
+
const preservedSet = new Set<string>()
|
|
316
|
+
const resolvedStaging = resolveStagingRoot(stagingRoot)
|
|
317
|
+
const backupDir = resolve(projectRoot, '.context', 'lincoln-update-backup')
|
|
318
|
+
|
|
319
|
+
function collectFiles(dir: string): string[] {
|
|
320
|
+
const files: string[] = []
|
|
321
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
322
|
+
if (entry.isSymbolicLink()) {
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
const fullPath = join(dir, entry.name)
|
|
326
|
+
if (entry.isDirectory()) {
|
|
327
|
+
files.push(...collectFiles(fullPath))
|
|
328
|
+
} else if (entry.isFile()) {
|
|
329
|
+
files.push(fullPath)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return files
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function processSource(sourcePath: string, relativePath: string): void {
|
|
336
|
+
if (isPreserved(relativePath)) {
|
|
337
|
+
const existingTarget = resolve(projectRoot, relativePath)
|
|
338
|
+
if (existsSync(existingTarget)) {
|
|
339
|
+
const topLevel = PRESERVE_PATHS.find((prefix) => {
|
|
340
|
+
const posix = normalizeRelativePath(relativePath)
|
|
341
|
+
if (prefix.endsWith('-')) {
|
|
342
|
+
return posix === prefix || posix.startsWith(prefix)
|
|
343
|
+
}
|
|
344
|
+
return posix === prefix || posix.startsWith(`${prefix}/`)
|
|
345
|
+
})
|
|
346
|
+
if (topLevel) {
|
|
347
|
+
preservedSet.add(topLevel)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const targetPath = resolve(projectRoot, relativePath)
|
|
354
|
+
backupIfExists(projectRoot, relativePath)
|
|
355
|
+
backedUp.push(relativePath)
|
|
356
|
+
mkdirSync(dirname(targetPath), { recursive: true })
|
|
357
|
+
copyFileSync(sourcePath, targetPath)
|
|
358
|
+
updated.push(relativePath)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
for (const dirName of ALLOWLIST_DIRS) {
|
|
363
|
+
const sourceDir = join(resolvedStaging, dirName)
|
|
364
|
+
if (!existsSync(sourceDir)) {
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
for (const file of collectFiles(sourceDir)) {
|
|
368
|
+
const rel = relative(sourceDir, file)
|
|
369
|
+
processSource(file, normalizeRelativePath(join(dirName, rel)))
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (const fileName of ALLOWLIST_FILES) {
|
|
374
|
+
const sourceFile = join(resolvedStaging, fileName)
|
|
375
|
+
if (!existsSync(sourceFile)) {
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
processSource(sourceFile, fileName)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for (const prefix of PRESERVE_PATHS) {
|
|
382
|
+
if (existsSync(resolve(projectRoot, prefix))) {
|
|
383
|
+
preservedSet.add(prefix)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
387
|
+
restoreBackups(projectRoot, backedUp)
|
|
388
|
+
throw err
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { updated, preserved: Array.from(preservedSet), backupDir }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export async function updateRelease(options: UpdateOptions): Promise<UpdateResult> {
|
|
395
|
+
const { root, repo = DEFAULT_REPO, dryRun = false } = options
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const currentVersion = getCurrentVersion(root)
|
|
399
|
+
if (!currentVersion) {
|
|
400
|
+
return { success: false, updated: [], preserved: [], error: 'Could not detect current Lincoln version' }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const latest = await fetchLatestRelease(repo)
|
|
404
|
+
if (!isUpdateNeeded(currentVersion, latest.version)) {
|
|
405
|
+
return {
|
|
406
|
+
success: true,
|
|
407
|
+
fromVersion: currentVersion,
|
|
408
|
+
toVersion: currentVersion,
|
|
409
|
+
updated: [],
|
|
410
|
+
preserved: [],
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (dryRun) {
|
|
415
|
+
return {
|
|
416
|
+
success: true,
|
|
417
|
+
fromVersion: currentVersion,
|
|
418
|
+
toVersion: latest.version,
|
|
419
|
+
updated: [],
|
|
420
|
+
preserved: [],
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const tempDir = join(root, '.context', 'lincoln-update-staging')
|
|
425
|
+
mkdirSync(tempDir, { recursive: true })
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const archivePath = join(tempDir, 'lincoln-latest.tar.gz')
|
|
429
|
+
const extractDir = join(tempDir, 'extracted')
|
|
430
|
+
|
|
431
|
+
await downloadAsset(latest.assetUrl, archivePath)
|
|
432
|
+
if (latest.checksumUrl) {
|
|
433
|
+
await verifyChecksum(archivePath, latest.checksumUrl)
|
|
434
|
+
}
|
|
435
|
+
await extractArchive(archivePath, extractDir)
|
|
436
|
+
|
|
437
|
+
const merge = await mergeRelease(extractDir, root)
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
success: true,
|
|
441
|
+
fromVersion: currentVersion,
|
|
442
|
+
toVersion: latest.version,
|
|
443
|
+
updated: merge.updated,
|
|
444
|
+
preserved: merge.preserved,
|
|
445
|
+
backupDir: merge.backupDir,
|
|
446
|
+
}
|
|
447
|
+
} finally {
|
|
448
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
449
|
+
}
|
|
450
|
+
} catch (err) {
|
|
451
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
452
|
+
return { success: false, updated: [], preserved: [], error: message }
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'
|
|
3
|
+
import { render } from 'ink-testing-library'
|
|
4
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { InstallerApp } from '../../src/components/InstallerApp'
|
|
8
|
+
import type { InstallerConfig } from '../../src/config/resolveConfig'
|
|
9
|
+
import * as runSetupModule from '../../src/utils/runSetup'
|
|
10
|
+
|
|
11
|
+
async function tick() {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, 0))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createProjectDir(): string {
|
|
16
|
+
const root = mkdtempSync(join(tmpdir(), 'lincoln-'))
|
|
17
|
+
const scriptsDir = join(root, 'scripts')
|
|
18
|
+
mkdirSync(scriptsDir)
|
|
19
|
+
writeFileSync(join(scriptsDir, 'lincoln-setup.py'), '')
|
|
20
|
+
return root
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('InstallerApp', () => {
|
|
24
|
+
const dirs: string[] = []
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.spyOn(runSetupModule, 'runSetup').mockResolvedValue({
|
|
28
|
+
success: true,
|
|
29
|
+
code: 0,
|
|
30
|
+
stdout: '{"ok":true}',
|
|
31
|
+
stderr: '',
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
vi.restoreAllMocks()
|
|
37
|
+
for (const dir of dirs) {
|
|
38
|
+
rmSync(dir, { recursive: true, force: true })
|
|
39
|
+
}
|
|
40
|
+
dirs.length = 0
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('renders welcome screen by default', () => {
|
|
44
|
+
const root = createProjectDir()
|
|
45
|
+
dirs.push(root)
|
|
46
|
+
const config: InstallerConfig = {
|
|
47
|
+
root,
|
|
48
|
+
harnesses: [],
|
|
49
|
+
yes: false,
|
|
50
|
+
dryRun: false,
|
|
51
|
+
format: 'human',
|
|
52
|
+
}
|
|
53
|
+
const { lastFrame } = render(<InstallerApp config={config} />)
|
|
54
|
+
expect(lastFrame() ?? '').toContain('Welcome to Lincoln')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('navigates from welcome to summary via input', async () => {
|
|
58
|
+
const root = createProjectDir()
|
|
59
|
+
dirs.push(root)
|
|
60
|
+
const config: InstallerConfig = {
|
|
61
|
+
root,
|
|
62
|
+
harnesses: [],
|
|
63
|
+
yes: false,
|
|
64
|
+
dryRun: false,
|
|
65
|
+
format: 'human',
|
|
66
|
+
}
|
|
67
|
+
const { lastFrame, stdin } = render(<InstallerApp config={config} />)
|
|
68
|
+
|
|
69
|
+
await tick()
|
|
70
|
+
stdin.write('\r')
|
|
71
|
+
await tick()
|
|
72
|
+
expect(lastFrame() ?? '').toContain('Select agent harnesses')
|
|
73
|
+
|
|
74
|
+
stdin.write('\r')
|
|
75
|
+
await tick()
|
|
76
|
+
expect(lastFrame() ?? '').toContain('Options')
|
|
77
|
+
|
|
78
|
+
stdin.write('\r')
|
|
79
|
+
await tick()
|
|
80
|
+
expect(lastFrame() ?? '').toContain('Summary')
|
|
81
|
+
|
|
82
|
+
stdin.write('\r')
|
|
83
|
+
await tick()
|
|
84
|
+
await vi.waitFor(() => expect(lastFrame() ?? '').toContain('complete'), {
|
|
85
|
+
timeout: 2000,
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
3
|
+
import { render } from 'ink-testing-library'
|
|
4
|
+
import { SelectMenu } from '../../src/components/SelectMenu'
|
|
5
|
+
|
|
6
|
+
describe('SelectMenu', () => {
|
|
7
|
+
test('renders items and marks selected', () => {
|
|
8
|
+
const onSelect = vi.fn()
|
|
9
|
+
const onConfirm = vi.fn()
|
|
10
|
+
const { lastFrame } = render(
|
|
11
|
+
<SelectMenu
|
|
12
|
+
title="Choose"
|
|
13
|
+
items={[
|
|
14
|
+
{ label: 'One', value: 'one' },
|
|
15
|
+
{ label: 'Two', value: 'two' },
|
|
16
|
+
]}
|
|
17
|
+
selected={['one']}
|
|
18
|
+
onSelect={onSelect}
|
|
19
|
+
onConfirm={onConfirm}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
const frame = lastFrame() ?? ''
|
|
23
|
+
expect(frame).toContain('One')
|
|
24
|
+
expect(frame).toContain('Two')
|
|
25
|
+
})
|
|
26
|
+
})
|