@herdctl/core 5.5.0 → 5.7.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/dist/config/__tests__/merge.test.js +1 -1
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +10 -2
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/distribution/__tests__/agent-discovery.test.d.ts +7 -0
- package/dist/distribution/__tests__/agent-discovery.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/agent-discovery.test.js +443 -0
- package/dist/distribution/__tests__/agent-discovery.test.js.map +1 -0
- package/dist/distribution/__tests__/agent-info.test.d.ts +7 -0
- package/dist/distribution/__tests__/agent-info.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/agent-info.test.js +568 -0
- package/dist/distribution/__tests__/agent-info.test.js.map +1 -0
- package/dist/distribution/__tests__/agent-remover.test.d.ts +7 -0
- package/dist/distribution/__tests__/agent-remover.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/agent-remover.test.js +498 -0
- package/dist/distribution/__tests__/agent-remover.test.js.map +1 -0
- package/dist/distribution/__tests__/agent-repo-metadata.test.d.ts +5 -0
- package/dist/distribution/__tests__/agent-repo-metadata.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/agent-repo-metadata.test.js +500 -0
- package/dist/distribution/__tests__/agent-repo-metadata.test.js.map +1 -0
- package/dist/distribution/__tests__/env-scanner.test.d.ts +5 -0
- package/dist/distribution/__tests__/env-scanner.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/env-scanner.test.js +576 -0
- package/dist/distribution/__tests__/env-scanner.test.js.map +1 -0
- package/dist/distribution/__tests__/file-installer.test.d.ts +7 -0
- package/dist/distribution/__tests__/file-installer.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/file-installer.test.js +714 -0
- package/dist/distribution/__tests__/file-installer.test.js.map +1 -0
- package/dist/distribution/__tests__/fleet-config-updater.test.d.ts +7 -0
- package/dist/distribution/__tests__/fleet-config-updater.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/fleet-config-updater.test.js +531 -0
- package/dist/distribution/__tests__/fleet-config-updater.test.js.map +1 -0
- package/dist/distribution/__tests__/installation-metadata.test.d.ts +2 -0
- package/dist/distribution/__tests__/installation-metadata.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/installation-metadata.test.js +292 -0
- package/dist/distribution/__tests__/installation-metadata.test.js.map +1 -0
- package/dist/distribution/__tests__/integration.test.d.ts +10 -0
- package/dist/distribution/__tests__/integration.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/integration.test.js +522 -0
- package/dist/distribution/__tests__/integration.test.js.map +1 -0
- package/dist/distribution/__tests__/repository-fetcher.test.d.ts +5 -0
- package/dist/distribution/__tests__/repository-fetcher.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/repository-fetcher.test.js +386 -0
- package/dist/distribution/__tests__/repository-fetcher.test.js.map +1 -0
- package/dist/distribution/__tests__/repository-validator.test.d.ts +7 -0
- package/dist/distribution/__tests__/repository-validator.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/repository-validator.test.js +447 -0
- package/dist/distribution/__tests__/repository-validator.test.js.map +1 -0
- package/dist/distribution/__tests__/source-specifier.test.d.ts +5 -0
- package/dist/distribution/__tests__/source-specifier.test.d.ts.map +1 -0
- package/dist/distribution/__tests__/source-specifier.test.js +533 -0
- package/dist/distribution/__tests__/source-specifier.test.js.map +1 -0
- package/dist/distribution/agent-discovery.d.ts +81 -0
- package/dist/distribution/agent-discovery.d.ts.map +1 -0
- package/dist/distribution/agent-discovery.js +264 -0
- package/dist/distribution/agent-discovery.js.map +1 -0
- package/dist/distribution/agent-info.d.ts +86 -0
- package/dist/distribution/agent-info.d.ts.map +1 -0
- package/dist/distribution/agent-info.js +225 -0
- package/dist/distribution/agent-info.js.map +1 -0
- package/dist/distribution/agent-remover.d.ts +83 -0
- package/dist/distribution/agent-remover.d.ts.map +1 -0
- package/dist/distribution/agent-remover.js +222 -0
- package/dist/distribution/agent-remover.js.map +1 -0
- package/dist/distribution/agent-repo-metadata.d.ts +181 -0
- package/dist/distribution/agent-repo-metadata.d.ts.map +1 -0
- package/dist/distribution/agent-repo-metadata.js +143 -0
- package/dist/distribution/agent-repo-metadata.js.map +1 -0
- package/dist/distribution/env-scanner.d.ts +78 -0
- package/dist/distribution/env-scanner.d.ts.map +1 -0
- package/dist/distribution/env-scanner.js +144 -0
- package/dist/distribution/env-scanner.js.map +1 -0
- package/dist/distribution/file-installer.d.ts +80 -0
- package/dist/distribution/file-installer.d.ts.map +1 -0
- package/dist/distribution/file-installer.js +268 -0
- package/dist/distribution/file-installer.js.map +1 -0
- package/dist/distribution/fleet-config-updater.d.ts +96 -0
- package/dist/distribution/fleet-config-updater.d.ts.map +1 -0
- package/dist/distribution/fleet-config-updater.js +266 -0
- package/dist/distribution/fleet-config-updater.js.map +1 -0
- package/dist/distribution/index.d.ts +23 -0
- package/dist/distribution/index.d.ts.map +1 -0
- package/dist/distribution/index.js +42 -0
- package/dist/distribution/index.js.map +1 -0
- package/dist/distribution/installation-metadata.d.ts +191 -0
- package/dist/distribution/installation-metadata.d.ts.map +1 -0
- package/dist/distribution/installation-metadata.js +100 -0
- package/dist/distribution/installation-metadata.js.map +1 -0
- package/dist/distribution/repository-fetcher.d.ts +104 -0
- package/dist/distribution/repository-fetcher.d.ts.map +1 -0
- package/dist/distribution/repository-fetcher.js +246 -0
- package/dist/distribution/repository-fetcher.js.map +1 -0
- package/dist/distribution/repository-validator.d.ts +86 -0
- package/dist/distribution/repository-validator.d.ts.map +1 -0
- package/dist/distribution/repository-validator.js +296 -0
- package/dist/distribution/repository-validator.js.map +1 -0
- package/dist/distribution/source-specifier.d.ts +106 -0
- package/dist/distribution/source-specifier.d.ts.map +1 -0
- package/dist/distribution/source-specifier.js +247 -0
- package/dist/distribution/source-specifier.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +15 -0
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +16 -0
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +31 -9
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/message-processor.d.ts.map +1 -1
- package/dist/runner/message-processor.js +7 -2
- package/dist/runner/message-processor.js.map +1 -1
- package/dist/runner/runtime/container-manager.js +1 -1
- package/dist/runner/runtime/container-manager.js.map +1 -1
- package/dist/scheduler/errors.d.ts +15 -0
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +6 -5
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/state/__tests__/jsonl-parser.test.d.ts +5 -0
- package/dist/state/__tests__/jsonl-parser.test.d.ts.map +1 -0
- package/dist/state/__tests__/jsonl-parser.test.js +307 -0
- package/dist/state/__tests__/jsonl-parser.test.js.map +1 -0
- package/dist/state/__tests__/session-attribution.test.d.ts +2 -0
- package/dist/state/__tests__/session-attribution.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-attribution.test.js +567 -0
- package/dist/state/__tests__/session-attribution.test.js.map +1 -0
- package/dist/state/__tests__/session-discovery.test.d.ts +2 -0
- package/dist/state/__tests__/session-discovery.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-discovery.test.js +941 -0
- package/dist/state/__tests__/session-discovery.test.js.map +1 -0
- package/dist/state/__tests__/session-metadata.test.d.ts +2 -0
- package/dist/state/__tests__/session-metadata.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-metadata.test.js +422 -0
- package/dist/state/__tests__/session-metadata.test.js.map +1 -0
- package/dist/state/__tests__/tool-parsing.test.d.ts +5 -0
- package/dist/state/__tests__/tool-parsing.test.d.ts.map +1 -0
- package/dist/state/__tests__/tool-parsing.test.js +315 -0
- package/dist/state/__tests__/tool-parsing.test.js.map +1 -0
- package/dist/state/index.d.ts +5 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +10 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/jsonl-parser.d.ts +115 -0
- package/dist/state/jsonl-parser.d.ts.map +1 -0
- package/dist/state/jsonl-parser.js +437 -0
- package/dist/state/jsonl-parser.js.map +1 -0
- package/dist/state/session-attribution.d.ts +35 -0
- package/dist/state/session-attribution.d.ts.map +1 -0
- package/dist/state/session-attribution.js +179 -0
- package/dist/state/session-attribution.js.map +1 -0
- package/dist/state/session-discovery.d.ts +188 -0
- package/dist/state/session-discovery.d.ts.map +1 -0
- package/dist/state/session-discovery.js +513 -0
- package/dist/state/session-discovery.js.map +1 -0
- package/dist/state/session-metadata.d.ts +186 -0
- package/dist/state/session-metadata.d.ts.map +1 -0
- package/dist/state/session-metadata.js +297 -0
- package/dist/state/session-metadata.js.map +1 -0
- package/dist/state/tool-parsing.d.ts +88 -0
- package/dist/state/tool-parsing.d.ts.map +1 -0
- package/dist/state/tool-parsing.js +199 -0
- package/dist/state/tool-parsing.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Validation for Agent Distribution
|
|
3
|
+
*
|
|
4
|
+
* Validates that a directory is a valid agent repository by checking:
|
|
5
|
+
* - agent.yaml exists and is valid YAML conforming to AgentConfigSchema
|
|
6
|
+
* - docker.network is not "none" (agents need network access for Anthropic API)
|
|
7
|
+
* - herdctl.json is valid if present
|
|
8
|
+
* - Optional files like CLAUDE.md and README.md
|
|
9
|
+
*/
|
|
10
|
+
import { access, readFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { parse as parseYaml, YAMLParseError } from "yaml";
|
|
13
|
+
import { AgentConfigSchema } from "../config/schema.js";
|
|
14
|
+
import { createLogger } from "../utils/logger.js";
|
|
15
|
+
import { AgentRepoMetadataSchema } from "./agent-repo-metadata.js";
|
|
16
|
+
const logger = createLogger("distribution:validator");
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Error Codes
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/** Error: agent.yaml not found */
|
|
21
|
+
export const MISSING_AGENT_YAML = "MISSING_AGENT_YAML";
|
|
22
|
+
/** Error: agent.yaml is not valid YAML */
|
|
23
|
+
export const YAML_PARSE_ERROR = "YAML_PARSE_ERROR";
|
|
24
|
+
/** Error: agent.yaml fails schema validation */
|
|
25
|
+
export const INVALID_AGENT_YAML = "INVALID_AGENT_YAML";
|
|
26
|
+
/** Error: docker.network is set to "none" */
|
|
27
|
+
export const DOCKER_NETWORK_NONE = "DOCKER_NETWORK_NONE";
|
|
28
|
+
/** Error: herdctl.json is not valid JSON */
|
|
29
|
+
export const JSON_PARSE_ERROR = "JSON_PARSE_ERROR";
|
|
30
|
+
/** Error: herdctl.json fails schema validation */
|
|
31
|
+
export const INVALID_HERDCTL_JSON = "INVALID_HERDCTL_JSON";
|
|
32
|
+
/** Warning: no herdctl.json (optional but recommended) */
|
|
33
|
+
export const MISSING_HERDCTL_JSON = "MISSING_HERDCTL_JSON";
|
|
34
|
+
/** Warning: name in agent.yaml differs from name in herdctl.json */
|
|
35
|
+
export const NAME_MISMATCH = "NAME_MISMATCH";
|
|
36
|
+
/** Warning: no CLAUDE.md file (optional but recommended) */
|
|
37
|
+
export const MISSING_CLAUDE_MD = "MISSING_CLAUDE_MD";
|
|
38
|
+
/** Warning: no README.md (optional but recommended) */
|
|
39
|
+
export const MISSING_README = "MISSING_README";
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Helper Functions
|
|
42
|
+
// =============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Check if a file exists
|
|
45
|
+
*/
|
|
46
|
+
async function fileExists(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
await access(filePath);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read and parse YAML file
|
|
57
|
+
*/
|
|
58
|
+
async function readYamlFile(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await readFile(filePath, "utf-8");
|
|
61
|
+
const data = parseYaml(content);
|
|
62
|
+
return { success: true, data };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof YAMLParseError) {
|
|
66
|
+
const position = err.linePos?.[0];
|
|
67
|
+
const locationInfo = position ? ` at line ${position.line}, column ${position.col}` : "";
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Invalid YAML syntax${locationInfo}: ${err.message}`,
|
|
71
|
+
isYamlError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// File read error
|
|
75
|
+
const error = err;
|
|
76
|
+
return { success: false, error: error.message, isYamlError: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read and parse JSON file
|
|
81
|
+
*/
|
|
82
|
+
async function readJsonFile(filePath) {
|
|
83
|
+
try {
|
|
84
|
+
const content = await readFile(filePath, "utf-8");
|
|
85
|
+
const data = JSON.parse(content);
|
|
86
|
+
return { success: true, data };
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof SyntaxError) {
|
|
90
|
+
return { success: false, error: `Invalid JSON: ${err.message}`, isJsonError: true };
|
|
91
|
+
}
|
|
92
|
+
// File read error
|
|
93
|
+
const error = err;
|
|
94
|
+
return { success: false, error: error.message, isJsonError: false };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Main Validator
|
|
99
|
+
// =============================================================================
|
|
100
|
+
/**
|
|
101
|
+
* Validate a directory as an agent repository
|
|
102
|
+
*
|
|
103
|
+
* Checks that the directory contains valid agent repository files:
|
|
104
|
+
* - agent.yaml (required) - must be valid YAML conforming to AgentConfigSchema
|
|
105
|
+
* - herdctl.json (optional) - if present, must be valid JSON conforming to AgentRepoMetadataSchema
|
|
106
|
+
* - CLAUDE.md (optional) - recommended for agent identity
|
|
107
|
+
* - README.md (optional) - recommended for documentation
|
|
108
|
+
*
|
|
109
|
+
* Also performs safety checks:
|
|
110
|
+
* - docker.network cannot be "none" (agents need network for Anthropic API)
|
|
111
|
+
*
|
|
112
|
+
* @param dirPath - Absolute path to the directory to validate
|
|
113
|
+
* @returns Validation result with errors, warnings, and parsed configurations
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const result = await validateRepository("/tmp/my-agent");
|
|
118
|
+
* if (result.valid) {
|
|
119
|
+
* console.log(`Agent ${result.agentName} is valid`);
|
|
120
|
+
* } else {
|
|
121
|
+
* console.error("Validation errors:", result.errors);
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export async function validateRepository(dirPath) {
|
|
126
|
+
logger.debug("Validating repository", { path: dirPath });
|
|
127
|
+
const errors = [];
|
|
128
|
+
const warnings = [];
|
|
129
|
+
let agentName = null;
|
|
130
|
+
let agentConfig = null;
|
|
131
|
+
let repoMetadata = null;
|
|
132
|
+
// ==========================================================================
|
|
133
|
+
// 1. Check and validate agent.yaml (required)
|
|
134
|
+
// ==========================================================================
|
|
135
|
+
const agentYamlPath = join(dirPath, "agent.yaml");
|
|
136
|
+
const agentYamlExists = await fileExists(agentYamlPath);
|
|
137
|
+
if (!agentYamlExists) {
|
|
138
|
+
logger.debug("agent.yaml not found", { path: agentYamlPath });
|
|
139
|
+
errors.push({
|
|
140
|
+
code: MISSING_AGENT_YAML,
|
|
141
|
+
message: "Required file agent.yaml not found. Every agent repository must have an agent.yaml file.",
|
|
142
|
+
path: "agent.yaml",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Read and parse YAML
|
|
147
|
+
const yamlResult = await readYamlFile(agentYamlPath);
|
|
148
|
+
if (!yamlResult.success) {
|
|
149
|
+
logger.debug("Failed to parse agent.yaml", { error: yamlResult.error });
|
|
150
|
+
errors.push({
|
|
151
|
+
code: yamlResult.isYamlError ? YAML_PARSE_ERROR : MISSING_AGENT_YAML,
|
|
152
|
+
message: yamlResult.error,
|
|
153
|
+
path: "agent.yaml",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// ==========================================================================
|
|
158
|
+
// 2. Check docker.network BEFORE schema validation (provides clearer error)
|
|
159
|
+
// Note: AgentDockerSchema doesn't include 'network' field (it's fleet-level only),
|
|
160
|
+
// but we want to give a specific error message if someone sets network: none
|
|
161
|
+
// ==========================================================================
|
|
162
|
+
const rawData = yamlResult.data;
|
|
163
|
+
const dockerConfig = rawData?.docker;
|
|
164
|
+
if (dockerConfig?.network === "none") {
|
|
165
|
+
logger.debug("docker.network is set to none", { docker: dockerConfig });
|
|
166
|
+
errors.push({
|
|
167
|
+
code: DOCKER_NETWORK_NONE,
|
|
168
|
+
message: 'docker.network is set to "none", which would prevent the agent from accessing Anthropic APIs. ' +
|
|
169
|
+
"Agents require network access. Remove the network field (bridge is the default at fleet level) " +
|
|
170
|
+
'or set it at the fleet level to "bridge" or "host".',
|
|
171
|
+
path: "agent.yaml",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Validate against AgentConfigSchema
|
|
175
|
+
const schemaResult = AgentConfigSchema.safeParse(yamlResult.data);
|
|
176
|
+
if (!schemaResult.success) {
|
|
177
|
+
const issues = schemaResult.error.issues
|
|
178
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
179
|
+
.join("; ");
|
|
180
|
+
logger.debug("agent.yaml schema validation failed", { issues });
|
|
181
|
+
errors.push({
|
|
182
|
+
code: INVALID_AGENT_YAML,
|
|
183
|
+
message: `agent.yaml validation failed: ${issues}`,
|
|
184
|
+
path: "agent.yaml",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
agentConfig = schemaResult.data;
|
|
189
|
+
agentName = schemaResult.data.name;
|
|
190
|
+
logger.debug("agent.yaml is valid", { name: agentName });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ==========================================================================
|
|
195
|
+
// 3. Check and validate herdctl.json (optional)
|
|
196
|
+
// ==========================================================================
|
|
197
|
+
const herdctlJsonPath = join(dirPath, "herdctl.json");
|
|
198
|
+
const herdctlJsonExists = await fileExists(herdctlJsonPath);
|
|
199
|
+
if (!herdctlJsonExists) {
|
|
200
|
+
logger.debug("herdctl.json not found (optional)", { path: herdctlJsonPath });
|
|
201
|
+
warnings.push({
|
|
202
|
+
code: MISSING_HERDCTL_JSON,
|
|
203
|
+
message: "No herdctl.json found. This file is optional but recommended for registry listing and installation metadata.",
|
|
204
|
+
path: "herdctl.json",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Read and parse JSON
|
|
209
|
+
const jsonResult = await readJsonFile(herdctlJsonPath);
|
|
210
|
+
if (!jsonResult.success) {
|
|
211
|
+
logger.debug("Failed to parse herdctl.json", { error: jsonResult.error });
|
|
212
|
+
errors.push({
|
|
213
|
+
code: jsonResult.isJsonError ? JSON_PARSE_ERROR : INVALID_HERDCTL_JSON,
|
|
214
|
+
message: jsonResult.error,
|
|
215
|
+
path: "herdctl.json",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Validate against AgentRepoMetadataSchema
|
|
220
|
+
const schemaResult = AgentRepoMetadataSchema.safeParse(jsonResult.data);
|
|
221
|
+
if (!schemaResult.success) {
|
|
222
|
+
const issues = schemaResult.error.issues
|
|
223
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
224
|
+
.join("; ");
|
|
225
|
+
logger.debug("herdctl.json schema validation failed", { issues });
|
|
226
|
+
errors.push({
|
|
227
|
+
code: INVALID_HERDCTL_JSON,
|
|
228
|
+
message: `herdctl.json validation failed: ${issues}`,
|
|
229
|
+
path: "herdctl.json",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
repoMetadata = schemaResult.data;
|
|
234
|
+
logger.debug("herdctl.json is valid", { name: repoMetadata.name });
|
|
235
|
+
// ==========================================================================
|
|
236
|
+
// 4. Check name consistency between agent.yaml and herdctl.json
|
|
237
|
+
// ==========================================================================
|
|
238
|
+
if (agentName && repoMetadata.name !== agentName) {
|
|
239
|
+
logger.debug("Name mismatch between agent.yaml and herdctl.json", {
|
|
240
|
+
agentYaml: agentName,
|
|
241
|
+
herdctlJson: repoMetadata.name,
|
|
242
|
+
});
|
|
243
|
+
warnings.push({
|
|
244
|
+
code: NAME_MISMATCH,
|
|
245
|
+
message: `Name mismatch: agent.yaml has name "${agentName}" but herdctl.json has name "${repoMetadata.name}". ` +
|
|
246
|
+
`The name from agent.yaml will be used for installation.`,
|
|
247
|
+
path: "herdctl.json",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ==========================================================================
|
|
254
|
+
// 5. Check for optional recommended files
|
|
255
|
+
// ==========================================================================
|
|
256
|
+
const claudeMdPath = join(dirPath, "CLAUDE.md");
|
|
257
|
+
const claudeMdExists = await fileExists(claudeMdPath);
|
|
258
|
+
if (!claudeMdExists) {
|
|
259
|
+
logger.debug("CLAUDE.md not found (optional)", { path: claudeMdPath });
|
|
260
|
+
warnings.push({
|
|
261
|
+
code: MISSING_CLAUDE_MD,
|
|
262
|
+
message: "No CLAUDE.md found. This file is optional but recommended for defining agent identity and behavior.",
|
|
263
|
+
path: "CLAUDE.md",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const readmePath = join(dirPath, "README.md");
|
|
267
|
+
const readmeExists = await fileExists(readmePath);
|
|
268
|
+
if (!readmeExists) {
|
|
269
|
+
logger.debug("README.md not found (optional)", { path: readmePath });
|
|
270
|
+
warnings.push({
|
|
271
|
+
code: MISSING_README,
|
|
272
|
+
message: "No README.md found. This file is optional but recommended for documentation.",
|
|
273
|
+
path: "README.md",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// ==========================================================================
|
|
277
|
+
// Build and return result
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
const valid = errors.length === 0;
|
|
280
|
+
logger.info("Repository validation complete", {
|
|
281
|
+
path: dirPath,
|
|
282
|
+
valid,
|
|
283
|
+
errorCount: errors.length,
|
|
284
|
+
warningCount: warnings.length,
|
|
285
|
+
agentName,
|
|
286
|
+
});
|
|
287
|
+
return {
|
|
288
|
+
valid,
|
|
289
|
+
agentName,
|
|
290
|
+
agentConfig,
|
|
291
|
+
repoMetadata,
|
|
292
|
+
errors,
|
|
293
|
+
warnings,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=repository-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository-validator.js","sourceRoot":"","sources":["../../src/distribution/repository-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Specifier Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses source strings into structured specifier objects for agent distribution.
|
|
5
|
+
*
|
|
6
|
+
* Supported formats:
|
|
7
|
+
* - `github:user/repo` → GitHub source (explicit prefix)
|
|
8
|
+
* - `github:user/repo@v1.0.0` → GitHub source with tag/branch/commit ref
|
|
9
|
+
* - `user/repo` → GitHub source (shorthand, equivalent to `github:user/repo`)
|
|
10
|
+
* - `user/repo@v1.0.0` → GitHub source shorthand with ref
|
|
11
|
+
* - `./local/path` or `../path` or `/absolute/path` → Local source
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const specifier = parseSourceSpecifier("github:user/repo@v1.0.0");
|
|
16
|
+
* // { type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' }
|
|
17
|
+
*
|
|
18
|
+
* const shorthand = parseSourceSpecifier("user/repo");
|
|
19
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
20
|
+
*
|
|
21
|
+
* const local = parseSourceSpecifier("./my-agents/custom");
|
|
22
|
+
* // { type: 'local', path: '/resolved/absolute/path/my-agents/custom' }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { FleetManagerError } from "../fleet-manager/errors.js";
|
|
26
|
+
/**
|
|
27
|
+
* GitHub source specifier.
|
|
28
|
+
* Points to a GitHub repository, optionally at a specific ref (tag, branch, or commit).
|
|
29
|
+
*/
|
|
30
|
+
export interface GitHubSource {
|
|
31
|
+
type: "github";
|
|
32
|
+
/** GitHub username or organization */
|
|
33
|
+
owner: string;
|
|
34
|
+
/** Repository name */
|
|
35
|
+
repo: string;
|
|
36
|
+
/** Optional ref: tag, branch name, or commit SHA */
|
|
37
|
+
ref?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Local source specifier.
|
|
41
|
+
* Points to a local directory path (resolved to absolute).
|
|
42
|
+
*/
|
|
43
|
+
export interface LocalSource {
|
|
44
|
+
type: "local";
|
|
45
|
+
/** Absolute path to the local directory */
|
|
46
|
+
path: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Union type for all source specifier variants.
|
|
50
|
+
*/
|
|
51
|
+
export type SourceSpecifier = GitHubSource | LocalSource;
|
|
52
|
+
/**
|
|
53
|
+
* Error thrown when a source specifier cannot be parsed.
|
|
54
|
+
*/
|
|
55
|
+
export declare class SourceParseError extends FleetManagerError {
|
|
56
|
+
readonly source: string;
|
|
57
|
+
constructor(message: string, source: string);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses a source specifier string into a structured SourceSpecifier object.
|
|
61
|
+
*
|
|
62
|
+
* @param source - The source string to parse
|
|
63
|
+
* @returns Parsed SourceSpecifier
|
|
64
|
+
* @throws SourceParseError if the source format is invalid
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // GitHub sources
|
|
69
|
+
* parseSourceSpecifier("github:user/repo")
|
|
70
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
71
|
+
*
|
|
72
|
+
* parseSourceSpecifier("github:user/repo@v1.0.0")
|
|
73
|
+
* // { type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' }
|
|
74
|
+
*
|
|
75
|
+
* // Local sources
|
|
76
|
+
* parseSourceSpecifier("./local/path")
|
|
77
|
+
* // { type: 'local', path: '/resolved/absolute/path' }
|
|
78
|
+
*
|
|
79
|
+
* // GitHub shorthand
|
|
80
|
+
* parseSourceSpecifier("user/repo")
|
|
81
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseSourceSpecifier(source: string): SourceSpecifier;
|
|
85
|
+
/**
|
|
86
|
+
* Type guard for GitHubSource.
|
|
87
|
+
*/
|
|
88
|
+
export declare function isGitHubSource(specifier: SourceSpecifier): specifier is GitHubSource;
|
|
89
|
+
/**
|
|
90
|
+
* Type guard for LocalSource.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isLocalSource(specifier: SourceSpecifier): specifier is LocalSource;
|
|
93
|
+
/**
|
|
94
|
+
* Converts a SourceSpecifier back to its string representation.
|
|
95
|
+
*
|
|
96
|
+
* @param specifier - The specifier to stringify
|
|
97
|
+
* @returns String representation of the specifier
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* stringifySourceSpecifier({ type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' })
|
|
102
|
+
* // "github:user/repo@v1.0.0"
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function stringifySourceSpecifier(specifier: SourceSpecifier): string;
|
|
106
|
+
//# sourceMappingURL=source-specifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-specifier.d.ts","sourceRoot":"","sources":["../../src/distribution/source-specifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAM/D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,WAAW,CAAC;AAMzD;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,iBAAiB;aAGnC,MAAM,EAAE,MAAM;gBAD9B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM;CAKjC;AA2JD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAiCpE;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,eAAe,GAAG,SAAS,IAAI,YAAY,CAEpF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,eAAe,GAAG,SAAS,IAAI,WAAW,CAElF;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,CAY3E"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Specifier Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses source strings into structured specifier objects for agent distribution.
|
|
5
|
+
*
|
|
6
|
+
* Supported formats:
|
|
7
|
+
* - `github:user/repo` → GitHub source (explicit prefix)
|
|
8
|
+
* - `github:user/repo@v1.0.0` → GitHub source with tag/branch/commit ref
|
|
9
|
+
* - `user/repo` → GitHub source (shorthand, equivalent to `github:user/repo`)
|
|
10
|
+
* - `user/repo@v1.0.0` → GitHub source shorthand with ref
|
|
11
|
+
* - `./local/path` or `../path` or `/absolute/path` → Local source
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const specifier = parseSourceSpecifier("github:user/repo@v1.0.0");
|
|
16
|
+
* // { type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' }
|
|
17
|
+
*
|
|
18
|
+
* const shorthand = parseSourceSpecifier("user/repo");
|
|
19
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
20
|
+
*
|
|
21
|
+
* const local = parseSourceSpecifier("./my-agents/custom");
|
|
22
|
+
* // { type: 'local', path: '/resolved/absolute/path/my-agents/custom' }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import * as path from "path";
|
|
26
|
+
import { FleetManagerError } from "../fleet-manager/errors.js";
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Error Class
|
|
29
|
+
// =============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when a source specifier cannot be parsed.
|
|
32
|
+
*/
|
|
33
|
+
export class SourceParseError extends FleetManagerError {
|
|
34
|
+
source;
|
|
35
|
+
constructor(message, source) {
|
|
36
|
+
super(message, { code: "SOURCE_PARSE_ERROR" });
|
|
37
|
+
this.source = source;
|
|
38
|
+
this.name = "SourceParseError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Constants
|
|
43
|
+
// =============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Pattern for validating GitHub owner/repo names.
|
|
46
|
+
* GitHub allows: alphanumeric, hyphens, underscores, and dots.
|
|
47
|
+
* Cannot start with a dot or end with .git.
|
|
48
|
+
* Maximum 100 characters for owner, 100 for repo.
|
|
49
|
+
*
|
|
50
|
+
* @see https://docs.github.com/en/repositories/creating-and-managing-repositories/about-repositories
|
|
51
|
+
*/
|
|
52
|
+
const GITHUB_NAME_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
|
|
53
|
+
/**
|
|
54
|
+
* Maximum length for GitHub owner/repo names.
|
|
55
|
+
*/
|
|
56
|
+
const MAX_GITHUB_NAME_LENGTH = 100;
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Parser Implementation
|
|
59
|
+
// =============================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Validates a GitHub owner or repository name.
|
|
62
|
+
*
|
|
63
|
+
* @param name - The name to validate
|
|
64
|
+
* @param field - Field name for error messages ("owner" or "repository")
|
|
65
|
+
* @param source - Original source string for error context
|
|
66
|
+
* @throws SourceParseError if the name is invalid
|
|
67
|
+
*/
|
|
68
|
+
function validateGitHubName(name, field, source) {
|
|
69
|
+
if (!name) {
|
|
70
|
+
throw new SourceParseError(`GitHub ${field} cannot be empty`, source);
|
|
71
|
+
}
|
|
72
|
+
if (name.length > MAX_GITHUB_NAME_LENGTH) {
|
|
73
|
+
throw new SourceParseError(`GitHub ${field} "${name}" exceeds maximum length of ${MAX_GITHUB_NAME_LENGTH} characters`, source);
|
|
74
|
+
}
|
|
75
|
+
if (!GITHUB_NAME_PATTERN.test(name)) {
|
|
76
|
+
throw new SourceParseError(`Invalid GitHub ${field} "${name}". ${field === "owner" ? "Owner" : "Repository"} names may only contain alphanumeric characters, hyphens, underscores, and dots, and cannot start with a dot.`, source);
|
|
77
|
+
}
|
|
78
|
+
// Additional check: cannot end with .git (GitHub convention)
|
|
79
|
+
if (name.endsWith(".git")) {
|
|
80
|
+
throw new SourceParseError(`GitHub ${field} "${name}" cannot end with ".git"`, source);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parses a GitHub source specifier.
|
|
85
|
+
*
|
|
86
|
+
* @param value - The value after "github:" prefix (e.g., "user/repo" or "user/repo@v1.0.0")
|
|
87
|
+
* @param source - Original source string for error context
|
|
88
|
+
* @returns Parsed GitHubSource
|
|
89
|
+
* @throws SourceParseError if the format is invalid
|
|
90
|
+
*/
|
|
91
|
+
function parseGitHubSource(value, source) {
|
|
92
|
+
if (!value) {
|
|
93
|
+
throw new SourceParseError('GitHub source requires owner/repo format (e.g., "github:user/repo")', source);
|
|
94
|
+
}
|
|
95
|
+
// Split ref if present: user/repo@ref
|
|
96
|
+
let repoPath = value;
|
|
97
|
+
let ref;
|
|
98
|
+
const atIndex = value.indexOf("@");
|
|
99
|
+
if (atIndex !== -1) {
|
|
100
|
+
repoPath = value.slice(0, atIndex);
|
|
101
|
+
ref = value.slice(atIndex + 1);
|
|
102
|
+
if (!ref) {
|
|
103
|
+
throw new SourceParseError('GitHub ref cannot be empty after "@" (e.g., "github:user/repo@v1.0.0")', source);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Split owner/repo
|
|
107
|
+
const slashIndex = repoPath.indexOf("/");
|
|
108
|
+
if (slashIndex === -1) {
|
|
109
|
+
throw new SourceParseError(`Invalid GitHub source format "${source}". Expected "github:owner/repo" format.`, source);
|
|
110
|
+
}
|
|
111
|
+
const owner = repoPath.slice(0, slashIndex);
|
|
112
|
+
const repo = repoPath.slice(slashIndex + 1);
|
|
113
|
+
// Check for extra slashes (nested paths not supported)
|
|
114
|
+
if (repo.includes("/")) {
|
|
115
|
+
throw new SourceParseError(`Invalid GitHub source format "${source}". Nested paths are not supported; use "github:owner/repo" format.`, source);
|
|
116
|
+
}
|
|
117
|
+
// Validate owner and repo
|
|
118
|
+
validateGitHubName(owner, "owner", source);
|
|
119
|
+
validateGitHubName(repo, "repository", source);
|
|
120
|
+
const result = { type: "github", owner, repo };
|
|
121
|
+
if (ref) {
|
|
122
|
+
result.ref = ref;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parses a local path source specifier.
|
|
128
|
+
*
|
|
129
|
+
* @param pathStr - The path string (relative or absolute)
|
|
130
|
+
* @returns Parsed LocalSource with absolute path
|
|
131
|
+
*/
|
|
132
|
+
function parseLocalSource(pathStr) {
|
|
133
|
+
// Resolve to absolute path
|
|
134
|
+
const absolutePath = path.resolve(pathStr);
|
|
135
|
+
return {
|
|
136
|
+
type: "local",
|
|
137
|
+
path: absolutePath,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Determines if a source string represents a local path.
|
|
142
|
+
*
|
|
143
|
+
* @param source - The source string to check
|
|
144
|
+
* @returns true if the source is a local path
|
|
145
|
+
*/
|
|
146
|
+
function isLocalPath(source) {
|
|
147
|
+
return (source.startsWith("./") ||
|
|
148
|
+
source.startsWith("../") ||
|
|
149
|
+
source.startsWith("/") ||
|
|
150
|
+
// Windows absolute paths (e.g., C:\)
|
|
151
|
+
/^[a-zA-Z]:[/\\]/.test(source));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parses a source specifier string into a structured SourceSpecifier object.
|
|
155
|
+
*
|
|
156
|
+
* @param source - The source string to parse
|
|
157
|
+
* @returns Parsed SourceSpecifier
|
|
158
|
+
* @throws SourceParseError if the source format is invalid
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // GitHub sources
|
|
163
|
+
* parseSourceSpecifier("github:user/repo")
|
|
164
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
165
|
+
*
|
|
166
|
+
* parseSourceSpecifier("github:user/repo@v1.0.0")
|
|
167
|
+
* // { type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' }
|
|
168
|
+
*
|
|
169
|
+
* // Local sources
|
|
170
|
+
* parseSourceSpecifier("./local/path")
|
|
171
|
+
* // { type: 'local', path: '/resolved/absolute/path' }
|
|
172
|
+
*
|
|
173
|
+
* // GitHub shorthand
|
|
174
|
+
* parseSourceSpecifier("user/repo")
|
|
175
|
+
* // { type: 'github', owner: 'user', repo: 'repo' }
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function parseSourceSpecifier(source) {
|
|
179
|
+
// Validate input
|
|
180
|
+
if (!source || typeof source !== "string") {
|
|
181
|
+
throw new SourceParseError("Source specifier cannot be empty", source || "");
|
|
182
|
+
}
|
|
183
|
+
const trimmed = source.trim();
|
|
184
|
+
if (!trimmed) {
|
|
185
|
+
throw new SourceParseError("Source specifier cannot be empty or whitespace only", source);
|
|
186
|
+
}
|
|
187
|
+
// Check for GitHub prefix
|
|
188
|
+
if (trimmed.startsWith("github:")) {
|
|
189
|
+
const value = trimmed.slice(7); // Remove "github:" prefix
|
|
190
|
+
return parseGitHubSource(value, source);
|
|
191
|
+
}
|
|
192
|
+
// Check for local path
|
|
193
|
+
if (isLocalPath(trimmed)) {
|
|
194
|
+
return parseLocalSource(trimmed);
|
|
195
|
+
}
|
|
196
|
+
// Check for GitHub shorthand (owner/repo or owner/repo@ref)
|
|
197
|
+
// A bare string containing "/" that isn't a local path is treated as GitHub shorthand
|
|
198
|
+
const slashCount = (trimmed.match(/\//g) || []).length;
|
|
199
|
+
if (slashCount === 1) {
|
|
200
|
+
return parseGitHubSource(trimmed, source);
|
|
201
|
+
}
|
|
202
|
+
throw new SourceParseError(`Unrecognized source format "${source}". Use "owner/repo", "github:owner/repo", or a local path (./path, ../path, /path).`, source);
|
|
203
|
+
}
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Type Guards
|
|
206
|
+
// =============================================================================
|
|
207
|
+
/**
|
|
208
|
+
* Type guard for GitHubSource.
|
|
209
|
+
*/
|
|
210
|
+
export function isGitHubSource(specifier) {
|
|
211
|
+
return specifier.type === "github";
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Type guard for LocalSource.
|
|
215
|
+
*/
|
|
216
|
+
export function isLocalSource(specifier) {
|
|
217
|
+
return specifier.type === "local";
|
|
218
|
+
}
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Utility Functions
|
|
221
|
+
// =============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* Converts a SourceSpecifier back to its string representation.
|
|
224
|
+
*
|
|
225
|
+
* @param specifier - The specifier to stringify
|
|
226
|
+
* @returns String representation of the specifier
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```typescript
|
|
230
|
+
* stringifySourceSpecifier({ type: 'github', owner: 'user', repo: 'repo', ref: 'v1.0.0' })
|
|
231
|
+
* // "github:user/repo@v1.0.0"
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export function stringifySourceSpecifier(specifier) {
|
|
235
|
+
switch (specifier.type) {
|
|
236
|
+
case "github": {
|
|
237
|
+
let str = `github:${specifier.owner}/${specifier.repo}`;
|
|
238
|
+
if (specifier.ref) {
|
|
239
|
+
str += `@${specifier.ref}`;
|
|
240
|
+
}
|
|
241
|
+
return str;
|
|
242
|
+
}
|
|
243
|
+
case "local":
|
|
244
|
+
return specifier.path;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=source-specifier.js.map
|