busy-cli 0.1.2
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 +129 -0
- package/dist/builders/context.d.ts +50 -0
- package/dist/builders/context.d.ts.map +1 -0
- package/dist/builders/context.js +190 -0
- package/dist/cache/index.d.ts +100 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +270 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +463 -0
- package/dist/commands/package.d.ts +96 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +361 -0
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +102 -0
- package/dist/package/manifest.d.ts +59 -0
- package/dist/package/manifest.d.ts.map +1 -0
- package/dist/package/manifest.js +265 -0
- package/dist/parser.d.ts +28 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +220 -0
- package/dist/parsers/frontmatter.d.ts +14 -0
- package/dist/parsers/frontmatter.d.ts.map +1 -0
- package/dist/parsers/frontmatter.js +110 -0
- package/dist/parsers/imports.d.ts +48 -0
- package/dist/parsers/imports.d.ts.map +1 -0
- package/dist/parsers/imports.js +147 -0
- package/dist/parsers/links.d.ts +12 -0
- package/dist/parsers/links.d.ts.map +1 -0
- package/dist/parsers/links.js +79 -0
- package/dist/parsers/localdefs.d.ts +6 -0
- package/dist/parsers/localdefs.d.ts.map +1 -0
- package/dist/parsers/localdefs.js +132 -0
- package/dist/parsers/operations.d.ts +32 -0
- package/dist/parsers/operations.d.ts.map +1 -0
- package/dist/parsers/operations.js +313 -0
- package/dist/parsers/sections.d.ts +15 -0
- package/dist/parsers/sections.d.ts.map +1 -0
- package/dist/parsers/sections.js +173 -0
- package/dist/parsers/tools.d.ts +30 -0
- package/dist/parsers/tools.d.ts.map +1 -0
- package/dist/parsers/tools.js +178 -0
- package/dist/parsers/triggers.d.ts +35 -0
- package/dist/parsers/triggers.d.ts.map +1 -0
- package/dist/parsers/triggers.js +219 -0
- package/dist/providers/base.d.ts +60 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +34 -0
- package/dist/providers/github.d.ts +18 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/gitlab.d.ts +18 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +101 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/local.d.ts +31 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +116 -0
- package/dist/providers/url.d.ts +16 -0
- package/dist/providers/url.d.ts.map +1 -0
- package/dist/providers/url.js +45 -0
- package/dist/registry/index.d.ts +99 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +320 -0
- package/dist/types/schema.d.ts +3259 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +258 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/slugify.d.ts +14 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +28 -0
- package/package.json +61 -0
- package/src/__tests__/cache.test.ts +393 -0
- package/src/__tests__/cli-package.test.ts +667 -0
- package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
- package/src/__tests__/fixtures/concept.busy.md +30 -0
- package/src/__tests__/fixtures/document.busy.md +44 -0
- package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
- package/src/__tests__/fixtures/tool-document.busy.md +71 -0
- package/src/__tests__/fixtures/tool.busy.md +54 -0
- package/src/__tests__/imports.test.ts +244 -0
- package/src/__tests__/integration.test.ts +432 -0
- package/src/__tests__/operations.test.ts +408 -0
- package/src/__tests__/package-manifest.test.ts +455 -0
- package/src/__tests__/providers.test.ts +672 -0
- package/src/__tests__/registry.test.ts +402 -0
- package/src/__tests__/schema.test.ts +467 -0
- package/src/__tests__/tools.test.ts +376 -0
- package/src/__tests__/triggers.test.ts +312 -0
- package/src/builders/context.ts +294 -0
- package/src/cache/index.ts +312 -0
- package/src/cli/index.ts +514 -0
- package/src/commands/package.ts +392 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +474 -0
- package/src/merge.ts +126 -0
- package/src/package/manifest.ts +349 -0
- package/src/parser.ts +278 -0
- package/src/parsers/frontmatter.ts +135 -0
- package/src/parsers/imports.ts +196 -0
- package/src/parsers/links.ts +108 -0
- package/src/parsers/localdefs.ts +166 -0
- package/src/parsers/operations.ts +404 -0
- package/src/parsers/sections.ts +230 -0
- package/src/parsers/tools.ts +215 -0
- package/src/parsers/triggers.ts +252 -0
- package/src/providers/base.ts +77 -0
- package/src/providers/github.ts +129 -0
- package/src/providers/gitlab.ts +121 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/local.ts +129 -0
- package/src/providers/url.ts +56 -0
- package/src/registry/index.ts +408 -0
- package/src/types/schema.ts +369 -0
- package/src/utils/logger.ts +25 -0
- package/src/utils/slugify.ts +31 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Manifest Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for package.busy.md manifest-based package installation.
|
|
5
|
+
* Packages are defined by their own package.busy.md that lists all documents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
parsePackageManifest,
|
|
15
|
+
PackageManifest,
|
|
16
|
+
PackageDocument,
|
|
17
|
+
fetchPackageFromManifest,
|
|
18
|
+
isPackageManifestUrl,
|
|
19
|
+
} from '../package/manifest.js';
|
|
20
|
+
import { initWorkspace, addPackage } from '../commands/package.js';
|
|
21
|
+
|
|
22
|
+
// Sample remote package.busy.md content
|
|
23
|
+
const SAMPLE_PACKAGE_MANIFEST = `---
|
|
24
|
+
Name: busy-v2
|
|
25
|
+
Type: Package
|
|
26
|
+
Version: v0.3.1
|
|
27
|
+
Description: BUSY document standard library
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Package Contents
|
|
31
|
+
|
|
32
|
+
## Core
|
|
33
|
+
|
|
34
|
+
Core document types and concepts.
|
|
35
|
+
|
|
36
|
+
- [Document](./core/document.busy.md) - Base document type
|
|
37
|
+
- [Operation](./core/operation.busy.md) - Operation definitions
|
|
38
|
+
- [Concept](./core/concept.busy.md) - Concept definitions
|
|
39
|
+
- [Checklist](./core/checklist.busy.md) - Checklist definitions
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
Tool definitions for runtime integration.
|
|
44
|
+
|
|
45
|
+
- [Event Tool](./toolbox/event-tool.busy.md) - Event emission
|
|
46
|
+
- [File Tool](./toolbox/file-tool.busy.md) - File operations
|
|
47
|
+
|
|
48
|
+
## Base
|
|
49
|
+
|
|
50
|
+
Base configurations and agents.
|
|
51
|
+
|
|
52
|
+
- [Busy Assistant](./base/busy-assistant.busy.md) - Default assistant
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
describe('parsePackageManifest', () => {
|
|
56
|
+
it('should parse package metadata from frontmatter', () => {
|
|
57
|
+
const manifest = parsePackageManifest(SAMPLE_PACKAGE_MANIFEST);
|
|
58
|
+
|
|
59
|
+
expect(manifest.name).toBe('busy-v2');
|
|
60
|
+
expect(manifest.type).toBe('Package');
|
|
61
|
+
expect(manifest.version).toBe('v0.3.1');
|
|
62
|
+
expect(manifest.description).toBe('BUSY document standard library');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should extract all document links', () => {
|
|
66
|
+
const manifest = parsePackageManifest(SAMPLE_PACKAGE_MANIFEST);
|
|
67
|
+
|
|
68
|
+
expect(manifest.documents.length).toBe(7);
|
|
69
|
+
|
|
70
|
+
const docNames = manifest.documents.map(d => d.name);
|
|
71
|
+
expect(docNames).toContain('Document');
|
|
72
|
+
expect(docNames).toContain('Operation');
|
|
73
|
+
expect(docNames).toContain('Event Tool');
|
|
74
|
+
expect(docNames).toContain('Busy Assistant');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should preserve relative paths', () => {
|
|
78
|
+
const manifest = parsePackageManifest(SAMPLE_PACKAGE_MANIFEST);
|
|
79
|
+
|
|
80
|
+
const doc = manifest.documents.find(d => d.name === 'Document');
|
|
81
|
+
expect(doc?.relativePath).toBe('./core/document.busy.md');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should categorize documents', () => {
|
|
85
|
+
const manifest = parsePackageManifest(SAMPLE_PACKAGE_MANIFEST);
|
|
86
|
+
|
|
87
|
+
const coreDoc = manifest.documents.find(d => d.name === 'Document');
|
|
88
|
+
expect(coreDoc?.category).toBe('Core');
|
|
89
|
+
|
|
90
|
+
const toolDoc = manifest.documents.find(d => d.name === 'Event Tool');
|
|
91
|
+
expect(toolDoc?.category).toBe('Tools');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle package with no documents', () => {
|
|
95
|
+
const emptyManifest = `---
|
|
96
|
+
Name: empty-package
|
|
97
|
+
Type: Package
|
|
98
|
+
Version: v1.0.0
|
|
99
|
+
Description: An empty package
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
# Package Contents
|
|
103
|
+
|
|
104
|
+
No documents yet.
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const manifest = parsePackageManifest(emptyManifest);
|
|
108
|
+
|
|
109
|
+
expect(manifest.name).toBe('empty-package');
|
|
110
|
+
expect(manifest.documents.length).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should handle anchors in document links', () => {
|
|
114
|
+
const manifestWithAnchors = `---
|
|
115
|
+
Name: test-package
|
|
116
|
+
Type: Package
|
|
117
|
+
Version: v1.0.0
|
|
118
|
+
Description: Test
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Package Contents
|
|
122
|
+
|
|
123
|
+
- [ExecutePrompt](./core/prompt.busy.md#executeprompt) - Execute a prompt
|
|
124
|
+
- [RunChecklist](./core/checklist.busy.md#runchecklist) - Run a checklist
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const manifest = parsePackageManifest(manifestWithAnchors);
|
|
128
|
+
|
|
129
|
+
expect(manifest.documents.length).toBe(2);
|
|
130
|
+
|
|
131
|
+
const execPrompt = manifest.documents.find(d => d.name === 'ExecutePrompt');
|
|
132
|
+
expect(execPrompt?.relativePath).toBe('./core/prompt.busy.md');
|
|
133
|
+
expect(execPrompt?.anchor).toBe('executeprompt');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should parse table-based format', () => {
|
|
137
|
+
const tableManifest = `---
|
|
138
|
+
Name: busy-v2
|
|
139
|
+
Type: Document
|
|
140
|
+
Version: v0.4.0
|
|
141
|
+
Description: Standard library
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
# Local Definitions
|
|
145
|
+
|
|
146
|
+
## Package Document
|
|
147
|
+
|
|
148
|
+
| Field | Required | Description |
|
|
149
|
+
|-------|----------|-------------|
|
|
150
|
+
| Path | Yes | Relative path |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
# Package Contents
|
|
155
|
+
|
|
156
|
+
## Core
|
|
157
|
+
|
|
158
|
+
### Document
|
|
159
|
+
|
|
160
|
+
| Field | Value |
|
|
161
|
+
|-------|-------|
|
|
162
|
+
| Path | ./core/document.busy.md |
|
|
163
|
+
| Type | Concept |
|
|
164
|
+
| Description | Base document type |
|
|
165
|
+
|
|
166
|
+
### Operation
|
|
167
|
+
|
|
168
|
+
| Field | Value |
|
|
169
|
+
|-------|-------|
|
|
170
|
+
| Path | ./core/operation.busy.md |
|
|
171
|
+
| Type | Concept |
|
|
172
|
+
| Description | Operation definitions |
|
|
173
|
+
|
|
174
|
+
## Tools
|
|
175
|
+
|
|
176
|
+
### Event Tool
|
|
177
|
+
|
|
178
|
+
| Field | Value |
|
|
179
|
+
|-------|-------|
|
|
180
|
+
| Path | ./toolbox/event-tool.busy.md |
|
|
181
|
+
| Type | Tool |
|
|
182
|
+
| Description | Emit events |
|
|
183
|
+
`;
|
|
184
|
+
|
|
185
|
+
const manifest = parsePackageManifest(tableManifest);
|
|
186
|
+
|
|
187
|
+
expect(manifest.name).toBe('busy-v2');
|
|
188
|
+
expect(manifest.type).toBe('Document');
|
|
189
|
+
expect(manifest.version).toBe('v0.4.0');
|
|
190
|
+
expect(manifest.documents.length).toBe(3);
|
|
191
|
+
|
|
192
|
+
const doc = manifest.documents.find(d => d.name === 'Document');
|
|
193
|
+
expect(doc?.relativePath).toBe('./core/document.busy.md');
|
|
194
|
+
expect(doc?.type).toBe('Concept');
|
|
195
|
+
expect(doc?.description).toBe('Base document type');
|
|
196
|
+
expect(doc?.category).toBe('Core');
|
|
197
|
+
|
|
198
|
+
const tool = manifest.documents.find(d => d.name === 'Event Tool');
|
|
199
|
+
expect(tool?.category).toBe('Tools');
|
|
200
|
+
expect(tool?.type).toBe('Tool');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should handle table format with anchors in Path', () => {
|
|
204
|
+
const tableWithAnchors = `---
|
|
205
|
+
Name: test
|
|
206
|
+
Type: Document
|
|
207
|
+
Version: v1.0.0
|
|
208
|
+
Description: Test
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# Package Contents
|
|
212
|
+
|
|
213
|
+
## Core
|
|
214
|
+
|
|
215
|
+
### ExecutePrompt
|
|
216
|
+
|
|
217
|
+
| Field | Value |
|
|
218
|
+
|-------|-------|
|
|
219
|
+
| Path | ./core/prompt.busy.md#executeprompt |
|
|
220
|
+
| Type | Operation |
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
const manifest = parsePackageManifest(tableWithAnchors);
|
|
224
|
+
|
|
225
|
+
expect(manifest.documents.length).toBe(1);
|
|
226
|
+
const doc = manifest.documents[0];
|
|
227
|
+
expect(doc.name).toBe('ExecutePrompt');
|
|
228
|
+
expect(doc.relativePath).toBe('./core/prompt.busy.md');
|
|
229
|
+
expect(doc.anchor).toBe('executeprompt');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('isPackageManifestUrl', () => {
|
|
234
|
+
it('should detect package.busy.md URLs', () => {
|
|
235
|
+
expect(isPackageManifestUrl('https://github.com/org/repo/blob/main/package.busy.md')).toBe(true);
|
|
236
|
+
expect(isPackageManifestUrl('https://github.com/org/repo/blob/main/pkg/package.busy.md')).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should not match regular file URLs', () => {
|
|
240
|
+
expect(isPackageManifestUrl('https://github.com/org/repo/blob/main/file.md')).toBe(false);
|
|
241
|
+
expect(isPackageManifestUrl('https://github.com/org/repo/blob/main/package.md')).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('fetchPackageFromManifest', () => {
|
|
246
|
+
let tempDir: string;
|
|
247
|
+
let originalFetch: typeof global.fetch;
|
|
248
|
+
|
|
249
|
+
beforeEach(async () => {
|
|
250
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-manifest-test-'));
|
|
251
|
+
await initWorkspace(tempDir);
|
|
252
|
+
originalFetch = global.fetch;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
afterEach(async () => {
|
|
256
|
+
global.fetch = originalFetch;
|
|
257
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should fetch package manifest and all documents', async () => {
|
|
261
|
+
// Mock responses for manifest and documents
|
|
262
|
+
const mockResponses: Record<string, string> = {
|
|
263
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/busy-v2/package.busy.md': `---
|
|
264
|
+
Name: busy-v2
|
|
265
|
+
Type: Package
|
|
266
|
+
Version: v1.0.0
|
|
267
|
+
Description: Test package
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
# Package Contents
|
|
271
|
+
|
|
272
|
+
## Core
|
|
273
|
+
|
|
274
|
+
- [Document](./core/document.busy.md) - Document type
|
|
275
|
+
- [Operation](./core/operation.busy.md) - Operation type
|
|
276
|
+
`,
|
|
277
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/busy-v2/core/document.busy.md': `---
|
|
278
|
+
Name: Document
|
|
279
|
+
Type: Concept
|
|
280
|
+
Description: Base document type
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
# Document
|
|
284
|
+
|
|
285
|
+
A document is...
|
|
286
|
+
`,
|
|
287
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/busy-v2/core/operation.busy.md': `---
|
|
288
|
+
Name: Operation
|
|
289
|
+
Type: Concept
|
|
290
|
+
Description: Operation definition
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
# Operation
|
|
294
|
+
|
|
295
|
+
An operation is...
|
|
296
|
+
`,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
300
|
+
const content = mockResponses[url];
|
|
301
|
+
if (content) {
|
|
302
|
+
return { ok: true, text: () => Promise.resolve(content) };
|
|
303
|
+
}
|
|
304
|
+
return { ok: false, status: 404, statusText: 'Not Found' };
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const manifestUrl = 'https://github.com/org/repo/blob/v1.0.0/busy-v2/package.busy.md';
|
|
308
|
+
const result = await fetchPackageFromManifest(tempDir, manifestUrl);
|
|
309
|
+
|
|
310
|
+
expect(result.name).toBe('busy-v2');
|
|
311
|
+
expect(result.version).toBe('v1.0.0');
|
|
312
|
+
expect(result.documents.length).toBe(2);
|
|
313
|
+
expect(result.cached).toBe('.libraries/busy-v2');
|
|
314
|
+
|
|
315
|
+
// Verify files were cached
|
|
316
|
+
const docPath = path.join(tempDir, '.libraries', 'busy-v2', 'core', 'document.busy.md');
|
|
317
|
+
const docExists = await fs.stat(docPath).then(() => true).catch(() => false);
|
|
318
|
+
expect(docExists).toBe(true);
|
|
319
|
+
|
|
320
|
+
const opPath = path.join(tempDir, '.libraries', 'busy-v2', 'core', 'operation.busy.md');
|
|
321
|
+
const opExists = await fs.stat(opPath).then(() => true).catch(() => false);
|
|
322
|
+
expect(opExists).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should add package to local registry', async () => {
|
|
326
|
+
const mockResponses: Record<string, string> = {
|
|
327
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/pkg/package.busy.md': `---
|
|
328
|
+
Name: test-pkg
|
|
329
|
+
Type: Package
|
|
330
|
+
Version: v1.0.0
|
|
331
|
+
Description: Test
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
# Package Contents
|
|
335
|
+
|
|
336
|
+
- [File](./file.busy.md) - A file
|
|
337
|
+
`,
|
|
338
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/pkg/file.busy.md': '# File\n\nContent',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
342
|
+
const content = mockResponses[url];
|
|
343
|
+
if (content) {
|
|
344
|
+
return { ok: true, text: () => Promise.resolve(content) };
|
|
345
|
+
}
|
|
346
|
+
return { ok: false, status: 404, statusText: 'Not Found' };
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const manifestUrl = 'https://github.com/org/repo/blob/v1.0.0/pkg/package.busy.md';
|
|
350
|
+
await fetchPackageFromManifest(tempDir, manifestUrl);
|
|
351
|
+
|
|
352
|
+
// Check local package.busy.md was updated
|
|
353
|
+
const localRegistry = await fs.readFile(path.join(tempDir, 'package.busy.md'), 'utf-8');
|
|
354
|
+
expect(localRegistry).toContain('test-pkg');
|
|
355
|
+
expect(localRegistry).toContain('v1.0.0');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should handle nested directory structures', async () => {
|
|
359
|
+
const mockResponses: Record<string, string> = {
|
|
360
|
+
'https://raw.githubusercontent.com/org/repo/main/package.busy.md': `---
|
|
361
|
+
Name: nested-pkg
|
|
362
|
+
Type: Package
|
|
363
|
+
Version: v2.0.0
|
|
364
|
+
Description: Nested structure
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
# Package Contents
|
|
368
|
+
|
|
369
|
+
- [Deep File](./a/b/c/deep.busy.md) - Deeply nested
|
|
370
|
+
`,
|
|
371
|
+
'https://raw.githubusercontent.com/org/repo/main/a/b/c/deep.busy.md': '# Deep\n\nNested content',
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
375
|
+
const content = mockResponses[url];
|
|
376
|
+
if (content) {
|
|
377
|
+
return { ok: true, text: () => Promise.resolve(content) };
|
|
378
|
+
}
|
|
379
|
+
return { ok: false, status: 404, statusText: 'Not Found' };
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const manifestUrl = 'https://github.com/org/repo/blob/main/package.busy.md';
|
|
383
|
+
const result = await fetchPackageFromManifest(tempDir, manifestUrl);
|
|
384
|
+
|
|
385
|
+
// Verify nested file was cached
|
|
386
|
+
const deepPath = path.join(tempDir, '.libraries', 'nested-pkg', 'a', 'b', 'c', 'deep.busy.md');
|
|
387
|
+
const deepExists = await fs.stat(deepPath).then(() => true).catch(() => false);
|
|
388
|
+
expect(deepExists).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('addPackage with manifest', () => {
|
|
393
|
+
let tempDir: string;
|
|
394
|
+
let originalFetch: typeof global.fetch;
|
|
395
|
+
|
|
396
|
+
beforeEach(async () => {
|
|
397
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-add-manifest-test-'));
|
|
398
|
+
await initWorkspace(tempDir);
|
|
399
|
+
originalFetch = global.fetch;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
afterEach(async () => {
|
|
403
|
+
global.fetch = originalFetch;
|
|
404
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should detect and handle package.busy.md URLs', async () => {
|
|
408
|
+
const mockResponses: Record<string, string> = {
|
|
409
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/package.busy.md': `---
|
|
410
|
+
Name: my-package
|
|
411
|
+
Type: Package
|
|
412
|
+
Version: v1.0.0
|
|
413
|
+
Description: My package
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
# Package Contents
|
|
417
|
+
|
|
418
|
+
- [Doc](./doc.busy.md) - A document
|
|
419
|
+
`,
|
|
420
|
+
'https://raw.githubusercontent.com/org/repo/v1.0.0/doc.busy.md': '# Doc\n\nContent',
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
424
|
+
const content = mockResponses[url];
|
|
425
|
+
if (content) {
|
|
426
|
+
return { ok: true, text: () => Promise.resolve(content) };
|
|
427
|
+
}
|
|
428
|
+
return { ok: false, status: 404, statusText: 'Not Found' };
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// addPackage should auto-detect package.busy.md and use manifest flow
|
|
432
|
+
const result = await addPackage(tempDir, 'https://github.com/org/repo/blob/v1.0.0/package.busy.md');
|
|
433
|
+
|
|
434
|
+
expect(result.id).toBe('my-package');
|
|
435
|
+
expect(result.version).toBe('v1.0.0');
|
|
436
|
+
|
|
437
|
+
// Verify the package was cached as a directory
|
|
438
|
+
const docPath = path.join(tempDir, '.libraries', 'my-package', 'doc.busy.md');
|
|
439
|
+
const exists = await fs.stat(docPath).then(() => true).catch(() => false);
|
|
440
|
+
expect(exists).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should still handle single file URLs', async () => {
|
|
444
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
445
|
+
ok: true,
|
|
446
|
+
text: () => Promise.resolve('# Single File\n\nContent'),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Non-manifest URL should work as before
|
|
450
|
+
const result = await addPackage(tempDir, 'https://github.com/org/repo/blob/v1.0.0/file.md');
|
|
451
|
+
|
|
452
|
+
expect(result.id).toBe('file');
|
|
453
|
+
expect(result.cached).toContain('file.md');
|
|
454
|
+
});
|
|
455
|
+
});
|