@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,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for file installer
|
|
3
|
+
*
|
|
4
|
+
* Uses real file I/O with temporary directories to test installation logic.
|
|
5
|
+
*/
|
|
6
|
+
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
+
import { AGENT_ALREADY_EXISTS, AgentInstallError, INVALID_AGENT_NAME, INVALID_AGENT_YAML, installAgentFiles, MISSING_AGENT_YAML, } from "../file-installer.js";
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Test Setup
|
|
13
|
+
// =============================================================================
|
|
14
|
+
describe("installAgentFiles", () => {
|
|
15
|
+
let sourceDir;
|
|
16
|
+
let targetDir;
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
// Create fresh temp directories for each test
|
|
19
|
+
sourceDir = await mkdtemp(join(tmpdir(), "herdctl-installer-source-"));
|
|
20
|
+
targetDir = await mkdtemp(join(tmpdir(), "herdctl-installer-target-"));
|
|
21
|
+
});
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
// Clean up temp directories
|
|
24
|
+
await rm(sourceDir, { recursive: true, force: true });
|
|
25
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
// Helper Functions
|
|
29
|
+
// ===========================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Minimal valid agent.yaml content
|
|
32
|
+
*/
|
|
33
|
+
const minimalAgentYaml = `
|
|
34
|
+
name: test-agent
|
|
35
|
+
runtime: cli
|
|
36
|
+
`;
|
|
37
|
+
/**
|
|
38
|
+
* Standard source for metadata tracking
|
|
39
|
+
*/
|
|
40
|
+
const standardSource = {
|
|
41
|
+
type: "github",
|
|
42
|
+
url: "https://github.com/user/test-agent",
|
|
43
|
+
ref: "v1.0.0",
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Helper to check if a path exists
|
|
47
|
+
*/
|
|
48
|
+
async function pathExists(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
await access(filePath);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Helper to read JSON file
|
|
59
|
+
*/
|
|
60
|
+
async function readJsonFile(filePath) {
|
|
61
|
+
const content = await readFile(filePath, "utf-8");
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
}
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// Successful Installation Tests
|
|
66
|
+
// ===========================================================================
|
|
67
|
+
describe("successful installation", () => {
|
|
68
|
+
it("installs agent from source directory", async () => {
|
|
69
|
+
// Setup source directory with minimal agent.yaml
|
|
70
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
71
|
+
const result = await installAgentFiles({
|
|
72
|
+
sourceDir,
|
|
73
|
+
targetBaseDir: targetDir,
|
|
74
|
+
source: standardSource,
|
|
75
|
+
});
|
|
76
|
+
expect(result.agentName).toBe("test-agent");
|
|
77
|
+
expect(result.installPath).toBe(join(targetDir, "agents", "test-agent"));
|
|
78
|
+
expect(result.copiedFiles).toContain("agent.yaml");
|
|
79
|
+
});
|
|
80
|
+
it("creates workspace directory", async () => {
|
|
81
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
82
|
+
const result = await installAgentFiles({
|
|
83
|
+
sourceDir,
|
|
84
|
+
targetBaseDir: targetDir,
|
|
85
|
+
source: standardSource,
|
|
86
|
+
});
|
|
87
|
+
const workspacePath = join(result.installPath, "workspace");
|
|
88
|
+
expect(await pathExists(workspacePath)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
it("writes valid metadata.json", async () => {
|
|
91
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
92
|
+
const result = await installAgentFiles({
|
|
93
|
+
sourceDir,
|
|
94
|
+
targetBaseDir: targetDir,
|
|
95
|
+
source: standardSource,
|
|
96
|
+
});
|
|
97
|
+
const metadataPath = join(result.installPath, "metadata.json");
|
|
98
|
+
expect(await pathExists(metadataPath)).toBe(true);
|
|
99
|
+
const metadata = await readJsonFile(metadataPath);
|
|
100
|
+
expect(metadata.source.type).toBe("github");
|
|
101
|
+
expect(metadata.source.url).toBe("https://github.com/user/test-agent");
|
|
102
|
+
expect(metadata.source.ref).toBe("v1.0.0");
|
|
103
|
+
expect(metadata.installed_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
104
|
+
expect(metadata.installed_by).toMatch(/^herdctl@/);
|
|
105
|
+
});
|
|
106
|
+
it("excludes .git directory", async () => {
|
|
107
|
+
// Setup source with .git directory
|
|
108
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
109
|
+
await mkdir(join(sourceDir, ".git"), { recursive: true });
|
|
110
|
+
await writeFile(join(sourceDir, ".git", "config"), "git config");
|
|
111
|
+
await writeFile(join(sourceDir, ".git", "HEAD"), "ref: refs/heads/main");
|
|
112
|
+
const result = await installAgentFiles({
|
|
113
|
+
sourceDir,
|
|
114
|
+
targetBaseDir: targetDir,
|
|
115
|
+
source: standardSource,
|
|
116
|
+
});
|
|
117
|
+
// .git should not exist in target
|
|
118
|
+
expect(await pathExists(join(result.installPath, ".git"))).toBe(false);
|
|
119
|
+
// agent.yaml should exist
|
|
120
|
+
expect(await pathExists(join(result.installPath, "agent.yaml"))).toBe(true);
|
|
121
|
+
// copiedFiles should not include .git entries
|
|
122
|
+
expect(result.copiedFiles.some((f) => f.includes(".git"))).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
it("excludes node_modules directory", async () => {
|
|
125
|
+
// Setup source with node_modules directory
|
|
126
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
127
|
+
await mkdir(join(sourceDir, "node_modules", "some-package"), { recursive: true });
|
|
128
|
+
await writeFile(join(sourceDir, "node_modules", "some-package", "index.js"), "module.exports = {};");
|
|
129
|
+
const result = await installAgentFiles({
|
|
130
|
+
sourceDir,
|
|
131
|
+
targetBaseDir: targetDir,
|
|
132
|
+
source: standardSource,
|
|
133
|
+
});
|
|
134
|
+
// node_modules should not exist in target
|
|
135
|
+
expect(await pathExists(join(result.installPath, "node_modules"))).toBe(false);
|
|
136
|
+
// copiedFiles should not include node_modules entries
|
|
137
|
+
expect(result.copiedFiles.some((f) => f.includes("node_modules"))).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
it("handles nested directory structures", async () => {
|
|
140
|
+
// Setup source with nested directories
|
|
141
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
142
|
+
await writeFile(join(sourceDir, "CLAUDE.md"), "# Agent");
|
|
143
|
+
await mkdir(join(sourceDir, "knowledge"), { recursive: true });
|
|
144
|
+
await writeFile(join(sourceDir, "knowledge", "guide.md"), "# Guide");
|
|
145
|
+
await mkdir(join(sourceDir, "knowledge", "deep"), { recursive: true });
|
|
146
|
+
await writeFile(join(sourceDir, "knowledge", "deep", "nested.md"), "# Nested");
|
|
147
|
+
const result = await installAgentFiles({
|
|
148
|
+
sourceDir,
|
|
149
|
+
targetBaseDir: targetDir,
|
|
150
|
+
source: standardSource,
|
|
151
|
+
});
|
|
152
|
+
// All files should be copied
|
|
153
|
+
expect(await pathExists(join(result.installPath, "agent.yaml"))).toBe(true);
|
|
154
|
+
expect(await pathExists(join(result.installPath, "CLAUDE.md"))).toBe(true);
|
|
155
|
+
expect(await pathExists(join(result.installPath, "knowledge", "guide.md"))).toBe(true);
|
|
156
|
+
expect(await pathExists(join(result.installPath, "knowledge", "deep", "nested.md"))).toBe(true);
|
|
157
|
+
// copiedFiles should include all relative paths
|
|
158
|
+
expect(result.copiedFiles).toContain("agent.yaml");
|
|
159
|
+
expect(result.copiedFiles).toContain("CLAUDE.md");
|
|
160
|
+
expect(result.copiedFiles).toContain(join("knowledge", "guide.md"));
|
|
161
|
+
expect(result.copiedFiles).toContain(join("knowledge", "deep", "nested.md"));
|
|
162
|
+
});
|
|
163
|
+
it("works with --path override option", async () => {
|
|
164
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
165
|
+
const customPath = join(targetDir, "custom", "location", "my-agent");
|
|
166
|
+
const result = await installAgentFiles({
|
|
167
|
+
sourceDir,
|
|
168
|
+
targetBaseDir: targetDir,
|
|
169
|
+
source: standardSource,
|
|
170
|
+
targetPath: customPath,
|
|
171
|
+
});
|
|
172
|
+
expect(result.installPath).toBe(customPath);
|
|
173
|
+
expect(await pathExists(join(customPath, "agent.yaml"))).toBe(true);
|
|
174
|
+
expect(await pathExists(join(customPath, "workspace"))).toBe(true);
|
|
175
|
+
expect(await pathExists(join(customPath, "metadata.json"))).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it("returns correct copiedFiles list", async () => {
|
|
178
|
+
// Setup source with multiple files
|
|
179
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
180
|
+
await writeFile(join(sourceDir, "CLAUDE.md"), "# Identity");
|
|
181
|
+
await writeFile(join(sourceDir, "README.md"), "# Docs");
|
|
182
|
+
await writeFile(join(sourceDir, "herdctl.json"), "{}");
|
|
183
|
+
const result = await installAgentFiles({
|
|
184
|
+
sourceDir,
|
|
185
|
+
targetBaseDir: targetDir,
|
|
186
|
+
source: standardSource,
|
|
187
|
+
});
|
|
188
|
+
expect(result.copiedFiles).toHaveLength(4);
|
|
189
|
+
expect(result.copiedFiles).toContain("agent.yaml");
|
|
190
|
+
expect(result.copiedFiles).toContain("CLAUDE.md");
|
|
191
|
+
expect(result.copiedFiles).toContain("README.md");
|
|
192
|
+
expect(result.copiedFiles).toContain("herdctl.json");
|
|
193
|
+
});
|
|
194
|
+
it("handles local source type in metadata", async () => {
|
|
195
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
196
|
+
const localSource = {
|
|
197
|
+
type: "local",
|
|
198
|
+
url: "/path/to/local/agent",
|
|
199
|
+
};
|
|
200
|
+
const result = await installAgentFiles({
|
|
201
|
+
sourceDir,
|
|
202
|
+
targetBaseDir: targetDir,
|
|
203
|
+
source: localSource,
|
|
204
|
+
});
|
|
205
|
+
const metadata = await readJsonFile(join(result.installPath, "metadata.json"));
|
|
206
|
+
expect(metadata.source.type).toBe("local");
|
|
207
|
+
expect(metadata.source.url).toBe("/path/to/local/agent");
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
// ===========================================================================
|
|
211
|
+
// Error: Agent Already Exists
|
|
212
|
+
// ===========================================================================
|
|
213
|
+
describe("agent already exists error", () => {
|
|
214
|
+
it("errors when target already exists", async () => {
|
|
215
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
216
|
+
// Create existing agent directory
|
|
217
|
+
const existingPath = join(targetDir, "agents", "test-agent");
|
|
218
|
+
await mkdir(existingPath, { recursive: true });
|
|
219
|
+
await writeFile(join(existingPath, "agent.yaml"), minimalAgentYaml);
|
|
220
|
+
await expect(installAgentFiles({
|
|
221
|
+
sourceDir,
|
|
222
|
+
targetBaseDir: targetDir,
|
|
223
|
+
source: standardSource,
|
|
224
|
+
})).rejects.toThrow(AgentInstallError);
|
|
225
|
+
try {
|
|
226
|
+
await installAgentFiles({
|
|
227
|
+
sourceDir,
|
|
228
|
+
targetBaseDir: targetDir,
|
|
229
|
+
source: standardSource,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
const error = err;
|
|
234
|
+
expect(error.code).toBe(AGENT_ALREADY_EXISTS);
|
|
235
|
+
expect(error.message).toContain("test-agent");
|
|
236
|
+
expect(error.message).toContain("already exists");
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
it("errors even if target is an empty directory", async () => {
|
|
240
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
241
|
+
// Create existing empty directory
|
|
242
|
+
const existingPath = join(targetDir, "agents", "test-agent");
|
|
243
|
+
await mkdir(existingPath, { recursive: true });
|
|
244
|
+
await expect(installAgentFiles({
|
|
245
|
+
sourceDir,
|
|
246
|
+
targetBaseDir: targetDir,
|
|
247
|
+
source: standardSource,
|
|
248
|
+
})).rejects.toThrow(AgentInstallError);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
// ===========================================================================
|
|
252
|
+
// Error: Invalid Agent Name
|
|
253
|
+
// ===========================================================================
|
|
254
|
+
describe("invalid agent name error", () => {
|
|
255
|
+
it("errors on agent name with spaces", async () => {
|
|
256
|
+
const invalidNameYaml = `
|
|
257
|
+
name: "invalid name with spaces"
|
|
258
|
+
runtime: cli
|
|
259
|
+
`;
|
|
260
|
+
await writeFile(join(sourceDir, "agent.yaml"), invalidNameYaml);
|
|
261
|
+
await expect(installAgentFiles({
|
|
262
|
+
sourceDir,
|
|
263
|
+
targetBaseDir: targetDir,
|
|
264
|
+
source: standardSource,
|
|
265
|
+
})).rejects.toThrow(AgentInstallError);
|
|
266
|
+
try {
|
|
267
|
+
await installAgentFiles({
|
|
268
|
+
sourceDir,
|
|
269
|
+
targetBaseDir: targetDir,
|
|
270
|
+
source: standardSource,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
const error = err;
|
|
275
|
+
expect(error.code).toBe(INVALID_AGENT_NAME);
|
|
276
|
+
expect(error.message).toContain("Invalid agent name");
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
it("errors on agent name starting with hyphen", async () => {
|
|
280
|
+
const invalidNameYaml = `
|
|
281
|
+
name: "-starts-with-hyphen"
|
|
282
|
+
runtime: cli
|
|
283
|
+
`;
|
|
284
|
+
await writeFile(join(sourceDir, "agent.yaml"), invalidNameYaml);
|
|
285
|
+
await expect(installAgentFiles({
|
|
286
|
+
sourceDir,
|
|
287
|
+
targetBaseDir: targetDir,
|
|
288
|
+
source: standardSource,
|
|
289
|
+
})).rejects.toThrow(AgentInstallError);
|
|
290
|
+
try {
|
|
291
|
+
await installAgentFiles({
|
|
292
|
+
sourceDir,
|
|
293
|
+
targetBaseDir: targetDir,
|
|
294
|
+
source: standardSource,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
const error = err;
|
|
299
|
+
expect(error.code).toBe(INVALID_AGENT_NAME);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
it("errors on agent name with special characters", async () => {
|
|
303
|
+
const invalidNameYaml = `
|
|
304
|
+
name: "agent@name.with.dots"
|
|
305
|
+
runtime: cli
|
|
306
|
+
`;
|
|
307
|
+
await writeFile(join(sourceDir, "agent.yaml"), invalidNameYaml);
|
|
308
|
+
await expect(installAgentFiles({
|
|
309
|
+
sourceDir,
|
|
310
|
+
targetBaseDir: targetDir,
|
|
311
|
+
source: standardSource,
|
|
312
|
+
})).rejects.toThrow(AgentInstallError);
|
|
313
|
+
try {
|
|
314
|
+
await installAgentFiles({
|
|
315
|
+
sourceDir,
|
|
316
|
+
targetBaseDir: targetDir,
|
|
317
|
+
source: standardSource,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
const error = err;
|
|
322
|
+
expect(error.code).toBe(INVALID_AGENT_NAME);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
it("errors on agent name with path traversal", async () => {
|
|
326
|
+
const invalidNameYaml = `
|
|
327
|
+
name: "../../../etc/passwd"
|
|
328
|
+
runtime: cli
|
|
329
|
+
`;
|
|
330
|
+
await writeFile(join(sourceDir, "agent.yaml"), invalidNameYaml);
|
|
331
|
+
await expect(installAgentFiles({
|
|
332
|
+
sourceDir,
|
|
333
|
+
targetBaseDir: targetDir,
|
|
334
|
+
source: standardSource,
|
|
335
|
+
})).rejects.toThrow(AgentInstallError);
|
|
336
|
+
try {
|
|
337
|
+
await installAgentFiles({
|
|
338
|
+
sourceDir,
|
|
339
|
+
targetBaseDir: targetDir,
|
|
340
|
+
source: standardSource,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
const error = err;
|
|
345
|
+
expect(error.code).toBe(INVALID_AGENT_NAME);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
it("allows valid agent names with underscores and hyphens", async () => {
|
|
349
|
+
const validNameYaml = `
|
|
350
|
+
name: my-agent_v2
|
|
351
|
+
runtime: cli
|
|
352
|
+
`;
|
|
353
|
+
await writeFile(join(sourceDir, "agent.yaml"), validNameYaml);
|
|
354
|
+
const result = await installAgentFiles({
|
|
355
|
+
sourceDir,
|
|
356
|
+
targetBaseDir: targetDir,
|
|
357
|
+
source: standardSource,
|
|
358
|
+
});
|
|
359
|
+
expect(result.agentName).toBe("my-agent_v2");
|
|
360
|
+
});
|
|
361
|
+
it("allows valid agent names starting with number", async () => {
|
|
362
|
+
const validNameYaml = `
|
|
363
|
+
name: 2fast2furious
|
|
364
|
+
runtime: cli
|
|
365
|
+
`;
|
|
366
|
+
await writeFile(join(sourceDir, "agent.yaml"), validNameYaml);
|
|
367
|
+
const result = await installAgentFiles({
|
|
368
|
+
sourceDir,
|
|
369
|
+
targetBaseDir: targetDir,
|
|
370
|
+
source: standardSource,
|
|
371
|
+
});
|
|
372
|
+
expect(result.agentName).toBe("2fast2furious");
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
// ===========================================================================
|
|
376
|
+
// Error: Missing agent.yaml
|
|
377
|
+
// ===========================================================================
|
|
378
|
+
describe("missing agent.yaml error", () => {
|
|
379
|
+
it("errors on missing agent.yaml", async () => {
|
|
380
|
+
// Don't create agent.yaml - empty source directory
|
|
381
|
+
await expect(installAgentFiles({
|
|
382
|
+
sourceDir,
|
|
383
|
+
targetBaseDir: targetDir,
|
|
384
|
+
source: standardSource,
|
|
385
|
+
})).rejects.toThrow(AgentInstallError);
|
|
386
|
+
try {
|
|
387
|
+
await installAgentFiles({
|
|
388
|
+
sourceDir,
|
|
389
|
+
targetBaseDir: targetDir,
|
|
390
|
+
source: standardSource,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
const error = err;
|
|
395
|
+
expect(error.code).toBe(MISSING_AGENT_YAML);
|
|
396
|
+
expect(error.message).toContain("agent.yaml not found");
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
it("errors when only other files exist", async () => {
|
|
400
|
+
// Create other files but not agent.yaml
|
|
401
|
+
await writeFile(join(sourceDir, "README.md"), "# Docs");
|
|
402
|
+
await writeFile(join(sourceDir, "CLAUDE.md"), "# Identity");
|
|
403
|
+
await expect(installAgentFiles({
|
|
404
|
+
sourceDir,
|
|
405
|
+
targetBaseDir: targetDir,
|
|
406
|
+
source: standardSource,
|
|
407
|
+
})).rejects.toThrow(AgentInstallError);
|
|
408
|
+
try {
|
|
409
|
+
await installAgentFiles({
|
|
410
|
+
sourceDir,
|
|
411
|
+
targetBaseDir: targetDir,
|
|
412
|
+
source: standardSource,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
const error = err;
|
|
417
|
+
expect(error.code).toBe(MISSING_AGENT_YAML);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
// ===========================================================================
|
|
422
|
+
// Error: Invalid agent.yaml
|
|
423
|
+
// ===========================================================================
|
|
424
|
+
describe("invalid agent.yaml error", () => {
|
|
425
|
+
it("errors on invalid YAML syntax", async () => {
|
|
426
|
+
const invalidYaml = `
|
|
427
|
+
name: test-agent
|
|
428
|
+
bad-indent: invalid
|
|
429
|
+
nested: wrong
|
|
430
|
+
`;
|
|
431
|
+
await writeFile(join(sourceDir, "agent.yaml"), invalidYaml);
|
|
432
|
+
await expect(installAgentFiles({
|
|
433
|
+
sourceDir,
|
|
434
|
+
targetBaseDir: targetDir,
|
|
435
|
+
source: standardSource,
|
|
436
|
+
})).rejects.toThrow(AgentInstallError);
|
|
437
|
+
try {
|
|
438
|
+
await installAgentFiles({
|
|
439
|
+
sourceDir,
|
|
440
|
+
targetBaseDir: targetDir,
|
|
441
|
+
source: standardSource,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
const error = err;
|
|
446
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
447
|
+
expect(error.message).toContain("Invalid YAML syntax");
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
it("errors on empty agent.yaml", async () => {
|
|
451
|
+
await writeFile(join(sourceDir, "agent.yaml"), "");
|
|
452
|
+
await expect(installAgentFiles({
|
|
453
|
+
sourceDir,
|
|
454
|
+
targetBaseDir: targetDir,
|
|
455
|
+
source: standardSource,
|
|
456
|
+
})).rejects.toThrow(AgentInstallError);
|
|
457
|
+
try {
|
|
458
|
+
await installAgentFiles({
|
|
459
|
+
sourceDir,
|
|
460
|
+
targetBaseDir: targetDir,
|
|
461
|
+
source: standardSource,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
const error = err;
|
|
466
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
it("errors on agent.yaml without name field", async () => {
|
|
470
|
+
const noNameYaml = `
|
|
471
|
+
description: An agent without a name
|
|
472
|
+
runtime: cli
|
|
473
|
+
`;
|
|
474
|
+
await writeFile(join(sourceDir, "agent.yaml"), noNameYaml);
|
|
475
|
+
await expect(installAgentFiles({
|
|
476
|
+
sourceDir,
|
|
477
|
+
targetBaseDir: targetDir,
|
|
478
|
+
source: standardSource,
|
|
479
|
+
})).rejects.toThrow(AgentInstallError);
|
|
480
|
+
try {
|
|
481
|
+
await installAgentFiles({
|
|
482
|
+
sourceDir,
|
|
483
|
+
targetBaseDir: targetDir,
|
|
484
|
+
source: standardSource,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
const error = err;
|
|
489
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
490
|
+
expect(error.message).toContain("name");
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
it("errors on agent.yaml with empty name", async () => {
|
|
494
|
+
const emptyNameYaml = `
|
|
495
|
+
name: ""
|
|
496
|
+
runtime: cli
|
|
497
|
+
`;
|
|
498
|
+
await writeFile(join(sourceDir, "agent.yaml"), emptyNameYaml);
|
|
499
|
+
await expect(installAgentFiles({
|
|
500
|
+
sourceDir,
|
|
501
|
+
targetBaseDir: targetDir,
|
|
502
|
+
source: standardSource,
|
|
503
|
+
})).rejects.toThrow(AgentInstallError);
|
|
504
|
+
try {
|
|
505
|
+
await installAgentFiles({
|
|
506
|
+
sourceDir,
|
|
507
|
+
targetBaseDir: targetDir,
|
|
508
|
+
source: standardSource,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
const error = err;
|
|
513
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
it("errors on agent.yaml with non-string name", async () => {
|
|
517
|
+
const nonStringNameYaml = `
|
|
518
|
+
name: 123
|
|
519
|
+
runtime: cli
|
|
520
|
+
`;
|
|
521
|
+
await writeFile(join(sourceDir, "agent.yaml"), nonStringNameYaml);
|
|
522
|
+
await expect(installAgentFiles({
|
|
523
|
+
sourceDir,
|
|
524
|
+
targetBaseDir: targetDir,
|
|
525
|
+
source: standardSource,
|
|
526
|
+
})).rejects.toThrow(AgentInstallError);
|
|
527
|
+
try {
|
|
528
|
+
await installAgentFiles({
|
|
529
|
+
sourceDir,
|
|
530
|
+
targetBaseDir: targetDir,
|
|
531
|
+
source: standardSource,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
const error = err;
|
|
536
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
it("errors on agent.yaml that is just a string", async () => {
|
|
540
|
+
await writeFile(join(sourceDir, "agent.yaml"), "just a string");
|
|
541
|
+
await expect(installAgentFiles({
|
|
542
|
+
sourceDir,
|
|
543
|
+
targetBaseDir: targetDir,
|
|
544
|
+
source: standardSource,
|
|
545
|
+
})).rejects.toThrow(AgentInstallError);
|
|
546
|
+
try {
|
|
547
|
+
await installAgentFiles({
|
|
548
|
+
sourceDir,
|
|
549
|
+
targetBaseDir: targetDir,
|
|
550
|
+
source: standardSource,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
catch (err) {
|
|
554
|
+
const error = err;
|
|
555
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
it("errors on agent.yaml with only comments", async () => {
|
|
559
|
+
await writeFile(join(sourceDir, "agent.yaml"), "# Just a comment\n# Another comment");
|
|
560
|
+
await expect(installAgentFiles({
|
|
561
|
+
sourceDir,
|
|
562
|
+
targetBaseDir: targetDir,
|
|
563
|
+
source: standardSource,
|
|
564
|
+
})).rejects.toThrow(AgentInstallError);
|
|
565
|
+
try {
|
|
566
|
+
await installAgentFiles({
|
|
567
|
+
sourceDir,
|
|
568
|
+
targetBaseDir: targetDir,
|
|
569
|
+
source: standardSource,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
const error = err;
|
|
574
|
+
expect(error.code).toBe(INVALID_AGENT_YAML);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
// ===========================================================================
|
|
579
|
+
// Edge Cases
|
|
580
|
+
// ===========================================================================
|
|
581
|
+
describe("edge cases", () => {
|
|
582
|
+
it("handles source with both .git and node_modules", async () => {
|
|
583
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
584
|
+
await mkdir(join(sourceDir, ".git", "objects"), { recursive: true });
|
|
585
|
+
await mkdir(join(sourceDir, "node_modules", "pkg"), { recursive: true });
|
|
586
|
+
await mkdir(join(sourceDir, "src"), { recursive: true });
|
|
587
|
+
await writeFile(join(sourceDir, ".git", "objects", "abc123"), "blob");
|
|
588
|
+
await writeFile(join(sourceDir, "node_modules", "pkg", "index.js"), "code");
|
|
589
|
+
await writeFile(join(sourceDir, "src", "main.ts"), "export default {}");
|
|
590
|
+
const result = await installAgentFiles({
|
|
591
|
+
sourceDir,
|
|
592
|
+
targetBaseDir: targetDir,
|
|
593
|
+
source: standardSource,
|
|
594
|
+
});
|
|
595
|
+
// Excluded directories should not exist
|
|
596
|
+
expect(await pathExists(join(result.installPath, ".git"))).toBe(false);
|
|
597
|
+
expect(await pathExists(join(result.installPath, "node_modules"))).toBe(false);
|
|
598
|
+
// Other files should exist
|
|
599
|
+
expect(await pathExists(join(result.installPath, "agent.yaml"))).toBe(true);
|
|
600
|
+
expect(await pathExists(join(result.installPath, "src", "main.ts"))).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
it("handles deeply nested excluded directories", async () => {
|
|
603
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
604
|
+
await mkdir(join(sourceDir, "project", ".git", "hooks"), { recursive: true });
|
|
605
|
+
await writeFile(join(sourceDir, "project", ".git", "hooks", "pre-commit"), "#!/bin/sh");
|
|
606
|
+
await mkdir(join(sourceDir, "project", "lib"), { recursive: true });
|
|
607
|
+
await writeFile(join(sourceDir, "project", "lib", "utils.ts"), "code");
|
|
608
|
+
const result = await installAgentFiles({
|
|
609
|
+
sourceDir,
|
|
610
|
+
targetBaseDir: targetDir,
|
|
611
|
+
source: standardSource,
|
|
612
|
+
});
|
|
613
|
+
// .git in nested project should still be excluded
|
|
614
|
+
expect(await pathExists(join(result.installPath, "project", ".git"))).toBe(false);
|
|
615
|
+
// Other nested files should exist
|
|
616
|
+
expect(await pathExists(join(result.installPath, "project", "lib", "utils.ts"))).toBe(true);
|
|
617
|
+
});
|
|
618
|
+
it("preserves file contents after copy", async () => {
|
|
619
|
+
const yamlContent = `
|
|
620
|
+
name: test-agent
|
|
621
|
+
description: "A test agent for content verification"
|
|
622
|
+
runtime: cli
|
|
623
|
+
`;
|
|
624
|
+
const readmeContent = "# Test Agent\n\nThis is the readme content.";
|
|
625
|
+
const codeContent = 'export const VERSION = "1.0.0";';
|
|
626
|
+
await writeFile(join(sourceDir, "agent.yaml"), yamlContent);
|
|
627
|
+
await writeFile(join(sourceDir, "README.md"), readmeContent);
|
|
628
|
+
await mkdir(join(sourceDir, "src"), { recursive: true });
|
|
629
|
+
await writeFile(join(sourceDir, "src", "index.ts"), codeContent);
|
|
630
|
+
const result = await installAgentFiles({
|
|
631
|
+
sourceDir,
|
|
632
|
+
targetBaseDir: targetDir,
|
|
633
|
+
source: standardSource,
|
|
634
|
+
});
|
|
635
|
+
// Verify content is preserved
|
|
636
|
+
const copiedYaml = await readFile(join(result.installPath, "agent.yaml"), "utf-8");
|
|
637
|
+
const copiedReadme = await readFile(join(result.installPath, "README.md"), "utf-8");
|
|
638
|
+
const copiedCode = await readFile(join(result.installPath, "src", "index.ts"), "utf-8");
|
|
639
|
+
expect(copiedYaml).toBe(yamlContent);
|
|
640
|
+
expect(copiedReadme).toBe(readmeContent);
|
|
641
|
+
expect(copiedCode).toBe(codeContent);
|
|
642
|
+
});
|
|
643
|
+
it("handles agent name that is just numbers", async () => {
|
|
644
|
+
const numbersNameYaml = `
|
|
645
|
+
name: "123456"
|
|
646
|
+
runtime: cli
|
|
647
|
+
`;
|
|
648
|
+
await writeFile(join(sourceDir, "agent.yaml"), numbersNameYaml);
|
|
649
|
+
const result = await installAgentFiles({
|
|
650
|
+
sourceDir,
|
|
651
|
+
targetBaseDir: targetDir,
|
|
652
|
+
source: standardSource,
|
|
653
|
+
});
|
|
654
|
+
expect(result.agentName).toBe("123456");
|
|
655
|
+
expect(result.installPath).toBe(join(targetDir, "agents", "123456"));
|
|
656
|
+
});
|
|
657
|
+
it("handles whitespace in agent.yaml name (after trim)", async () => {
|
|
658
|
+
// YAML will preserve the string including whitespace
|
|
659
|
+
const whitespaceNameYaml = `
|
|
660
|
+
name: " spaced "
|
|
661
|
+
runtime: cli
|
|
662
|
+
`;
|
|
663
|
+
await writeFile(join(sourceDir, "agent.yaml"), whitespaceNameYaml);
|
|
664
|
+
// The name with spaces should fail validation
|
|
665
|
+
await expect(installAgentFiles({
|
|
666
|
+
sourceDir,
|
|
667
|
+
targetBaseDir: targetDir,
|
|
668
|
+
source: standardSource,
|
|
669
|
+
})).rejects.toThrow(AgentInstallError);
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
// ===========================================================================
|
|
673
|
+
// Metadata Content Tests
|
|
674
|
+
// ===========================================================================
|
|
675
|
+
describe("metadata content", () => {
|
|
676
|
+
it("includes all source fields in metadata", async () => {
|
|
677
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
678
|
+
const fullSource = {
|
|
679
|
+
type: "github",
|
|
680
|
+
url: "https://github.com/org/repo",
|
|
681
|
+
ref: "v2.0.0",
|
|
682
|
+
version: "2.0.0",
|
|
683
|
+
};
|
|
684
|
+
const result = await installAgentFiles({
|
|
685
|
+
sourceDir,
|
|
686
|
+
targetBaseDir: targetDir,
|
|
687
|
+
source: fullSource,
|
|
688
|
+
});
|
|
689
|
+
const metadata = await readJsonFile(join(result.installPath, "metadata.json"));
|
|
690
|
+
expect(metadata.source.type).toBe("github");
|
|
691
|
+
expect(metadata.source.url).toBe("https://github.com/org/repo");
|
|
692
|
+
expect(metadata.source.ref).toBe("v2.0.0");
|
|
693
|
+
expect(metadata.source.version).toBe("2.0.0");
|
|
694
|
+
});
|
|
695
|
+
it("generates valid ISO 8601 timestamp", async () => {
|
|
696
|
+
await writeFile(join(sourceDir, "agent.yaml"), minimalAgentYaml);
|
|
697
|
+
const result = await installAgentFiles({
|
|
698
|
+
sourceDir,
|
|
699
|
+
targetBaseDir: targetDir,
|
|
700
|
+
source: standardSource,
|
|
701
|
+
});
|
|
702
|
+
const metadata = await readJsonFile(join(result.installPath, "metadata.json"));
|
|
703
|
+
// Check it's a valid ISO 8601 timestamp
|
|
704
|
+
const timestamp = new Date(metadata.installed_at);
|
|
705
|
+
expect(timestamp).toBeInstanceOf(Date);
|
|
706
|
+
expect(Number.isNaN(timestamp.getTime())).toBe(false);
|
|
707
|
+
// Check it's recent (within last minute)
|
|
708
|
+
const now = Date.now();
|
|
709
|
+
const installedTime = timestamp.getTime();
|
|
710
|
+
expect(now - installedTime).toBeLessThan(60000);
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
//# sourceMappingURL=file-installer.test.js.map
|