@mindfoldhq/trellis 0.5.0-beta.8 → 0.5.0-rc.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/README.md +60 -95
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +474 -210
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +295 -54
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/antigravity.d.ts.map +1 -1
- package/dist/configurators/antigravity.js +2 -8
- package/dist/configurators/antigravity.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +4 -10
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codebuddy.d.ts.map +1 -1
- package/dist/configurators/codebuddy.js +3 -3
- package/dist/configurators/codebuddy.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +5 -13
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +5 -19
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/cursor.d.ts.map +1 -1
- package/dist/configurators/cursor.js +3 -3
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/droid.d.ts.map +1 -1
- package/dist/configurators/droid.js +3 -3
- package/dist/configurators/droid.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js +3 -5
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +44 -55
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kilo.d.ts.map +1 -1
- package/dist/configurators/kilo.js +2 -8
- package/dist/configurators/kilo.js.map +1 -1
- package/dist/configurators/kiro.d.ts.map +1 -1
- package/dist/configurators/kiro.js +3 -3
- package/dist/configurators/kiro.js.map +1 -1
- package/dist/configurators/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +7 -4
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +44 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts +7 -6
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +18 -12
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +30 -6
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +65 -15
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/windsurf.d.ts.map +1 -1
- package/dist/configurators/windsurf.js +2 -8
- package/dist/configurators/windsurf.js.map +1 -1
- package/dist/constants/paths.d.ts +2 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +2 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.0.json +2 -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.5.json +2 -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/templates/claude/agents/trellis-research.md +1 -1
- package/dist/templates/claude/settings.json +0 -4
- package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
- package/dist/templates/codex/agents/trellis-research.toml +3 -2
- package/dist/templates/codex/hooks/session-start.py +126 -26
- package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
- package/dist/templates/codex/skills/start/SKILL.md +12 -9
- 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 +81 -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 +64 -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 +101 -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 +79 -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 +9 -5
- package/dist/templates/common/commands/finish-work.md +34 -10
- package/dist/templates/common/index.d.ts +22 -2
- package/dist/templates/common/index.d.ts.map +1 -1
- package/dist/templates/common/index.js +53 -4
- package/dist/templates/common/index.js.map +1 -1
- package/dist/templates/common/skills/brainstorm.md +50 -4
- package/dist/templates/copilot/hooks/session-start.py +127 -30
- package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
- package/dist/templates/copilot/prompts/start.prompt.md +12 -9
- package/dist/templates/cursor/agents/trellis-check.md +1 -1
- package/dist/templates/cursor/agents/trellis-implement.md +1 -1
- package/dist/templates/cursor/agents/trellis-research.md +2 -2
- package/dist/templates/cursor/hooks.json +7 -1
- package/dist/templates/droid/droids/trellis-research.md +1 -1
- package/dist/templates/extract.d.ts +6 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +14 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/agents/trellis-research.md +1 -1
- package/dist/templates/kiro/agents/trellis-research.json +1 -1
- package/dist/templates/markdown/agents.md +19 -12
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
- package/dist/templates/opencode/agents/trellis-check.md +1 -1
- package/dist/templates/opencode/agents/trellis-implement.md +7 -4
- package/dist/templates/opencode/agents/trellis-research.md +2 -2
- package/dist/templates/opencode/lib/trellis-context.js +100 -13
- package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
- package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -44
- package/dist/templates/opencode/plugins/session-start.js +76 -31
- package/dist/templates/pi/agents/trellis-check.md +28 -0
- package/dist/templates/pi/agents/trellis-implement.md +33 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -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 +12 -0
- package/dist/templates/qoder/agents/trellis-research.md +1 -1
- package/dist/templates/shared-hooks/index.d.ts +31 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +59 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
- package/dist/templates/shared-hooks/inject-workflow-state.py +85 -92
- package/dist/templates/shared-hooks/session-start.py +232 -36
- package/dist/templates/trellis/config.yaml +6 -0
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +8 -0
- package/dist/templates/trellis/scripts/common/active_task.py +593 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
- package/dist/templates/trellis/scripts/common/paths.py +61 -58
- package/dist/templates/trellis/scripts/common/session_context.py +12 -0
- package/dist/templates/trellis/scripts/common/task_context.py +27 -194
- package/dist/templates/trellis/scripts/common/task_store.py +102 -26
- package/dist/templates/trellis/scripts/common/tasks.py +4 -1
- package/dist/templates/trellis/scripts/common/types.py +0 -2
- package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
- package/dist/templates/trellis/scripts/task.py +99 -34
- package/dist/templates/trellis/workflow.md +332 -64
- package/dist/types/ai-tools.d.ts +12 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +29 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +7 -2
- package/dist/utils/file-writer.js.map +1 -1
- 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 +2 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +120 -11
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/task-json.d.ts +46 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +49 -0
- package/dist/utils/task-json.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +22 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +405 -27
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts +22 -3
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +99 -19
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +7 -7
- package/dist/templates/markdown/spec/backend/directory-structure.md +0 -292
- package/dist/templates/markdown/spec/backend/index.md +0 -40
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
- package/dist/templates/shared-hooks/statusline.py +0 -218
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
|
@@ -21,6 +21,19 @@ export interface SpecTemplate {
|
|
|
21
21
|
tags?: string[];
|
|
22
22
|
}
|
|
23
23
|
export type TemplateStrategy = "skip" | "overwrite" | "append";
|
|
24
|
+
export type RegistryBackend = "http" | "git";
|
|
25
|
+
export type RegistrySourceKind = "prefixed" | "https" | "ssh";
|
|
26
|
+
export type RegistryErrorKind = "auth" | "git-unavailable" | "invalid-json" | "network" | "not-found" | "path-not-found" | "ref-not-found" | "unknown";
|
|
27
|
+
export declare class RegistryBackendError extends Error {
|
|
28
|
+
readonly kind: RegistryErrorKind;
|
|
29
|
+
constructor(kind: RegistryErrorKind, message: string);
|
|
30
|
+
}
|
|
31
|
+
export interface RegistryProbeResult {
|
|
32
|
+
templates: SpecTemplate[];
|
|
33
|
+
isNotFound: boolean;
|
|
34
|
+
backend: RegistryBackend;
|
|
35
|
+
error?: RegistryBackendError;
|
|
36
|
+
}
|
|
24
37
|
export interface RegistrySource {
|
|
25
38
|
/** Original provider prefix (e.g., "gh", "gitlab", "bitbucket") */
|
|
26
39
|
provider: string;
|
|
@@ -36,6 +49,12 @@ export interface RegistrySource {
|
|
|
36
49
|
gigetSource: string;
|
|
37
50
|
/** Custom host for self-hosted instances (e.g., "git.company.com"). Undefined for public providers. */
|
|
38
51
|
host?: string;
|
|
52
|
+
/** Git remote URL used by git-backed registry access. */
|
|
53
|
+
gitUrl: string;
|
|
54
|
+
/** Whether registry access should prefer Git over anonymous raw HTTP. */
|
|
55
|
+
preferGit: boolean;
|
|
56
|
+
/** Input family used to derive credential behavior and clone URL shape. */
|
|
57
|
+
sourceKind: RegistrySourceKind;
|
|
39
58
|
}
|
|
40
59
|
export declare const SUPPORTED_PROVIDERS: string[];
|
|
41
60
|
/**
|
|
@@ -81,10 +100,7 @@ export declare function fetchTemplateIndex(indexUrl?: string): Promise<SpecTempl
|
|
|
81
100
|
* - Other HTTP error / network timeout → { templates: [], isNotFound: false }
|
|
82
101
|
* - 200 + valid JSON → { templates: [...], isNotFound: false }
|
|
83
102
|
*/
|
|
84
|
-
export declare function probeRegistryIndex(indexUrl: string): Promise<
|
|
85
|
-
templates: SpecTemplate[];
|
|
86
|
-
isNotFound: boolean;
|
|
87
|
-
}>;
|
|
103
|
+
export declare function probeRegistryIndex(indexUrl: string, registry?: RegistrySource): Promise<RegistryProbeResult>;
|
|
88
104
|
/**
|
|
89
105
|
* Find a template by ID from the index
|
|
90
106
|
*/
|
|
@@ -117,7 +133,7 @@ export declare function downloadWithStrategy(templatePath: string, destDir: stri
|
|
|
117
133
|
* repo as the giget source instead of the default TEMPLATE_REPO.
|
|
118
134
|
* @returns Object with success status and message
|
|
119
135
|
*/
|
|
120
|
-
export declare function downloadTemplateById(cwd: string, templateId: string, strategy: TemplateStrategy, template?: SpecTemplate, registry?: RegistrySource, destDirOverride?: string): Promise<{
|
|
136
|
+
export declare function downloadTemplateById(cwd: string, templateId: string, strategy: TemplateStrategy, template?: SpecTemplate, registry?: RegistrySource, destDirOverride?: string, registryBackend?: RegistryBackend): Promise<{
|
|
121
137
|
success: boolean;
|
|
122
138
|
message: string;
|
|
123
139
|
skipped?: boolean;
|
|
@@ -126,7 +142,7 @@ export declare function downloadTemplateById(cwd: string, templateId: string, st
|
|
|
126
142
|
* Download a registry source directly to the spec directory (no index.json).
|
|
127
143
|
* Used when the registry source points to a spec directory, not a marketplace.
|
|
128
144
|
*/
|
|
129
|
-
export declare function downloadRegistryDirect(cwd: string, registry: RegistrySource, strategy: TemplateStrategy, destDirOverride?: string): Promise<{
|
|
145
|
+
export declare function downloadRegistryDirect(cwd: string, registry: RegistrySource, strategy: TemplateStrategy, destDirOverride?: string, registryBackend?: RegistryBackend): Promise<{
|
|
130
146
|
success: boolean;
|
|
131
147
|
message: string;
|
|
132
148
|
skipped?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-fetcher.d.ts","sourceRoot":"","sources":["../../src/utils/template-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,eAAO,MAAM,kBAAkB,8EAC8C,CAAC;AAY9E,+CAA+C;AAC/C,eAAO,MAAM,QAAQ;IACnB,mDAAmD;;IAEnD,wDAAwD;;CAEhD,CAAC;AAMX,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAOD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"template-fetcher.d.ts","sourceRoot":"","sources":["../../src/utils/template-fetcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,14 @@ export const TIMEOUTS = {
|
|
|
27
27
|
/** Timeout for downloading a template via giget (ms) */
|
|
28
28
|
DOWNLOAD_MS: 30_000,
|
|
29
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
|
+
}
|
|
30
38
|
// =============================================================================
|
|
31
39
|
// Registry Source Parsing
|
|
32
40
|
// =============================================================================
|
|
@@ -74,6 +82,29 @@ const PUBLIC_DOMAIN_TO_PREFIX = {
|
|
|
74
82
|
"gitlab.com": "gitlab",
|
|
75
83
|
"bitbucket.org": "bitbucket",
|
|
76
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
|
+
}
|
|
77
108
|
/**
|
|
78
109
|
* Parse a giget-style registry source into its components.
|
|
79
110
|
*
|
|
@@ -98,12 +129,20 @@ export function parseRegistrySource(source) {
|
|
|
98
129
|
// --- Self-hosted URL detection (SSH + unknown HTTPS) ---
|
|
99
130
|
let host;
|
|
100
131
|
let normalizedInput;
|
|
132
|
+
let sourceKind = "prefixed";
|
|
133
|
+
let sshHost;
|
|
134
|
+
let sshPort;
|
|
135
|
+
let sshStyle;
|
|
101
136
|
// SSH URL: git@host:org/repo[.git] or ssh://git@host[:port]/org/repo[.git]
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
107
146
|
const publicPrefix = PUBLIC_DOMAIN_TO_PREFIX[sshDomain];
|
|
108
147
|
if (publicPrefix) {
|
|
109
148
|
// Public provider SSH (e.g., git@github.com:org/repo) — use native prefix, no host
|
|
@@ -116,9 +155,10 @@ export function parseRegistrySource(source) {
|
|
|
116
155
|
}
|
|
117
156
|
}
|
|
118
157
|
// HTTPS URL to unknown domain (not github.com/gitlab.com/bitbucket.org)
|
|
119
|
-
if (!
|
|
158
|
+
if (!normalizedInput) {
|
|
120
159
|
const httpsMatch = source.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
121
160
|
if (httpsMatch) {
|
|
161
|
+
sourceKind = "https";
|
|
122
162
|
const domain = httpsMatch[1];
|
|
123
163
|
if (!KNOWN_PUBLIC_DOMAINS.includes(domain)) {
|
|
124
164
|
host = domain;
|
|
@@ -176,7 +216,28 @@ export function parseRegistrySource(source) {
|
|
|
176
216
|
}
|
|
177
217
|
// Build giget source (use normalized format)
|
|
178
218
|
const gigetSource = normalized;
|
|
179
|
-
|
|
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
|
+
};
|
|
180
241
|
}
|
|
181
242
|
// =============================================================================
|
|
182
243
|
// Helpers
|
|
@@ -240,6 +301,81 @@ export async function fetchTemplateIndex(indexUrl) {
|
|
|
240
301
|
return [];
|
|
241
302
|
}
|
|
242
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
|
+
}
|
|
243
379
|
/**
|
|
244
380
|
* Probe a registry's index.json, distinguishing "not found" from transient errors.
|
|
245
381
|
* Used by the registry flow to decide marketplace vs direct-download mode.
|
|
@@ -248,23 +384,177 @@ export async function fetchTemplateIndex(indexUrl) {
|
|
|
248
384
|
* - Other HTTP error / network timeout → { templates: [], isNotFound: false }
|
|
249
385
|
* - 200 + valid JSON → { templates: [...], isNotFound: false }
|
|
250
386
|
*/
|
|
251
|
-
export async function probeRegistryIndex(indexUrl) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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 });
|
|
255
413
|
});
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
]);
|
|
258
474
|
}
|
|
259
|
-
|
|
260
|
-
|
|
475
|
+
catch (error) {
|
|
476
|
+
throw classifyGitError(error, "clone", registry);
|
|
261
477
|
}
|
|
262
|
-
|
|
263
|
-
|
|
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();
|
|
264
517
|
}
|
|
265
518
|
catch {
|
|
266
|
-
|
|
267
|
-
|
|
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);
|
|
268
558
|
}
|
|
269
559
|
}
|
|
270
560
|
/**
|
|
@@ -387,6 +677,64 @@ async function copyMissing(src, dest) {
|
|
|
387
677
|
}
|
|
388
678
|
}
|
|
389
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
|
+
}
|
|
390
738
|
/**
|
|
391
739
|
* Download a template by ID
|
|
392
740
|
*
|
|
@@ -398,14 +746,22 @@ async function copyMissing(src, dest) {
|
|
|
398
746
|
* repo as the giget source instead of the default TEMPLATE_REPO.
|
|
399
747
|
* @returns Object with success status and message
|
|
400
748
|
*/
|
|
401
|
-
export async function downloadTemplateById(cwd, templateId, strategy, template, registry, destDirOverride) {
|
|
749
|
+
export async function downloadTemplateById(cwd, templateId, strategy, template, registry, destDirOverride, registryBackend) {
|
|
402
750
|
// Use pre-fetched template or find from index
|
|
403
751
|
let resolved = template;
|
|
752
|
+
let backend = registryBackend;
|
|
404
753
|
if (!resolved) {
|
|
405
754
|
const indexUrl = registry ? `${registry.rawBaseUrl}/index.json` : undefined;
|
|
406
755
|
if (registry && indexUrl) {
|
|
407
756
|
// Use probe to distinguish "template not in index" from "registry unreachable"
|
|
408
|
-
const probeResult = await probeRegistryIndex(indexUrl);
|
|
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
|
+
}
|
|
409
765
|
if (probeResult.templates.length === 0 && !probeResult.isNotFound) {
|
|
410
766
|
return {
|
|
411
767
|
success: false,
|
|
@@ -450,10 +806,15 @@ export async function downloadTemplateById(cwd, templateId, strategy, template,
|
|
|
450
806
|
// Download template
|
|
451
807
|
try {
|
|
452
808
|
if (registry) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
}
|
|
457
818
|
}
|
|
458
819
|
else {
|
|
459
820
|
await downloadWithStrategy(resolved.path, destDir, strategy);
|
|
@@ -465,6 +826,12 @@ export async function downloadTemplateById(cwd, templateId, strategy, template,
|
|
|
465
826
|
}
|
|
466
827
|
catch (error) {
|
|
467
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
|
+
}
|
|
468
835
|
// Classify errors for user-friendly messages
|
|
469
836
|
if (errorMessage.includes("timed out")) {
|
|
470
837
|
return {
|
|
@@ -489,7 +856,7 @@ export async function downloadTemplateById(cwd, templateId, strategy, template,
|
|
|
489
856
|
* Download a registry source directly to the spec directory (no index.json).
|
|
490
857
|
* Used when the registry source points to a spec directory, not a marketplace.
|
|
491
858
|
*/
|
|
492
|
-
export async function downloadRegistryDirect(cwd, registry, strategy, destDirOverride) {
|
|
859
|
+
export async function downloadRegistryDirect(cwd, registry, strategy, destDirOverride, registryBackend) {
|
|
493
860
|
const destDir = destDirOverride ?? getInstallPath(cwd, "spec");
|
|
494
861
|
if (strategy === "skip" && fs.existsSync(destDir)) {
|
|
495
862
|
return {
|
|
@@ -499,7 +866,12 @@ export async function downloadRegistryDirect(cwd, registry, strategy, destDirOve
|
|
|
499
866
|
};
|
|
500
867
|
}
|
|
501
868
|
try {
|
|
502
|
-
|
|
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
|
+
}
|
|
503
875
|
return {
|
|
504
876
|
success: true,
|
|
505
877
|
message: `Downloaded spec from ${registry.gigetSource} to ${destDir}`,
|
|
@@ -507,6 +879,12 @@ export async function downloadRegistryDirect(cwd, registry, strategy, destDirOve
|
|
|
507
879
|
}
|
|
508
880
|
catch (error) {
|
|
509
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
|
+
}
|
|
510
888
|
if (errorMessage.includes("timed out")) {
|
|
511
889
|
return {
|
|
512
890
|
success: false,
|