trellis-herbivore 0.1.0
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/bin/trellis.js +3 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +174 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/channel/adapters/claude.d.ts +38 -0
- package/dist/commands/channel/adapters/claude.d.ts.map +1 -0
- package/dist/commands/channel/adapters/claude.js +209 -0
- package/dist/commands/channel/adapters/claude.js.map +1 -0
- package/dist/commands/channel/adapters/codex.d.ts +77 -0
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -0
- package/dist/commands/channel/adapters/codex.js +495 -0
- package/dist/commands/channel/adapters/codex.js.map +1 -0
- package/dist/commands/channel/adapters/index.d.ts +79 -0
- package/dist/commands/channel/adapters/index.d.ts.map +1 -0
- package/dist/commands/channel/adapters/index.js +109 -0
- package/dist/commands/channel/adapters/index.js.map +1 -0
- package/dist/commands/channel/adapters/types.d.ts +33 -0
- package/dist/commands/channel/adapters/types.d.ts.map +1 -0
- package/dist/commands/channel/adapters/types.js +2 -0
- package/dist/commands/channel/adapters/types.js.map +1 -0
- package/dist/commands/channel/agent-loader.d.ts +32 -0
- package/dist/commands/channel/agent-loader.d.ts.map +1 -0
- package/dist/commands/channel/agent-loader.js +154 -0
- package/dist/commands/channel/agent-loader.js.map +1 -0
- package/dist/commands/channel/context-loader.d.ts +26 -0
- package/dist/commands/channel/context-loader.d.ts.map +1 -0
- package/dist/commands/channel/context-loader.js +290 -0
- package/dist/commands/channel/context-loader.js.map +1 -0
- package/dist/commands/channel/context.d.ts +16 -0
- package/dist/commands/channel/context.d.ts.map +1 -0
- package/dist/commands/channel/context.js +83 -0
- package/dist/commands/channel/context.js.map +1 -0
- package/dist/commands/channel/create.d.ts +27 -0
- package/dist/commands/channel/create.d.ts.map +1 -0
- package/dist/commands/channel/create.js +39 -0
- package/dist/commands/channel/create.js.map +1 -0
- package/dist/commands/channel/dev-parse-trace.d.ts +14 -0
- package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -0
- package/dist/commands/channel/dev-parse-trace.js +70 -0
- package/dist/commands/channel/dev-parse-trace.js.map +1 -0
- package/dist/commands/channel/index.d.ts +3 -0
- package/dist/commands/channel/index.d.ts.map +1 -0
- package/dist/commands/channel/index.js +496 -0
- package/dist/commands/channel/index.js.map +1 -0
- package/dist/commands/channel/kill.d.ts +7 -0
- package/dist/commands/channel/kill.d.ts.map +1 -0
- package/dist/commands/channel/kill.js +121 -0
- package/dist/commands/channel/kill.js.map +1 -0
- package/dist/commands/channel/list.d.ts +17 -0
- package/dist/commands/channel/list.d.ts.map +1 -0
- package/dist/commands/channel/list.js +233 -0
- package/dist/commands/channel/list.js.map +1 -0
- package/dist/commands/channel/messages.d.ts +16 -0
- package/dist/commands/channel/messages.d.ts.map +1 -0
- package/dist/commands/channel/messages.js +237 -0
- package/dist/commands/channel/messages.js.map +1 -0
- package/dist/commands/channel/rm.d.ts +27 -0
- package/dist/commands/channel/rm.d.ts.map +1 -0
- package/dist/commands/channel/rm.js +216 -0
- package/dist/commands/channel/rm.js.map +1 -0
- package/dist/commands/channel/run.d.ts +31 -0
- package/dist/commands/channel/run.d.ts.map +1 -0
- package/dist/commands/channel/run.js +137 -0
- package/dist/commands/channel/run.js.map +1 -0
- package/dist/commands/channel/send.d.ts +12 -0
- package/dist/commands/channel/send.d.ts.map +1 -0
- package/dist/commands/channel/send.js +24 -0
- package/dist/commands/channel/send.js.map +1 -0
- package/dist/commands/channel/spawn.d.ts +25 -0
- package/dist/commands/channel/spawn.d.ts.map +1 -0
- package/dist/commands/channel/spawn.js +192 -0
- package/dist/commands/channel/spawn.js.map +1 -0
- package/dist/commands/channel/store/events.d.ts +39 -0
- package/dist/commands/channel/store/events.d.ts.map +1 -0
- package/dist/commands/channel/store/events.js +87 -0
- package/dist/commands/channel/store/events.js.map +1 -0
- package/dist/commands/channel/store/filter.d.ts +3 -0
- package/dist/commands/channel/store/filter.d.ts.map +1 -0
- package/dist/commands/channel/store/filter.js +2 -0
- package/dist/commands/channel/store/filter.js.map +1 -0
- package/dist/commands/channel/store/lock.d.ts +23 -0
- package/dist/commands/channel/store/lock.d.ts.map +1 -0
- package/dist/commands/channel/store/lock.js +99 -0
- package/dist/commands/channel/store/lock.js.map +1 -0
- package/dist/commands/channel/store/paths.d.ts +63 -0
- package/dist/commands/channel/store/paths.d.ts.map +1 -0
- package/dist/commands/channel/store/paths.js +246 -0
- package/dist/commands/channel/store/paths.js.map +1 -0
- package/dist/commands/channel/store/schema.d.ts +27 -0
- package/dist/commands/channel/store/schema.d.ts.map +1 -0
- package/dist/commands/channel/store/schema.js +34 -0
- package/dist/commands/channel/store/schema.js.map +1 -0
- package/dist/commands/channel/store/thread-state.d.ts +5 -0
- package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
- package/dist/commands/channel/store/thread-state.js +16 -0
- package/dist/commands/channel/store/thread-state.js.map +1 -0
- package/dist/commands/channel/store/watch.d.ts +19 -0
- package/dist/commands/channel/store/watch.d.ts.map +1 -0
- package/dist/commands/channel/store/watch.js +130 -0
- package/dist/commands/channel/store/watch.js.map +1 -0
- package/dist/commands/channel/supervisor/inbox.d.ts +25 -0
- package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/inbox.js +99 -0
- package/dist/commands/channel/supervisor/inbox.js.map +1 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts +66 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/shutdown.js +143 -0
- package/dist/commands/channel/supervisor/shutdown.js.map +1 -0
- package/dist/commands/channel/supervisor/stdout.d.ts +49 -0
- package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -0
- package/dist/commands/channel/supervisor/stdout.js +107 -0
- package/dist/commands/channel/supervisor/stdout.js.map +1 -0
- package/dist/commands/channel/supervisor.d.ts +47 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -0
- package/dist/commands/channel/supervisor.js +283 -0
- package/dist/commands/channel/supervisor.js.map +1 -0
- package/dist/commands/channel/text-body.d.ts +13 -0
- package/dist/commands/channel/text-body.d.ts.map +1 -0
- package/dist/commands/channel/text-body.js +47 -0
- package/dist/commands/channel/text-body.js.map +1 -0
- package/dist/commands/channel/threads.d.ts +39 -0
- package/dist/commands/channel/threads.d.ts.map +1 -0
- package/dist/commands/channel/threads.js +106 -0
- package/dist/commands/channel/threads.js.map +1 -0
- package/dist/commands/channel/title.d.ts +12 -0
- package/dist/commands/channel/title.d.ts.map +1 -0
- package/dist/commands/channel/title.js +24 -0
- package/dist/commands/channel/title.js.map +1 -0
- package/dist/commands/channel/wait.d.ts +18 -0
- package/dist/commands/channel/wait.d.ts.map +1 -0
- package/dist/commands/channel/wait.js +76 -0
- package/dist/commands/channel/wait.js.map +1 -0
- package/dist/commands/init.d.ts +57 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +1466 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mem.d.ts +234 -0
- package/dist/commands/mem.d.ts.map +1 -0
- package/dist/commands/mem.js +1869 -0
- package/dist/commands/mem.js.map +1 -0
- package/dist/commands/uninstall.d.ts +27 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +339 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +72 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +1926 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/upgrade.d.ts +28 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +84 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/configurators/antigravity.d.ts +7 -0
- package/dist/configurators/antigravity.d.ts.map +1 -0
- package/dist/configurators/antigravity.js +19 -0
- package/dist/configurators/antigravity.js.map +1 -0
- package/dist/configurators/claude.d.ts +9 -0
- package/dist/configurators/claude.d.ts.map +1 -0
- package/dist/configurators/claude.js +72 -0
- package/dist/configurators/claude.js.map +1 -0
- package/dist/configurators/codebuddy.d.ts +10 -0
- package/dist/configurators/codebuddy.d.ts.map +1 -0
- package/dist/configurators/codebuddy.js +30 -0
- package/dist/configurators/codebuddy.js.map +1 -0
- package/dist/configurators/codex.d.ts +8 -0
- package/dist/configurators/codex.d.ts.map +1 -0
- package/dist/configurators/codex.js +87 -0
- package/dist/configurators/codex.js.map +1 -0
- package/dist/configurators/copilot.d.ts +10 -0
- package/dist/configurators/copilot.d.ts.map +1 -0
- package/dist/configurators/copilot.js +51 -0
- package/dist/configurators/copilot.js.map +1 -0
- package/dist/configurators/cursor.d.ts +10 -0
- package/dist/configurators/cursor.d.ts.map +1 -0
- package/dist/configurators/cursor.js +29 -0
- package/dist/configurators/cursor.js.map +1 -0
- package/dist/configurators/droid.d.ts +10 -0
- package/dist/configurators/droid.d.ts.map +1 -0
- package/dist/configurators/droid.js +30 -0
- package/dist/configurators/droid.js.map +1 -0
- package/dist/configurators/gemini.d.ts +16 -0
- package/dist/configurators/gemini.d.ts.map +1 -0
- package/dist/configurators/gemini.js +38 -0
- package/dist/configurators/gemini.js.map +1 -0
- package/dist/configurators/index.d.ts +65 -0
- package/dist/configurators/index.d.ts.map +1 -0
- package/dist/configurators/index.js +367 -0
- package/dist/configurators/index.js.map +1 -0
- package/dist/configurators/kilo.d.ts +7 -0
- package/dist/configurators/kilo.d.ts.map +1 -0
- package/dist/configurators/kilo.js +19 -0
- package/dist/configurators/kilo.js.map +1 -0
- package/dist/configurators/kiro.d.ts +8 -0
- package/dist/configurators/kiro.d.ts.map +1 -0
- package/dist/configurators/kiro.js +24 -0
- package/dist/configurators/kiro.js.map +1 -0
- package/dist/configurators/opencode.d.ts +14 -0
- package/dist/configurators/opencode.d.ts.map +1 -0
- package/dist/configurators/opencode.js +96 -0
- package/dist/configurators/opencode.js.map +1 -0
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +45 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts +11 -0
- package/dist/configurators/qoder.d.ts.map +1 -0
- package/dist/configurators/qoder.js +31 -0
- package/dist/configurators/qoder.js.map +1 -0
- package/dist/configurators/shared.d.ts +178 -0
- package/dist/configurators/shared.d.ts.map +1 -0
- package/dist/configurators/shared.js +538 -0
- package/dist/configurators/shared.js.map +1 -0
- package/dist/configurators/windsurf.d.ts +7 -0
- package/dist/configurators/windsurf.d.ts.map +1 -0
- package/dist/configurators/windsurf.js +19 -0
- package/dist/configurators/windsurf.js.map +1 -0
- package/dist/configurators/workflow.d.ts +29 -0
- package/dist/configurators/workflow.d.ts.map +1 -0
- package/dist/configurators/workflow.js +163 -0
- package/dist/configurators/workflow.js.map +1 -0
- package/dist/constants/paths.d.ts +70 -0
- package/dist/constants/paths.d.ts.map +1 -0
- package/dist/constants/paths.js +79 -0
- package/dist/constants/paths.js.map +1 -0
- package/dist/constants/version.d.ts +9 -0
- package/dist/constants/version.d.ts.map +1 -0
- package/dist/constants/version.js +15 -0
- package/dist/constants/version.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.d.ts +62 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +187 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/manifests/0.1.9.json +30 -0
- package/dist/migrations/manifests/0.2.0.json +49 -0
- package/dist/migrations/manifests/0.2.12.json +9 -0
- package/dist/migrations/manifests/0.2.13.json +9 -0
- package/dist/migrations/manifests/0.2.14.json +175 -0
- package/dist/migrations/manifests/0.2.15.json +33 -0
- package/dist/migrations/manifests/0.3.0-beta.0.json +297 -0
- package/dist/migrations/manifests/0.3.0-beta.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.15.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.7.json +11 -0
- package/dist/migrations/manifests/0.3.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.3.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.0.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.1.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.2.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.3.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.4.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.5.json +9 -0
- package/dist/migrations/manifests/0.3.0-rc.6.json +9 -0
- package/dist/migrations/manifests/0.3.0.json +11 -0
- package/dist/migrations/manifests/0.3.1.json +9 -0
- package/dist/migrations/manifests/0.3.10.json +9 -0
- package/dist/migrations/manifests/0.3.2.json +9 -0
- package/dist/migrations/manifests/0.3.3.json +9 -0
- package/dist/migrations/manifests/0.3.4.json +21 -0
- package/dist/migrations/manifests/0.3.5.json +9 -0
- package/dist/migrations/manifests/0.3.6.json +9 -0
- package/dist/migrations/manifests/0.3.7.json +9 -0
- package/dist/migrations/manifests/0.3.8.json +9 -0
- package/dist/migrations/manifests/0.3.9.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.1.json +228 -0
- package/dist/migrations/manifests/0.4.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.4.0-beta.8.json +34 -0
- package/dist/migrations/manifests/0.4.0-beta.9.json +9 -0
- package/dist/migrations/manifests/0.4.0-rc.0.json +9 -0
- package/dist/migrations/manifests/0.4.0-rc.1.json +9 -0
- package/dist/migrations/manifests/0.4.0.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.0.json +1646 -0
- package/dist/migrations/manifests/0.5.0-beta.1.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
- package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.5.json +222 -0
- package/dist/migrations/manifests/0.5.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
- package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.1.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.2.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.3.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.4.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.5.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.6.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.7.json +9 -0
- package/dist/migrations/manifests/0.5.0.json +9 -0
- package/dist/migrations/manifests/0.5.1.json +9 -0
- package/dist/migrations/manifests/0.5.10.json +9 -0
- package/dist/migrations/manifests/0.5.11.json +16 -0
- package/dist/migrations/manifests/0.5.12.json +9 -0
- package/dist/migrations/manifests/0.5.13.json +9 -0
- package/dist/migrations/manifests/0.5.14.json +9 -0
- package/dist/migrations/manifests/0.5.15.json +9 -0
- package/dist/migrations/manifests/0.5.2.json +9 -0
- package/dist/migrations/manifests/0.5.3.json +9 -0
- package/dist/migrations/manifests/0.5.4.json +9 -0
- package/dist/migrations/manifests/0.5.5.json +9 -0
- package/dist/migrations/manifests/0.5.6.json +9 -0
- package/dist/migrations/manifests/0.5.7.json +16 -0
- package/dist/migrations/manifests/0.5.8.json +9 -0
- package/dist/migrations/manifests/0.5.9.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.0.json +16 -0
- package/dist/migrations/manifests/0.6.0-beta.1.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.2.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.3.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.6.json +16 -0
- package/dist/migrations/manifests/0.6.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.9.json +9 -0
- package/dist/templates/claude/agents/trellis-check.md +114 -0
- package/dist/templates/claude/agents/trellis-implement.md +113 -0
- package/dist/templates/claude/agents/trellis-research.md +137 -0
- package/dist/templates/claude/index.d.ts +22 -0
- package/dist/templates/claude/index.d.ts.map +1 -0
- package/dist/templates/claude/index.js +46 -0
- package/dist/templates/claude/index.js.map +1 -0
- package/dist/templates/claude/settings.json +73 -0
- package/dist/templates/codebuddy/agents/trellis-check.md +109 -0
- package/dist/templates/codebuddy/agents/trellis-implement.md +110 -0
- package/dist/templates/codebuddy/agents/trellis-research.md +137 -0
- package/dist/templates/codebuddy/index.d.ts +15 -0
- package/dist/templates/codebuddy/index.d.ts.map +1 -0
- package/dist/templates/codebuddy/index.js +15 -0
- package/dist/templates/codebuddy/index.js.map +1 -0
- package/dist/templates/codebuddy/settings.json +59 -0
- package/dist/templates/codex/agents/trellis-check.toml +84 -0
- package/dist/templates/codex/agents/trellis-implement.toml +65 -0
- package/dist/templates/codex/agents/trellis-research.toml +73 -0
- package/dist/templates/codex/config.toml +35 -0
- package/dist/templates/codex/hooks/session-start.py +545 -0
- package/dist/templates/codex/hooks.json +15 -0
- package/dist/templates/codex/index.d.ts +39 -0
- package/dist/templates/codex/index.d.ts.map +1 -0
- package/dist/templates/codex/index.js +85 -0
- package/dist/templates/codex/index.js.map +1 -0
- package/dist/templates/codex/skills/before-dev/SKILL.md +40 -0
- package/dist/templates/codex/skills/brainstorm/SKILL.md +112 -0
- package/dist/templates/codex/skills/break-loop/SKILL.md +130 -0
- package/dist/templates/codex/skills/check/SKILL.md +98 -0
- package/dist/templates/codex/skills/check-cross-layer/SKILL.md +158 -0
- package/dist/templates/codex/skills/create-command/SKILL.md +101 -0
- package/dist/templates/codex/skills/finish-work/SKILL.md +90 -0
- package/dist/templates/codex/skills/improve-ut/SKILL.md +69 -0
- package/dist/templates/codex/skills/integrate-skill/SKILL.md +221 -0
- package/dist/templates/codex/skills/onboard/SKILL.md +363 -0
- package/dist/templates/codex/skills/record-session/SKILL.md +67 -0
- package/dist/templates/codex/skills/start/SKILL.md +64 -0
- package/dist/templates/codex/skills/update-spec/SKILL.md +335 -0
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +84 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +65 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +103 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +80 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
- package/dist/templates/common/commands/continue.md +56 -0
- package/dist/templates/common/commands/finish-work.md +66 -0
- package/dist/templates/common/commands/start.md +59 -0
- package/dist/templates/common/index.d.ts +48 -0
- package/dist/templates/common/index.d.ts.map +1 -0
- package/dist/templates/common/index.js +104 -0
- package/dist/templates/common/index.js.map +1 -0
- package/dist/templates/common/skills/before-dev.md +35 -0
- package/dist/templates/common/skills/brainstorm.md +112 -0
- package/dist/templates/common/skills/break-loop.md +125 -0
- package/dist/templates/common/skills/check.md +93 -0
- package/dist/templates/common/skills/update-spec.md +351 -0
- package/dist/templates/copilot/hooks/session-start.py +547 -0
- package/dist/templates/copilot/hooks.json +19 -0
- package/dist/templates/copilot/index.d.ts +23 -0
- package/dist/templates/copilot/index.d.ts.map +1 -0
- package/dist/templates/copilot/index.js +54 -0
- package/dist/templates/copilot/index.js.map +1 -0
- package/dist/templates/copilot/prompts/before-dev.prompt.md +39 -0
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +111 -0
- package/dist/templates/copilot/prompts/break-loop.prompt.md +129 -0
- package/dist/templates/copilot/prompts/check-cross-layer.prompt.md +157 -0
- package/dist/templates/copilot/prompts/check.prompt.md +97 -0
- package/dist/templates/copilot/prompts/create-command.prompt.md +116 -0
- package/dist/templates/copilot/prompts/finish-work.prompt.md +99 -0
- package/dist/templates/copilot/prompts/integrate-skill.prompt.md +223 -0
- package/dist/templates/copilot/prompts/onboard.prompt.md +362 -0
- package/dist/templates/copilot/prompts/parallel.prompt.md +204 -0
- package/dist/templates/copilot/prompts/record-session.prompt.md +66 -0
- package/dist/templates/copilot/prompts/start.prompt.md +63 -0
- package/dist/templates/copilot/prompts/update-spec.prompt.md +358 -0
- package/dist/templates/cursor/agents/trellis-check.md +108 -0
- package/dist/templates/cursor/agents/trellis-implement.md +109 -0
- package/dist/templates/cursor/agents/trellis-research.md +136 -0
- package/dist/templates/cursor/hooks.json +30 -0
- package/dist/templates/cursor/index.d.ts +13 -0
- package/dist/templates/cursor/index.d.ts.map +1 -0
- package/dist/templates/cursor/index.js +13 -0
- package/dist/templates/cursor/index.js.map +1 -0
- package/dist/templates/droid/droids/trellis-check.md +101 -0
- package/dist/templates/droid/droids/trellis-implement.md +102 -0
- package/dist/templates/droid/droids/trellis-research.md +137 -0
- package/dist/templates/droid/index.d.ts +15 -0
- package/dist/templates/droid/index.d.ts.map +1 -0
- package/dist/templates/droid/index.js +15 -0
- package/dist/templates/droid/index.js.map +1 -0
- package/dist/templates/droid/settings.json +59 -0
- package/dist/templates/extract.d.ts +40 -0
- package/dist/templates/extract.d.ts.map +1 -0
- package/dist/templates/extract.js +106 -0
- package/dist/templates/extract.js.map +1 -0
- package/dist/templates/gemini/agents/trellis-check.md +101 -0
- package/dist/templates/gemini/agents/trellis-implement.md +102 -0
- package/dist/templates/gemini/agents/trellis-research.md +136 -0
- package/dist/templates/gemini/index.d.ts +13 -0
- package/dist/templates/gemini/index.d.ts.map +1 -0
- package/dist/templates/gemini/index.js +13 -0
- package/dist/templates/gemini/index.js.map +1 -0
- package/dist/templates/gemini/settings.json +28 -0
- package/dist/templates/kiro/agents/trellis-check.json +26 -0
- package/dist/templates/kiro/agents/trellis-implement.json +26 -0
- package/dist/templates/kiro/agents/trellis-research.json +30 -0
- package/dist/templates/kiro/index.d.ts +18 -0
- package/dist/templates/kiro/index.d.ts.map +1 -0
- package/dist/templates/kiro/index.js +18 -0
- package/dist/templates/kiro/index.js.map +1 -0
- package/dist/templates/markdown/agents.md +21 -0
- package/dist/templates/markdown/gitignore.txt +15 -0
- package/dist/templates/markdown/index.d.ts +27 -0
- package/dist/templates/markdown/index.d.ts.map +1 -0
- package/dist/templates/markdown/index.js +52 -0
- package/dist/templates/markdown/index.js.map +1 -0
- package/dist/templates/markdown/spec/backend/database-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/backend/error-handling.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/index.md.txt +38 -0
- package/dist/templates/markdown/spec/backend/logging-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/component-guidelines.md.txt +59 -0
- package/dist/templates/markdown/spec/frontend/directory-structure.md.txt +54 -0
- package/dist/templates/markdown/spec/frontend/hook-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/index.md.txt +39 -0
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/state-management.md.txt +51 -0
- package/dist/templates/markdown/spec/frontend/type-safety.md.txt +51 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +223 -0
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +259 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +595 -0
- package/dist/templates/markdown/spec/guides/index.md.txt +97 -0
- package/dist/templates/markdown/workspace-index.md +125 -0
- package/dist/templates/markdown/worktree.yaml.txt +58 -0
- package/dist/templates/opencode/agents/trellis-check.md +116 -0
- package/dist/templates/opencode/agents/trellis-implement.md +118 -0
- package/dist/templates/opencode/agents/trellis-research.md +145 -0
- package/dist/templates/opencode/lib/session-utils.js +521 -0
- package/dist/templates/opencode/lib/trellis-context.js +381 -0
- package/dist/templates/opencode/package.json +5 -0
- package/dist/templates/opencode/plugins/inject-subagent-context.js +513 -0
- package/dist/templates/opencode/plugins/inject-workflow-state.js +159 -0
- package/dist/templates/opencode/plugins/session-start.js +101 -0
- package/dist/templates/pi/agents/trellis-check.md +36 -0
- package/dist/templates/pi/agents/trellis-implement.md +41 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +1174 -0
- package/dist/templates/pi/index.d.ts +5 -0
- package/dist/templates/pi/index.d.ts.map +1 -0
- package/dist/templates/pi/index.js +12 -0
- package/dist/templates/pi/index.js.map +1 -0
- package/dist/templates/pi/settings.json +21 -0
- package/dist/templates/qoder/agents/trellis-check.md +102 -0
- package/dist/templates/qoder/agents/trellis-implement.md +103 -0
- package/dist/templates/qoder/agents/trellis-research.md +137 -0
- package/dist/templates/qoder/index.d.ts +15 -0
- package/dist/templates/qoder/index.d.ts.map +1 -0
- package/dist/templates/qoder/index.js +15 -0
- package/dist/templates/qoder/index.js.map +1 -0
- package/dist/templates/qoder/settings.json +47 -0
- package/dist/templates/shared-hooks/index.d.ts +50 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -0
- package/dist/templates/shared-hooks/index.js +89 -0
- package/dist/templates/shared-hooks/index.js.map +1 -0
- package/dist/templates/shared-hooks/inject-shell-session-context.py +183 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +771 -0
- package/dist/templates/shared-hooks/inject-workflow-state.py +363 -0
- package/dist/templates/shared-hooks/session-start.py +827 -0
- package/dist/templates/template-utils.d.ts +26 -0
- package/dist/templates/template-utils.d.ts.map +1 -0
- package/dist/templates/template-utils.js +60 -0
- package/dist/templates/template-utils.js.map +1 -0
- package/dist/templates/trellis/config.yaml +90 -0
- package/dist/templates/trellis/gitignore.txt +32 -0
- package/dist/templates/trellis/index.d.ts +52 -0
- package/dist/templates/trellis/index.d.ts.map +1 -0
- package/dist/templates/trellis/index.js +97 -0
- package/dist/templates/trellis/index.js.map +1 -0
- package/dist/templates/trellis/scripts/__init__.py +5 -0
- package/dist/templates/trellis/scripts/add_session.py +547 -0
- package/dist/templates/trellis/scripts/common/__init__.py +92 -0
- package/dist/templates/trellis/scripts/common/active_task.py +626 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +811 -0
- package/dist/templates/trellis/scripts/common/config.py +445 -0
- package/dist/templates/trellis/scripts/common/developer.py +190 -0
- package/dist/templates/trellis/scripts/common/git.py +31 -0
- package/dist/templates/trellis/scripts/common/git_context.py +106 -0
- package/dist/templates/trellis/scripts/common/io.py +37 -0
- package/dist/templates/trellis/scripts/common/log.py +45 -0
- package/dist/templates/trellis/scripts/common/packages_context.py +238 -0
- package/dist/templates/trellis/scripts/common/paths.py +447 -0
- package/dist/templates/trellis/scripts/common/safe_commit.py +285 -0
- package/dist/templates/trellis/scripts/common/session_context.py +821 -0
- package/dist/templates/trellis/scripts/common/task_context.py +223 -0
- package/dist/templates/trellis/scripts/common/task_queue.py +188 -0
- package/dist/templates/trellis/scripts/common/task_store.py +698 -0
- package/dist/templates/trellis/scripts/common/task_utils.py +274 -0
- package/dist/templates/trellis/scripts/common/tasks.py +112 -0
- package/dist/templates/trellis/scripts/common/trellis_config.py +131 -0
- package/dist/templates/trellis/scripts/common/types.py +110 -0
- package/dist/templates/trellis/scripts/common/workflow_phase.py +212 -0
- package/dist/templates/trellis/scripts/get_context.py +16 -0
- package/dist/templates/trellis/scripts/get_developer.py +26 -0
- package/dist/templates/trellis/scripts/hooks/linear_sync.py +243 -0
- package/dist/templates/trellis/scripts/init_developer.py +51 -0
- package/dist/templates/trellis/scripts/task.py +500 -0
- package/dist/templates/trellis/tasks/.gitkeep +0 -0
- package/dist/templates/trellis/workflow.md +690 -0
- package/dist/types/ai-tools.d.ts +95 -0
- package/dist/types/ai-tools.d.ts.map +1 -0
- package/dist/types/ai-tools.js +280 -0
- package/dist/types/ai-tools.js.map +1 -0
- package/dist/types/migration.d.ts +125 -0
- package/dist/types/migration.d.ts.map +1 -0
- package/dist/types/migration.js +8 -0
- package/dist/types/migration.js.map +1 -0
- package/dist/utils/compare-versions.d.ts +12 -0
- package/dist/utils/compare-versions.d.ts.map +1 -0
- package/dist/utils/compare-versions.js +86 -0
- package/dist/utils/compare-versions.js.map +1 -0
- package/dist/utils/cwd-guard.d.ts +38 -0
- package/dist/utils/cwd-guard.d.ts.map +1 -0
- package/dist/utils/cwd-guard.js +62 -0
- package/dist/utils/cwd-guard.js.map +1 -0
- package/dist/utils/file-writer.d.ts +36 -0
- package/dist/utils/file-writer.d.ts.map +1 -0
- package/dist/utils/file-writer.js +203 -0
- package/dist/utils/file-writer.js.map +1 -0
- package/dist/utils/manifest-prune.d.ts +61 -0
- package/dist/utils/manifest-prune.d.ts.map +1 -0
- package/dist/utils/manifest-prune.js +136 -0
- package/dist/utils/manifest-prune.js.map +1 -0
- package/dist/utils/posix.d.ts +13 -0
- package/dist/utils/posix.d.ts.map +1 -0
- package/dist/utils/posix.js +15 -0
- package/dist/utils/posix.js.map +1 -0
- package/dist/utils/project-detector.d.ts +46 -0
- package/dist/utils/project-detector.d.ts.map +1 -0
- package/dist/utils/project-detector.js +666 -0
- package/dist/utils/project-detector.js.map +1 -0
- package/dist/utils/proxy.d.ts +25 -0
- package/dist/utils/proxy.d.ts.map +1 -0
- package/dist/utils/proxy.js +60 -0
- package/dist/utils/proxy.js.map +1 -0
- package/dist/utils/task-json.d.ts +13 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +12 -0
- package/dist/utils/task-json.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +150 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -0
- package/dist/utils/template-fetcher.js +907 -0
- package/dist/utils/template-fetcher.js.map +1 -0
- package/dist/utils/template-hash.d.ts +123 -0
- package/dist/utils/template-hash.d.ts.map +1 -0
- package/dist/utils/template-hash.js +334 -0
- package/dist/utils/template-hash.js.map +1 -0
- package/dist/utils/uninstall-scrubbers.d.ts +66 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -0
- package/dist/utils/uninstall-scrubbers.js +342 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote template fetcher for Trellis CLI
|
|
3
|
+
*
|
|
4
|
+
* Fetches spec templates from the official marketplace:
|
|
5
|
+
* https://github.com/mindfold-ai/marketplace
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { downloadTemplate } from "giget";
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// =============================================================================
|
|
14
|
+
export const TEMPLATE_INDEX_URL = "https://raw.githubusercontent.com/mindfold-ai/marketplace/main/index.json";
|
|
15
|
+
const TEMPLATE_REPO = "gh:mindfold-ai/marketplace";
|
|
16
|
+
/** Map template type to installation path */
|
|
17
|
+
const INSTALL_PATHS = {
|
|
18
|
+
spec: ".trellis/spec",
|
|
19
|
+
skill: ".agents/skills",
|
|
20
|
+
command: ".claude/commands",
|
|
21
|
+
full: ".", // Entire project root
|
|
22
|
+
};
|
|
23
|
+
/** Timeout constants for network operations */
|
|
24
|
+
export const TIMEOUTS = {
|
|
25
|
+
/** Timeout for fetching the template index (ms) */
|
|
26
|
+
INDEX_FETCH_MS: 5_000,
|
|
27
|
+
/** Timeout for downloading a template via giget (ms) */
|
|
28
|
+
DOWNLOAD_MS: 30_000,
|
|
29
|
+
};
|
|
30
|
+
export class RegistryBackendError extends Error {
|
|
31
|
+
kind;
|
|
32
|
+
constructor(kind, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "RegistryBackendError";
|
|
35
|
+
this.kind = kind;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Registry Source Parsing
|
|
40
|
+
// =============================================================================
|
|
41
|
+
/** Maps provider prefixes to raw file URL patterns */
|
|
42
|
+
const RAW_URL_PATTERNS = {
|
|
43
|
+
gh: "https://raw.githubusercontent.com/{repo}/{ref}/{subdir}",
|
|
44
|
+
github: "https://raw.githubusercontent.com/{repo}/{ref}/{subdir}",
|
|
45
|
+
gitlab: "https://gitlab.com/{repo}/-/raw/{ref}/{subdir}",
|
|
46
|
+
bitbucket: "https://bitbucket.org/{repo}/raw/{ref}/{subdir}",
|
|
47
|
+
};
|
|
48
|
+
export const SUPPORTED_PROVIDERS = Object.keys(RAW_URL_PATTERNS);
|
|
49
|
+
/**
|
|
50
|
+
* Convert an HTTPS URL to giget-style source format.
|
|
51
|
+
* e.g. "https://github.com/user/repo" → "gh:user/repo"
|
|
52
|
+
* "https://github.com/user/repo/tree/branch/path" → "gh:user/repo/path#branch"
|
|
53
|
+
* Returns the original string if it's not a recognized HTTPS URL.
|
|
54
|
+
*/
|
|
55
|
+
export function normalizeRegistrySource(source) {
|
|
56
|
+
const patterns = [
|
|
57
|
+
{ re: /^https?:\/\/github\.com\//, prefix: "gh:" },
|
|
58
|
+
{ re: /^https?:\/\/gitlab\.com\//, prefix: "gitlab:" },
|
|
59
|
+
{ re: /^https?:\/\/bitbucket\.org\//, prefix: "bitbucket:" },
|
|
60
|
+
];
|
|
61
|
+
for (const { re, prefix } of patterns) {
|
|
62
|
+
if (!re.test(source))
|
|
63
|
+
continue;
|
|
64
|
+
const path = source.replace(re, "");
|
|
65
|
+
// Handle /tree/<branch>/<subdir> format (GitHub browse URLs)
|
|
66
|
+
const treeMatch = path.match(/^([^/]+\/[^/]+)\/tree\/([^/]+)(?:\/(.+?))?(?:\.git)?\/?$/);
|
|
67
|
+
if (treeMatch) {
|
|
68
|
+
const [, repo, ref, subdir] = treeMatch;
|
|
69
|
+
return `${prefix}${repo}${subdir ? `/${subdir}` : ""}#${ref}`;
|
|
70
|
+
}
|
|
71
|
+
// Plain URL: strip trailing .git and /
|
|
72
|
+
const cleaned = path.replace(/\.git\/?$/, "").replace(/\/$/, "");
|
|
73
|
+
return `${prefix}${cleaned}`;
|
|
74
|
+
}
|
|
75
|
+
return source;
|
|
76
|
+
}
|
|
77
|
+
/** Known public domains that have dedicated giget provider prefixes */
|
|
78
|
+
const KNOWN_PUBLIC_DOMAINS = ["github.com", "gitlab.com", "bitbucket.org"];
|
|
79
|
+
/** Maps SSH/HTTPS domains of public providers to their giget prefix */
|
|
80
|
+
const PUBLIC_DOMAIN_TO_PREFIX = {
|
|
81
|
+
"github.com": "gh",
|
|
82
|
+
"gitlab.com": "gitlab",
|
|
83
|
+
"bitbucket.org": "bitbucket",
|
|
84
|
+
};
|
|
85
|
+
function buildRegistryGitUrl(input) {
|
|
86
|
+
if (input.sourceKind === "ssh" && input.sshHost) {
|
|
87
|
+
if (input.sshStyle === "ssh-url") {
|
|
88
|
+
const port = input.sshPort ? `:${input.sshPort}` : "";
|
|
89
|
+
return `ssh://git@${input.sshHost}${port}/${input.repo}.git`;
|
|
90
|
+
}
|
|
91
|
+
return `git@${input.sshHost}:${input.repo}.git`;
|
|
92
|
+
}
|
|
93
|
+
if (input.host) {
|
|
94
|
+
return `https://${input.host}/${input.repo}.git`;
|
|
95
|
+
}
|
|
96
|
+
switch (input.provider) {
|
|
97
|
+
case "gh":
|
|
98
|
+
case "github":
|
|
99
|
+
return `https://github.com/${input.repo}.git`;
|
|
100
|
+
case "gitlab":
|
|
101
|
+
return `https://gitlab.com/${input.repo}.git`;
|
|
102
|
+
case "bitbucket":
|
|
103
|
+
return `https://bitbucket.org/${input.repo}.git`;
|
|
104
|
+
default:
|
|
105
|
+
return input.repo;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse a giget-style registry source into its components.
|
|
110
|
+
*
|
|
111
|
+
* Supported input formats:
|
|
112
|
+
* | Format | Example | Provider | Host? |
|
|
113
|
+
* |-------------------------------------|----------------------------------------------|-----------|-----------|
|
|
114
|
+
* | giget prefix | gh:org/repo, gitlab:org/repo#ref | native | no |
|
|
115
|
+
* | Public HTTPS | https://github.com/org/repo | native | no |
|
|
116
|
+
* | Public SSH | git@github.com:org/repo | native | no |
|
|
117
|
+
* | Self-hosted HTTPS | https://git.corp.com/org/repo | gitlab | yes |
|
|
118
|
+
* | Self-hosted SSH | git@git.corp.com:org/repo | gitlab | yes |
|
|
119
|
+
* | ssh:// protocol (with/without port) | ssh://git@host:2222/org/repo | gitlab | yes |
|
|
120
|
+
* | HTTPS with port | https://host:8443/org/repo | gitlab | yes |
|
|
121
|
+
* | GitLab browse URL | https://host/org/repo/-/tree/branch/path | gitlab | yes |
|
|
122
|
+
*
|
|
123
|
+
* Ref defaults to "main" if not specified.
|
|
124
|
+
* Unknown domains default to GitLab URL patterns (covers self-hosted GitLab CE/EE).
|
|
125
|
+
*
|
|
126
|
+
* @throws Error if provider is unsupported
|
|
127
|
+
*/
|
|
128
|
+
export function parseRegistrySource(source) {
|
|
129
|
+
// --- Self-hosted URL detection (SSH + unknown HTTPS) ---
|
|
130
|
+
let host;
|
|
131
|
+
let normalizedInput;
|
|
132
|
+
let sourceKind = "prefixed";
|
|
133
|
+
let sshHost;
|
|
134
|
+
let sshPort;
|
|
135
|
+
let sshStyle;
|
|
136
|
+
// SSH URL: git@host:org/repo[.git] or ssh://git@host[:port]/org/repo[.git]
|
|
137
|
+
const scpSshMatch = source.match(/^git@([^:]+):(.+?)(?:\.git)?\/?$/);
|
|
138
|
+
const protocolSshMatch = source.match(/^ssh:\/\/git@([^/:]+)(?::(\d+))?\/(.+?)(?:\.git)?\/?$/);
|
|
139
|
+
if (scpSshMatch || protocolSshMatch) {
|
|
140
|
+
sourceKind = "ssh";
|
|
141
|
+
sshStyle = scpSshMatch ? "scp" : "ssh-url";
|
|
142
|
+
const sshDomain = scpSshMatch?.[1] ?? protocolSshMatch?.[1] ?? "";
|
|
143
|
+
const sshPath = scpSshMatch?.[2] ?? protocolSshMatch?.[3] ?? "";
|
|
144
|
+
sshPort = protocolSshMatch?.[2];
|
|
145
|
+
sshHost = sshDomain;
|
|
146
|
+
const publicPrefix = PUBLIC_DOMAIN_TO_PREFIX[sshDomain];
|
|
147
|
+
if (publicPrefix) {
|
|
148
|
+
// Public provider SSH (e.g., git@github.com:org/repo) — use native prefix, no host
|
|
149
|
+
normalizedInput = `${publicPrefix}:${sshPath}`;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Self-hosted SSH — default to gitlab provider with host
|
|
153
|
+
host = sshDomain;
|
|
154
|
+
normalizedInput = `gitlab:${sshPath}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// HTTPS URL to unknown domain (not github.com/gitlab.com/bitbucket.org)
|
|
158
|
+
if (!normalizedInput) {
|
|
159
|
+
const httpsMatch = source.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
160
|
+
if (httpsMatch) {
|
|
161
|
+
sourceKind = "https";
|
|
162
|
+
const domain = httpsMatch[1];
|
|
163
|
+
if (!KNOWN_PUBLIC_DOMAINS.includes(domain)) {
|
|
164
|
+
host = domain;
|
|
165
|
+
const pathPart = httpsMatch[2];
|
|
166
|
+
// Handle GitLab browse URLs: /org/repo/-/tree/branch/path
|
|
167
|
+
const treeMatch = pathPart.match(/^([^/]+\/[^/]+)(?:\/-)?\/tree\/([^/]+)(?:\/(.+?))?$/);
|
|
168
|
+
if (treeMatch) {
|
|
169
|
+
const [, repoPath, ref, subdir] = treeMatch;
|
|
170
|
+
normalizedInput = `gitlab:${repoPath}${subdir ? `/${subdir}` : ""}#${ref}`;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
normalizedInput = `gitlab:${pathPart}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Auto-convert known HTTPS URLs to giget format (existing logic)
|
|
179
|
+
const normalized = normalizedInput ?? normalizeRegistrySource(source);
|
|
180
|
+
// Extract provider prefix
|
|
181
|
+
const colonIndex = normalized.indexOf(":");
|
|
182
|
+
if (colonIndex === -1) {
|
|
183
|
+
throw new Error(`Invalid registry source "${source}". Expected format: gh:user/repo/path`);
|
|
184
|
+
}
|
|
185
|
+
const provider = normalized.slice(0, colonIndex);
|
|
186
|
+
const rest = normalized.slice(colonIndex + 1);
|
|
187
|
+
// Check supported provider
|
|
188
|
+
const pattern = RAW_URL_PATTERNS[provider];
|
|
189
|
+
if (!pattern) {
|
|
190
|
+
const supported = [...new Set(Object.keys(RAW_URL_PATTERNS))].join(", ");
|
|
191
|
+
throw new Error(`Unsupported provider "${provider}". Supported: ${supported}`);
|
|
192
|
+
}
|
|
193
|
+
// Parse rest: user/repo/subdir#ref
|
|
194
|
+
// Match: user/repo (required), /subdir (optional), #ref (optional)
|
|
195
|
+
const refMatch = rest.match(/^([^#]+?)(?:#(.+))?$/);
|
|
196
|
+
if (!refMatch) {
|
|
197
|
+
throw new Error(`Invalid registry source "${normalized}". Expected format: ${provider}:user/repo/path`);
|
|
198
|
+
}
|
|
199
|
+
const pathPart = refMatch[1];
|
|
200
|
+
const ref = refMatch[2] ?? "main";
|
|
201
|
+
// Split into repo (first two segments) and subdir (rest)
|
|
202
|
+
const segments = pathPart.split("/").filter(Boolean);
|
|
203
|
+
if (segments.length < 2) {
|
|
204
|
+
throw new Error(`Invalid registry source "${normalized}". Must include user/repo at minimum.`);
|
|
205
|
+
}
|
|
206
|
+
const repo = `${segments[0]}/${segments[1]}`;
|
|
207
|
+
const subdir = segments.slice(2).join("/");
|
|
208
|
+
// Build raw base URL
|
|
209
|
+
let rawBaseUrl = pattern
|
|
210
|
+
.replace("{repo}", repo)
|
|
211
|
+
.replace("{ref}", ref)
|
|
212
|
+
.replace("{subdir}", subdir);
|
|
213
|
+
// Replace public domain with self-hosted host in rawBaseUrl
|
|
214
|
+
if (host && provider === "gitlab") {
|
|
215
|
+
rawBaseUrl = rawBaseUrl.replace("https://gitlab.com", `https://${host}`);
|
|
216
|
+
}
|
|
217
|
+
// Build giget source (use normalized format)
|
|
218
|
+
const gigetSource = normalized;
|
|
219
|
+
const gitUrl = buildRegistryGitUrl({
|
|
220
|
+
provider,
|
|
221
|
+
host,
|
|
222
|
+
repo,
|
|
223
|
+
sourceKind,
|
|
224
|
+
sshHost,
|
|
225
|
+
sshPort,
|
|
226
|
+
sshStyle,
|
|
227
|
+
});
|
|
228
|
+
const preferGit = sourceKind === "ssh" || host !== undefined;
|
|
229
|
+
return {
|
|
230
|
+
provider,
|
|
231
|
+
repo,
|
|
232
|
+
subdir,
|
|
233
|
+
ref,
|
|
234
|
+
rawBaseUrl,
|
|
235
|
+
gigetSource,
|
|
236
|
+
host,
|
|
237
|
+
gitUrl,
|
|
238
|
+
preferGit,
|
|
239
|
+
sourceKind,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// =============================================================================
|
|
243
|
+
// Helpers
|
|
244
|
+
// =============================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Temporarily set `GIGET_GITLAB_URL` env var for self-hosted GitLab downloads.
|
|
247
|
+
* Restores the previous value (or deletes it) after the callback completes.
|
|
248
|
+
*/
|
|
249
|
+
async function withGigetHost(host, fn) {
|
|
250
|
+
if (!host)
|
|
251
|
+
return fn();
|
|
252
|
+
const prev = process.env.GIGET_GITLAB_URL;
|
|
253
|
+
process.env.GIGET_GITLAB_URL = `https://${host}`;
|
|
254
|
+
try {
|
|
255
|
+
return await fn();
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
if (prev === undefined)
|
|
259
|
+
delete process.env.GIGET_GITLAB_URL;
|
|
260
|
+
else
|
|
261
|
+
process.env.GIGET_GITLAB_URL = prev;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Race a promise against a timeout.
|
|
266
|
+
* giget does not support AbortSignal, so we use Promise.race instead.
|
|
267
|
+
* The timer is cleaned up on success to avoid keeping the process alive.
|
|
268
|
+
*/
|
|
269
|
+
function withTimeout(promise, ms, label) {
|
|
270
|
+
let timer;
|
|
271
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
272
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1000}s`)), ms);
|
|
273
|
+
});
|
|
274
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// =============================================================================
|
|
279
|
+
// Fetch Template Index
|
|
280
|
+
// =============================================================================
|
|
281
|
+
/**
|
|
282
|
+
* Fetch available templates from the remote index
|
|
283
|
+
* Returns empty array on network error or timeout (allows fallback to blank)
|
|
284
|
+
*
|
|
285
|
+
* @param indexUrl - URL to fetch index.json from (defaults to official marketplace)
|
|
286
|
+
*/
|
|
287
|
+
export async function fetchTemplateIndex(indexUrl) {
|
|
288
|
+
try {
|
|
289
|
+
const url = indexUrl ?? TEMPLATE_INDEX_URL;
|
|
290
|
+
const res = await fetch(url, {
|
|
291
|
+
signal: AbortSignal.timeout(TIMEOUTS.INDEX_FETCH_MS),
|
|
292
|
+
});
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
throw new Error(`HTTP ${res.status}`);
|
|
295
|
+
}
|
|
296
|
+
const index = (await res.json());
|
|
297
|
+
return index.templates;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Network error or timeout - return empty array, caller will fallback to blank
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function isRecord(value) {
|
|
305
|
+
return typeof value === "object" && value !== null;
|
|
306
|
+
}
|
|
307
|
+
function parseTemplateIndex(raw, sourceLabel) {
|
|
308
|
+
let parsed;
|
|
309
|
+
try {
|
|
310
|
+
parsed = JSON.parse(raw);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
314
|
+
throw new RegistryBackendError("invalid-json", `${sourceLabel} is not valid JSON: ${detail}`);
|
|
315
|
+
}
|
|
316
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.templates)) {
|
|
317
|
+
throw new RegistryBackendError("invalid-json", `${sourceLabel} must contain a templates array.`);
|
|
318
|
+
}
|
|
319
|
+
const templates = [];
|
|
320
|
+
for (const item of parsed.templates) {
|
|
321
|
+
if (!isRecord(item)) {
|
|
322
|
+
throw new RegistryBackendError("invalid-json", `${sourceLabel} contains an invalid template entry.`);
|
|
323
|
+
}
|
|
324
|
+
const { id, type, name, description, path: templatePath, tags } = item;
|
|
325
|
+
if (typeof id !== "string" ||
|
|
326
|
+
typeof type !== "string" ||
|
|
327
|
+
typeof name !== "string" ||
|
|
328
|
+
typeof templatePath !== "string") {
|
|
329
|
+
throw new RegistryBackendError("invalid-json", `${sourceLabel} template entries must include string id, type, name, and path fields.`);
|
|
330
|
+
}
|
|
331
|
+
templates.push({
|
|
332
|
+
id,
|
|
333
|
+
type,
|
|
334
|
+
name,
|
|
335
|
+
path: templatePath,
|
|
336
|
+
...(typeof description === "string" ? { description } : {}),
|
|
337
|
+
...(Array.isArray(tags) && tags.every((tag) => typeof tag === "string")
|
|
338
|
+
? { tags }
|
|
339
|
+
: {}),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const version = typeof parsed.version === "number" ? parsed.version : 1;
|
|
343
|
+
return { version, templates };
|
|
344
|
+
}
|
|
345
|
+
function emptyProbeResult(backend, isNotFound, error) {
|
|
346
|
+
return { templates: [], isNotFound, backend, ...(error ? { error } : {}) };
|
|
347
|
+
}
|
|
348
|
+
function shouldFallbackToGit(registry, result) {
|
|
349
|
+
if (registry?.provider !== "gitlab")
|
|
350
|
+
return false;
|
|
351
|
+
if (result.backend !== "http" || result.isNotFound)
|
|
352
|
+
return false;
|
|
353
|
+
return result.error?.kind === "auth" || result.error?.kind === "invalid-json";
|
|
354
|
+
}
|
|
355
|
+
async function probeRegistryIndexHttp(indexUrl) {
|
|
356
|
+
try {
|
|
357
|
+
const res = await fetch(indexUrl, {
|
|
358
|
+
signal: AbortSignal.timeout(TIMEOUTS.INDEX_FETCH_MS),
|
|
359
|
+
});
|
|
360
|
+
if (res.status === 404) {
|
|
361
|
+
return emptyProbeResult("http", true);
|
|
362
|
+
}
|
|
363
|
+
if (res.status === 401 || res.status === 403) {
|
|
364
|
+
return emptyProbeResult("http", false, new RegistryBackendError("auth", `Registry index requires authentication (HTTP ${res.status}). Use a registry source accessible by your local Git credentials.`));
|
|
365
|
+
}
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
return emptyProbeResult("http", false, new RegistryBackendError("network", `Could not reach registry index (HTTP ${res.status}). Check your network connection and try again.`));
|
|
368
|
+
}
|
|
369
|
+
const index = parseTemplateIndex(await res.text(), "Registry index.json");
|
|
370
|
+
return { templates: index.templates, isNotFound: false, backend: "http" };
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
if (error instanceof RegistryBackendError) {
|
|
374
|
+
return emptyProbeResult("http", false, error);
|
|
375
|
+
}
|
|
376
|
+
return emptyProbeResult("http", false, new RegistryBackendError("network", `Could not reach registry index: ${error instanceof Error ? error.message : String(error)}`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Probe a registry's index.json, distinguishing "not found" from transient errors.
|
|
381
|
+
* Used by the registry flow to decide marketplace vs direct-download mode.
|
|
382
|
+
*
|
|
383
|
+
* - 404 → { templates: [], isNotFound: true }
|
|
384
|
+
* - Other HTTP error / network timeout → { templates: [], isNotFound: false }
|
|
385
|
+
* - 200 + valid JSON → { templates: [...], isNotFound: false }
|
|
386
|
+
*/
|
|
387
|
+
export async function probeRegistryIndex(indexUrl, registry) {
|
|
388
|
+
if (registry?.preferGit) {
|
|
389
|
+
return probeRegistryIndexGit(registry);
|
|
390
|
+
}
|
|
391
|
+
const httpResult = await probeRegistryIndexHttp(indexUrl);
|
|
392
|
+
if (registry && shouldFallbackToGit(registry, httpResult)) {
|
|
393
|
+
return probeRegistryIndexGit(registry);
|
|
394
|
+
}
|
|
395
|
+
return httpResult;
|
|
396
|
+
}
|
|
397
|
+
async function runGit(args) {
|
|
398
|
+
const { execFile } = await import("node:child_process");
|
|
399
|
+
return new Promise((resolve, reject) => {
|
|
400
|
+
execFile("git", args, {
|
|
401
|
+
encoding: "utf-8",
|
|
402
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
403
|
+
timeout: TIMEOUTS.DOWNLOAD_MS,
|
|
404
|
+
}, (error, stdout, stderr) => {
|
|
405
|
+
if (error) {
|
|
406
|
+
const commandError = error;
|
|
407
|
+
commandError.stdout = stdout;
|
|
408
|
+
commandError.stderr = stderr;
|
|
409
|
+
reject(commandError);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
resolve({ stdout, stderr });
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function getCommandErrorText(error) {
|
|
417
|
+
if (!(error instanceof Error))
|
|
418
|
+
return String(error);
|
|
419
|
+
const commandError = error;
|
|
420
|
+
return [commandError.message, commandError.stderr, commandError.stdout]
|
|
421
|
+
.filter((part) => typeof part === "string" && part.length > 0)
|
|
422
|
+
.join("\n");
|
|
423
|
+
}
|
|
424
|
+
function classifyGitError(error, stage, registry) {
|
|
425
|
+
const text = getCommandErrorText(error);
|
|
426
|
+
const lower = text.toLowerCase();
|
|
427
|
+
if (lower.includes("enoent") ||
|
|
428
|
+
lower.includes("not found: git") ||
|
|
429
|
+
lower.includes("spawn git")) {
|
|
430
|
+
return new RegistryBackendError("git-unavailable", 'Git is required to access this registry, but the "git" command was not found.');
|
|
431
|
+
}
|
|
432
|
+
if (lower.includes("authentication failed") ||
|
|
433
|
+
lower.includes("permission denied") ||
|
|
434
|
+
lower.includes("access denied") ||
|
|
435
|
+
lower.includes("could not read from remote repository") ||
|
|
436
|
+
lower.includes("terminal prompts disabled")) {
|
|
437
|
+
return new RegistryBackendError("auth", `Authentication failed or registry is not accessible via Git. Check your local Git credentials for ${registry.gitUrl}.`);
|
|
438
|
+
}
|
|
439
|
+
if (stage !== "clone" &&
|
|
440
|
+
(lower.includes("couldn't find remote ref") ||
|
|
441
|
+
lower.includes("could not find remote ref") ||
|
|
442
|
+
lower.includes("invalid reference") ||
|
|
443
|
+
lower.includes("reference is not a tree") ||
|
|
444
|
+
lower.includes("pathspec"))) {
|
|
445
|
+
return new RegistryBackendError("ref-not-found", `Registry ref "${registry.ref}" was not found in ${registry.gitUrl}.`);
|
|
446
|
+
}
|
|
447
|
+
if (lower.includes("could not resolve host") ||
|
|
448
|
+
lower.includes("failed to connect") ||
|
|
449
|
+
lower.includes("network is unreachable") ||
|
|
450
|
+
lower.includes("operation timed out") ||
|
|
451
|
+
lower.includes("timed out") ||
|
|
452
|
+
lower.includes("connection refused")) {
|
|
453
|
+
return new RegistryBackendError("network", `Could not reach registry ${registry.gitUrl}. Check your network connection and Git remote access.`);
|
|
454
|
+
}
|
|
455
|
+
if (stage === "clone" &&
|
|
456
|
+
(lower.includes("repository not found") ||
|
|
457
|
+
lower.includes("does not appear to be a git repository") ||
|
|
458
|
+
lower.includes("could not read from remote repository"))) {
|
|
459
|
+
return new RegistryBackendError("not-found", `Registry repository was not found or is not accessible: ${registry.gitUrl}.`);
|
|
460
|
+
}
|
|
461
|
+
return new RegistryBackendError("unknown", `Git registry operation failed: ${text}`);
|
|
462
|
+
}
|
|
463
|
+
async function cloneRegistryRef(registry) {
|
|
464
|
+
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "trellis-registry-"));
|
|
465
|
+
try {
|
|
466
|
+
try {
|
|
467
|
+
await runGit([
|
|
468
|
+
"clone",
|
|
469
|
+
"--filter=blob:none",
|
|
470
|
+
"--no-checkout",
|
|
471
|
+
registry.gitUrl,
|
|
472
|
+
dir,
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
throw classifyGitError(error, "clone", registry);
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
await runGit([
|
|
480
|
+
"-C",
|
|
481
|
+
dir,
|
|
482
|
+
"fetch",
|
|
483
|
+
"--depth",
|
|
484
|
+
"1",
|
|
485
|
+
"origin",
|
|
486
|
+
registry.ref,
|
|
487
|
+
]);
|
|
488
|
+
await runGit(["-C", dir, "checkout", "--detach", "FETCH_HEAD"]);
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
throw classifyGitError(error, "fetch", registry);
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
dir,
|
|
495
|
+
cleanup: async () => {
|
|
496
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function resolveInsideRegistryRoot(root, relativePath, label) {
|
|
506
|
+
const resolvedRoot = path.resolve(root);
|
|
507
|
+
const resolved = path.resolve(resolvedRoot, relativePath.length > 0 ? relativePath : ".");
|
|
508
|
+
const relative = path.relative(resolvedRoot, resolved);
|
|
509
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
510
|
+
throw new RegistryBackendError("path-not-found", `${label} path "${relativePath}" must stay inside the registry repository.`);
|
|
511
|
+
}
|
|
512
|
+
return resolved;
|
|
513
|
+
}
|
|
514
|
+
async function isDirectory(dir) {
|
|
515
|
+
try {
|
|
516
|
+
return (await fs.promises.stat(dir)).isDirectory();
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async function isFile(filePath) {
|
|
523
|
+
try {
|
|
524
|
+
return (await fs.promises.stat(filePath)).isFile();
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function getGitRegistryRoot(checkoutDir, registry) {
|
|
531
|
+
const registryRoot = resolveInsideRegistryRoot(checkoutDir, registry.subdir, "Registry");
|
|
532
|
+
if (!(await isDirectory(registryRoot))) {
|
|
533
|
+
throw new RegistryBackendError("path-not-found", `Registry path "${registry.subdir.length > 0 ? registry.subdir : "."}" was not found in ${registry.gitUrl}#${registry.ref}.`);
|
|
534
|
+
}
|
|
535
|
+
return registryRoot;
|
|
536
|
+
}
|
|
537
|
+
async function probeRegistryIndexGit(registry) {
|
|
538
|
+
try {
|
|
539
|
+
const checkout = await cloneRegistryRef(registry);
|
|
540
|
+
try {
|
|
541
|
+
const registryRoot = await getGitRegistryRoot(checkout.dir, registry);
|
|
542
|
+
const indexPath = path.join(registryRoot, "index.json");
|
|
543
|
+
if (!(await isFile(indexPath))) {
|
|
544
|
+
return emptyProbeResult("git", true);
|
|
545
|
+
}
|
|
546
|
+
const index = parseTemplateIndex(await fs.promises.readFile(indexPath, "utf-8"), "Registry index.json");
|
|
547
|
+
return { templates: index.templates, isNotFound: false, backend: "git" };
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
await checkout.cleanup();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
const registryError = error instanceof RegistryBackendError
|
|
555
|
+
? error
|
|
556
|
+
: new RegistryBackendError("unknown", `Git registry probe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
557
|
+
return emptyProbeResult("git", false, registryError);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Find a template by ID from the index
|
|
562
|
+
*/
|
|
563
|
+
export async function findTemplate(templateId, indexUrl) {
|
|
564
|
+
const templates = await fetchTemplateIndex(indexUrl);
|
|
565
|
+
return templates.find((t) => t.id === templateId) ?? null;
|
|
566
|
+
}
|
|
567
|
+
// =============================================================================
|
|
568
|
+
// Download Template
|
|
569
|
+
// =============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Get the installation path for a template type
|
|
572
|
+
*/
|
|
573
|
+
export function getInstallPath(cwd, templateType) {
|
|
574
|
+
const relativePath = INSTALL_PATHS[templateType] || INSTALL_PATHS.spec;
|
|
575
|
+
return path.join(cwd, relativePath);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Download a template with the specified strategy
|
|
579
|
+
*
|
|
580
|
+
* @param templatePath - Path in the docs repo (e.g., "marketplace/specs/electron-fullstack")
|
|
581
|
+
* OR a full giget source (e.g., "gh:myorg/myrepo/my-spec")
|
|
582
|
+
* @param destDir - Destination directory
|
|
583
|
+
* @param strategy - How to handle existing directory: skip, overwrite, or append
|
|
584
|
+
* @param repoSource - Optional giget repo source override. When set, templatePath is
|
|
585
|
+
* treated as relative to this repo. When not set, uses TEMPLATE_REPO.
|
|
586
|
+
* Pass null to use templatePath as a full giget source directly.
|
|
587
|
+
* @returns true if template was downloaded, false if skipped
|
|
588
|
+
*/
|
|
589
|
+
export async function downloadWithStrategy(templatePath, destDir, strategy, repoSource) {
|
|
590
|
+
// Build the giget download source
|
|
591
|
+
const gigetSource = repoSource === null
|
|
592
|
+
? templatePath // templatePath is already a full giget source
|
|
593
|
+
: `${repoSource ?? TEMPLATE_REPO}/${templatePath}`;
|
|
594
|
+
const exists = fs.existsSync(destDir);
|
|
595
|
+
// skip: Directory exists, don't download
|
|
596
|
+
if (strategy === "skip" && exists) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
// overwrite: Delete existing directory first
|
|
600
|
+
if (strategy === "overwrite" && exists) {
|
|
601
|
+
await fs.promises.rm(destDir, { recursive: true });
|
|
602
|
+
}
|
|
603
|
+
// append: Download to temp dir, then merge missing files
|
|
604
|
+
if (strategy === "append" && exists) {
|
|
605
|
+
const tempDir = path.join(os.tmpdir(), `trellis-template-${Date.now()}`);
|
|
606
|
+
try {
|
|
607
|
+
await withTimeout(downloadTemplate(gigetSource, {
|
|
608
|
+
dir: tempDir,
|
|
609
|
+
preferOffline: true,
|
|
610
|
+
}), TIMEOUTS.DOWNLOAD_MS, "Template download");
|
|
611
|
+
await copyMissing(tempDir, destDir);
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
// Clean up partially written files on timeout.
|
|
615
|
+
// Note: giget does not support AbortSignal, so the background download may
|
|
616
|
+
// still be running. Removing the directory causes it to fail with ENOENT,
|
|
617
|
+
// which settles the orphaned promise harmlessly.
|
|
618
|
+
if (error instanceof Error && error.message.includes("timed out")) {
|
|
619
|
+
try {
|
|
620
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
// Best-effort cleanup
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
finally {
|
|
629
|
+
// Clean up temp directory
|
|
630
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
631
|
+
}
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
// Default: Direct download (for new directory or after overwrite)
|
|
635
|
+
try {
|
|
636
|
+
await withTimeout(downloadTemplate(gigetSource, {
|
|
637
|
+
dir: destDir,
|
|
638
|
+
preferOffline: true,
|
|
639
|
+
}), TIMEOUTS.DOWNLOAD_MS, "Template download");
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
// Clean up partially written files on timeout.
|
|
643
|
+
// Note: giget does not support AbortSignal, so the background download may
|
|
644
|
+
// still be running. Removing the directory causes it to fail with ENOENT,
|
|
645
|
+
// which settles the orphaned promise harmlessly.
|
|
646
|
+
if (error instanceof Error && error.message.includes("timed out")) {
|
|
647
|
+
try {
|
|
648
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// Best-effort cleanup
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Copy only files that don't exist in the destination
|
|
660
|
+
*/
|
|
661
|
+
async function copyMissing(src, dest) {
|
|
662
|
+
// Ensure destination exists
|
|
663
|
+
if (!fs.existsSync(dest)) {
|
|
664
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
665
|
+
}
|
|
666
|
+
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
|
667
|
+
for (const entry of entries) {
|
|
668
|
+
const srcPath = path.join(src, entry.name);
|
|
669
|
+
const destPath = path.join(dest, entry.name);
|
|
670
|
+
if (entry.isDirectory()) {
|
|
671
|
+
// Recursively copy missing files in subdirectory
|
|
672
|
+
await copyMissing(srcPath, destPath);
|
|
673
|
+
}
|
|
674
|
+
else if (!fs.existsSync(destPath)) {
|
|
675
|
+
// Only copy if file doesn't exist
|
|
676
|
+
await fs.promises.copyFile(srcPath, destPath);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Copy all files from src to dest, overwriting existing files.
|
|
682
|
+
*/
|
|
683
|
+
async function copyAll(src, dest) {
|
|
684
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
685
|
+
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
|
686
|
+
for (const entry of entries) {
|
|
687
|
+
const srcPath = path.join(src, entry.name);
|
|
688
|
+
const destPath = path.join(dest, entry.name);
|
|
689
|
+
if (entry.isDirectory()) {
|
|
690
|
+
await copyAll(srcPath, destPath);
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
await fs.promises.copyFile(srcPath, destPath);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async function copyDirectoryWithStrategy(srcDir, destDir, strategy) {
|
|
698
|
+
const exists = fs.existsSync(destDir);
|
|
699
|
+
if (strategy === "skip" && exists) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
if (strategy === "overwrite" && exists) {
|
|
703
|
+
await fs.promises.rm(destDir, { recursive: true, force: true });
|
|
704
|
+
}
|
|
705
|
+
if (strategy === "append" && exists) {
|
|
706
|
+
await copyMissing(srcDir, destDir);
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
await copyAll(srcDir, destDir);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
async function downloadGitRegistryPath(registry, relativePath, destDir, strategy) {
|
|
713
|
+
const checkout = await cloneRegistryRef(registry);
|
|
714
|
+
try {
|
|
715
|
+
const sourceRoot = resolveInsideRegistryRoot(checkout.dir, relativePath, "Template");
|
|
716
|
+
if (!(await isDirectory(sourceRoot))) {
|
|
717
|
+
throw new RegistryBackendError("path-not-found", `Template path "${relativePath.length > 0 ? relativePath : "."}" was not found in ${registry.gitUrl}#${registry.ref}.`);
|
|
718
|
+
}
|
|
719
|
+
return await copyDirectoryWithStrategy(sourceRoot, destDir, strategy);
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
await checkout.cleanup();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async function downloadGitRegistryDirect(registry, destDir, strategy) {
|
|
726
|
+
const checkout = await cloneRegistryRef(registry);
|
|
727
|
+
try {
|
|
728
|
+
const sourceRoot = await getGitRegistryRoot(checkout.dir, registry);
|
|
729
|
+
return await copyDirectoryWithStrategy(sourceRoot, destDir, strategy);
|
|
730
|
+
}
|
|
731
|
+
finally {
|
|
732
|
+
await checkout.cleanup();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function resolveRegistryBackend(registry, backendOverride) {
|
|
736
|
+
return backendOverride ?? (registry.preferGit ? "git" : "http");
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Download a template by ID
|
|
740
|
+
*
|
|
741
|
+
* @param cwd - Current working directory
|
|
742
|
+
* @param templateId - Template ID from the index
|
|
743
|
+
* @param strategy - How to handle existing directory
|
|
744
|
+
* @param template - Optional pre-fetched SpecTemplate to avoid double-fetch
|
|
745
|
+
* @param registry - Optional registry source (parsed). When set, uses the registry's
|
|
746
|
+
* repo as the giget source instead of the default TEMPLATE_REPO.
|
|
747
|
+
* @returns Object with success status and message
|
|
748
|
+
*/
|
|
749
|
+
export async function downloadTemplateById(cwd, templateId, strategy, template, registry, destDirOverride, registryBackend) {
|
|
750
|
+
// Use pre-fetched template or find from index
|
|
751
|
+
let resolved = template;
|
|
752
|
+
let backend = registryBackend;
|
|
753
|
+
if (!resolved) {
|
|
754
|
+
const indexUrl = registry ? `${registry.rawBaseUrl}/index.json` : undefined;
|
|
755
|
+
if (registry && indexUrl) {
|
|
756
|
+
// Use probe to distinguish "template not in index" from "registry unreachable"
|
|
757
|
+
const probeResult = await probeRegistryIndex(indexUrl, registry);
|
|
758
|
+
backend = probeResult.backend;
|
|
759
|
+
if (probeResult.error) {
|
|
760
|
+
return {
|
|
761
|
+
success: false,
|
|
762
|
+
message: probeResult.error.message,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
if (probeResult.templates.length === 0 && !probeResult.isNotFound) {
|
|
766
|
+
return {
|
|
767
|
+
success: false,
|
|
768
|
+
message: "Could not reach registry. Check your network connection and try again.",
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
if (probeResult.isNotFound) {
|
|
772
|
+
return {
|
|
773
|
+
success: false,
|
|
774
|
+
message: "Registry has no index.json. Remove --template to use direct download mode.",
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
resolved = probeResult.templates.find((t) => t.id === templateId);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
resolved = (await findTemplate(templateId, indexUrl)) ?? undefined;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (!resolved) {
|
|
784
|
+
return {
|
|
785
|
+
success: false,
|
|
786
|
+
message: `Template "${templateId}" not found`,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
// Only support spec type in MVP
|
|
790
|
+
if (resolved.type !== "spec") {
|
|
791
|
+
return {
|
|
792
|
+
success: false,
|
|
793
|
+
message: `Template type "${resolved.type}" is not supported yet (only "spec" is supported)`,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
// Get destination path (use override for monorepo per-package downloads)
|
|
797
|
+
const destDir = destDirOverride ?? getInstallPath(cwd, resolved.type);
|
|
798
|
+
// Check if directory exists for skip strategy
|
|
799
|
+
if (strategy === "skip" && fs.existsSync(destDir)) {
|
|
800
|
+
return {
|
|
801
|
+
success: true,
|
|
802
|
+
skipped: true,
|
|
803
|
+
message: `Skipped: ${destDir} already exists`,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
// Download template
|
|
807
|
+
try {
|
|
808
|
+
if (registry) {
|
|
809
|
+
if (resolveRegistryBackend(registry, backend) === "git") {
|
|
810
|
+
await downloadGitRegistryPath(registry, resolved.path, destDir, strategy);
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
// Custom registry: build full giget source with ref at the end
|
|
814
|
+
// giget format: provider:user/repo/path#ref
|
|
815
|
+
const fullSource = `${registry.provider}:${registry.repo}/${resolved.path}#${registry.ref}`;
|
|
816
|
+
await withGigetHost(registry.host, () => downloadWithStrategy(fullSource, destDir, strategy, null));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
await downloadWithStrategy(resolved.path, destDir, strategy);
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
success: true,
|
|
824
|
+
message: `Downloaded template "${templateId}" to ${destDir}`,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
829
|
+
if (error instanceof RegistryBackendError) {
|
|
830
|
+
return {
|
|
831
|
+
success: false,
|
|
832
|
+
message: error.message,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
// Classify errors for user-friendly messages
|
|
836
|
+
if (errorMessage.includes("timed out")) {
|
|
837
|
+
return {
|
|
838
|
+
success: false,
|
|
839
|
+
message: "Download timed out. Check your network connection and try again.",
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
if (errorMessage.includes("Failed to download") ||
|
|
843
|
+
errorMessage.includes("Failed to fetch")) {
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
message: "Could not reach template server. Check your network connection.",
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
success: false,
|
|
851
|
+
message: `Download failed: ${errorMessage}`,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Download a registry source directly to the spec directory (no index.json).
|
|
857
|
+
* Used when the registry source points to a spec directory, not a marketplace.
|
|
858
|
+
*/
|
|
859
|
+
export async function downloadRegistryDirect(cwd, registry, strategy, destDirOverride, registryBackend) {
|
|
860
|
+
const destDir = destDirOverride ?? getInstallPath(cwd, "spec");
|
|
861
|
+
if (strategy === "skip" && fs.existsSync(destDir)) {
|
|
862
|
+
return {
|
|
863
|
+
success: true,
|
|
864
|
+
skipped: true,
|
|
865
|
+
message: `Skipped: ${destDir} already exists`,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
if (resolveRegistryBackend(registry, registryBackend) === "git") {
|
|
870
|
+
await downloadGitRegistryDirect(registry, destDir, strategy);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
await withGigetHost(registry.host, () => downloadWithStrategy(registry.gigetSource, destDir, strategy, null));
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
success: true,
|
|
877
|
+
message: `Downloaded spec from ${registry.gigetSource} to ${destDir}`,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
882
|
+
if (error instanceof RegistryBackendError) {
|
|
883
|
+
return {
|
|
884
|
+
success: false,
|
|
885
|
+
message: error.message,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
if (errorMessage.includes("timed out")) {
|
|
889
|
+
return {
|
|
890
|
+
success: false,
|
|
891
|
+
message: "Download timed out. Check your network connection and try again.",
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
if (errorMessage.includes("Failed to download") ||
|
|
895
|
+
errorMessage.includes("Failed to fetch")) {
|
|
896
|
+
return {
|
|
897
|
+
success: false,
|
|
898
|
+
message: "Could not reach template server. Check your network connection.",
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
success: false,
|
|
903
|
+
message: `Download failed: ${errorMessage}`,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
//# sourceMappingURL=template-fetcher.js.map
|