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,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Package Management Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for busy init, check, and package commands.
|
|
5
|
+
* TDD approach for package manager implementation.
|
|
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
|
+
// We'll implement these commands
|
|
14
|
+
import {
|
|
15
|
+
initWorkspace,
|
|
16
|
+
checkWorkspace,
|
|
17
|
+
addPackage,
|
|
18
|
+
removePackage,
|
|
19
|
+
upgradePackage,
|
|
20
|
+
listPackages,
|
|
21
|
+
getPackageInfo,
|
|
22
|
+
} from '../commands/package.js';
|
|
23
|
+
|
|
24
|
+
describe('initWorkspace', () => {
|
|
25
|
+
let tempDir: string;
|
|
26
|
+
|
|
27
|
+
beforeEach(async () => {
|
|
28
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should create package.busy.md', async () => {
|
|
36
|
+
const result = await initWorkspace(tempDir);
|
|
37
|
+
|
|
38
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
39
|
+
const exists = await fs.stat(packagePath).then(() => true).catch(() => false);
|
|
40
|
+
|
|
41
|
+
expect(exists).toBe(true);
|
|
42
|
+
expect(result.created).toContain('package.busy.md');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should create .libraries directory', async () => {
|
|
46
|
+
const result = await initWorkspace(tempDir);
|
|
47
|
+
|
|
48
|
+
const librariesPath = path.join(tempDir, '.libraries');
|
|
49
|
+
const exists = await fs.stat(librariesPath).then(() => true).catch(() => false);
|
|
50
|
+
|
|
51
|
+
expect(exists).toBe(true);
|
|
52
|
+
expect(result.created).toContain('.libraries');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should not overwrite existing package.busy.md', async () => {
|
|
56
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
57
|
+
await fs.writeFile(packagePath, 'existing content');
|
|
58
|
+
|
|
59
|
+
const result = await initWorkspace(tempDir);
|
|
60
|
+
|
|
61
|
+
const content = await fs.readFile(packagePath, 'utf-8');
|
|
62
|
+
expect(content).toBe('existing content');
|
|
63
|
+
expect(result.skipped).toContain('package.busy.md');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return workspace info', async () => {
|
|
67
|
+
const result = await initWorkspace(tempDir);
|
|
68
|
+
|
|
69
|
+
expect(result.workspaceRoot).toBe(tempDir);
|
|
70
|
+
expect(result.initialized).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('checkWorkspace', () => {
|
|
75
|
+
let tempDir: string;
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
79
|
+
await initWorkspace(tempDir);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(async () => {
|
|
83
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should pass for empty workspace', async () => {
|
|
87
|
+
const result = await checkWorkspace(tempDir);
|
|
88
|
+
|
|
89
|
+
expect(result.valid).toBe(true);
|
|
90
|
+
expect(result.errors).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should report missing cached files', async () => {
|
|
94
|
+
// Add a package entry but don't create the cached file
|
|
95
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
96
|
+
const content = `---
|
|
97
|
+
Name: package
|
|
98
|
+
Type: Document
|
|
99
|
+
Description: Test
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
# Definitions
|
|
103
|
+
|
|
104
|
+
## Package Entry
|
|
105
|
+
|
|
106
|
+
| Field | Required | Description |
|
|
107
|
+
|-------|----------|-------------|
|
|
108
|
+
| Source | Yes | URL |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
# Package Registry
|
|
113
|
+
|
|
114
|
+
## Packages
|
|
115
|
+
|
|
116
|
+
### test-package
|
|
117
|
+
|
|
118
|
+
Test package
|
|
119
|
+
|
|
120
|
+
| Field | Value |
|
|
121
|
+
|-------|-------|
|
|
122
|
+
| Source | https://example.com/file.md |
|
|
123
|
+
| Provider | url |
|
|
124
|
+
| Cached | .libraries/file.md |
|
|
125
|
+
| Version | v1.0.0 |
|
|
126
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
await fs.writeFile(packagePath, content);
|
|
130
|
+
|
|
131
|
+
const result = await checkWorkspace(tempDir);
|
|
132
|
+
|
|
133
|
+
expect(result.valid).toBe(false);
|
|
134
|
+
expect(result.errors.some(e => e.includes('test-package'))).toBe(true);
|
|
135
|
+
expect(result.errors.some(e => e.includes('cached file'))).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should pass when cached files exist', async () => {
|
|
139
|
+
// Add a package entry with a cached file
|
|
140
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
141
|
+
const content = `---
|
|
142
|
+
Name: package
|
|
143
|
+
Type: Document
|
|
144
|
+
Description: Test
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
# Definitions
|
|
148
|
+
|
|
149
|
+
## Package Entry
|
|
150
|
+
|
|
151
|
+
| Field | Required | Description |
|
|
152
|
+
|-------|----------|-------------|
|
|
153
|
+
| Source | Yes | URL |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
# Package Registry
|
|
158
|
+
|
|
159
|
+
## Packages
|
|
160
|
+
|
|
161
|
+
### test-package
|
|
162
|
+
|
|
163
|
+
Test package
|
|
164
|
+
|
|
165
|
+
| Field | Value |
|
|
166
|
+
|-------|-------|
|
|
167
|
+
| Source | https://example.com/file.md |
|
|
168
|
+
| Provider | url |
|
|
169
|
+
| Cached | .libraries/file.md |
|
|
170
|
+
| Version | v1.0.0 |
|
|
171
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
await fs.writeFile(packagePath, content);
|
|
175
|
+
|
|
176
|
+
// Create the cached file
|
|
177
|
+
const cachePath = path.join(tempDir, '.libraries', 'file.md');
|
|
178
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
179
|
+
await fs.writeFile(cachePath, '# Cached content');
|
|
180
|
+
|
|
181
|
+
const result = await checkWorkspace(tempDir);
|
|
182
|
+
|
|
183
|
+
expect(result.valid).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should verify integrity when specified', async () => {
|
|
187
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
188
|
+
const content = `---
|
|
189
|
+
Name: package
|
|
190
|
+
Type: Document
|
|
191
|
+
Description: Test
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
# Definitions
|
|
195
|
+
|
|
196
|
+
## Package Entry
|
|
197
|
+
|
|
198
|
+
| Field | Required | Description |
|
|
199
|
+
|-------|----------|-------------|
|
|
200
|
+
| Source | Yes | URL |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
# Package Registry
|
|
205
|
+
|
|
206
|
+
## Packages
|
|
207
|
+
|
|
208
|
+
### test-package
|
|
209
|
+
|
|
210
|
+
Test package
|
|
211
|
+
|
|
212
|
+
| Field | Value |
|
|
213
|
+
|-------|-------|
|
|
214
|
+
| Source | https://example.com/file.md |
|
|
215
|
+
| Provider | url |
|
|
216
|
+
| Cached | .libraries/file.md |
|
|
217
|
+
| Version | v1.0.0 |
|
|
218
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
219
|
+
| Integrity | sha256:0000000000000000000000000000000000000000000000000000000000000000 |
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
await fs.writeFile(packagePath, content);
|
|
223
|
+
|
|
224
|
+
// Create cached file with different content (integrity mismatch)
|
|
225
|
+
const cachePath = path.join(tempDir, '.libraries', 'file.md');
|
|
226
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
227
|
+
await fs.writeFile(cachePath, '# Different content');
|
|
228
|
+
|
|
229
|
+
const result = await checkWorkspace(tempDir);
|
|
230
|
+
|
|
231
|
+
expect(result.warnings.some(w => w.includes('integrity'))).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should throw for uninitialized workspace', async () => {
|
|
235
|
+
const uninitDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-uninit-'));
|
|
236
|
+
|
|
237
|
+
await expect(checkWorkspace(uninitDir)).rejects.toThrow();
|
|
238
|
+
|
|
239
|
+
await fs.rm(uninitDir, { recursive: true, force: true });
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('addPackage', () => {
|
|
244
|
+
let tempDir: string;
|
|
245
|
+
let originalFetch: typeof global.fetch;
|
|
246
|
+
|
|
247
|
+
beforeEach(async () => {
|
|
248
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
249
|
+
await initWorkspace(tempDir);
|
|
250
|
+
originalFetch = global.fetch;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterEach(async () => {
|
|
254
|
+
global.fetch = originalFetch;
|
|
255
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should add package from GitHub URL', async () => {
|
|
259
|
+
const mockContent = '# Test Content\n\nThis is test content.';
|
|
260
|
+
|
|
261
|
+
// Mock fetch for both raw content and API calls
|
|
262
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
263
|
+
if (url.includes('raw.githubusercontent.com')) {
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
text: () => Promise.resolve(mockContent),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
271
|
+
json: () => Promise.resolve([]),
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const url = 'https://github.com/org/repo/blob/v1.0.0/path/file.md';
|
|
276
|
+
const result = await addPackage(tempDir, url);
|
|
277
|
+
|
|
278
|
+
expect(result.id).toBe('file');
|
|
279
|
+
expect(result.version).toBe('v1.0.0');
|
|
280
|
+
expect(result.provider).toBe('github');
|
|
281
|
+
|
|
282
|
+
// Verify cached file exists
|
|
283
|
+
const cachePath = path.join(tempDir, result.cached);
|
|
284
|
+
const exists = await fs.stat(cachePath).then(() => true).catch(() => false);
|
|
285
|
+
expect(exists).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should derive entry ID from anchor', async () => {
|
|
289
|
+
const mockContent = '# Content';
|
|
290
|
+
|
|
291
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
292
|
+
ok: true,
|
|
293
|
+
text: () => Promise.resolve(mockContent),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const url = 'https://github.com/org/repo/blob/main/file.md#my-section';
|
|
297
|
+
const result = await addPackage(tempDir, url);
|
|
298
|
+
|
|
299
|
+
expect(result.id).toBe('my-section');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should update package.busy.md', async () => {
|
|
303
|
+
const mockContent = '# Content';
|
|
304
|
+
|
|
305
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
306
|
+
ok: true,
|
|
307
|
+
text: () => Promise.resolve(mockContent),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const url = 'https://github.com/org/repo/blob/v1.0.0/file.md';
|
|
311
|
+
await addPackage(tempDir, url);
|
|
312
|
+
|
|
313
|
+
// Read package.busy.md and verify entry
|
|
314
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
315
|
+
const content = await fs.readFile(packagePath, 'utf-8');
|
|
316
|
+
|
|
317
|
+
expect(content).toContain('### file');
|
|
318
|
+
expect(content).toContain('v1.0.0');
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe('removePackage', () => {
|
|
323
|
+
let tempDir: string;
|
|
324
|
+
|
|
325
|
+
beforeEach(async () => {
|
|
326
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
327
|
+
await initWorkspace(tempDir);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
afterEach(async () => {
|
|
331
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should remove package from registry', async () => {
|
|
335
|
+
// Set up a package first
|
|
336
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
337
|
+
const content = `---
|
|
338
|
+
Name: package
|
|
339
|
+
Type: Document
|
|
340
|
+
Description: Test
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
# Definitions
|
|
344
|
+
|
|
345
|
+
## Package Entry
|
|
346
|
+
|
|
347
|
+
| Field | Required | Description |
|
|
348
|
+
|-------|----------|-------------|
|
|
349
|
+
| Source | Yes | URL |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
# Package Registry
|
|
354
|
+
|
|
355
|
+
## Packages
|
|
356
|
+
|
|
357
|
+
### test-package
|
|
358
|
+
|
|
359
|
+
Test
|
|
360
|
+
|
|
361
|
+
| Field | Value |
|
|
362
|
+
|-------|-------|
|
|
363
|
+
| Source | https://example.com/file.md |
|
|
364
|
+
| Provider | url |
|
|
365
|
+
| Cached | .libraries/file.md |
|
|
366
|
+
| Version | v1.0.0 |
|
|
367
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
368
|
+
`;
|
|
369
|
+
|
|
370
|
+
await fs.writeFile(packagePath, content);
|
|
371
|
+
|
|
372
|
+
// Create cached file
|
|
373
|
+
const cachePath = path.join(tempDir, '.libraries', 'file.md');
|
|
374
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
375
|
+
await fs.writeFile(cachePath, '# Content');
|
|
376
|
+
|
|
377
|
+
// Remove package
|
|
378
|
+
const result = await removePackage(tempDir, 'test-package');
|
|
379
|
+
|
|
380
|
+
expect(result.removed).toBe(true);
|
|
381
|
+
expect(result.id).toBe('test-package');
|
|
382
|
+
|
|
383
|
+
// Verify cached file is removed
|
|
384
|
+
const exists = await fs.stat(cachePath).then(() => true).catch(() => false);
|
|
385
|
+
expect(exists).toBe(false);
|
|
386
|
+
|
|
387
|
+
// Verify registry entry is removed
|
|
388
|
+
const newContent = await fs.readFile(packagePath, 'utf-8');
|
|
389
|
+
expect(newContent).not.toContain('test-package');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should return false for non-existent package', async () => {
|
|
393
|
+
const result = await removePackage(tempDir, 'nonexistent');
|
|
394
|
+
|
|
395
|
+
expect(result.removed).toBe(false);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('listPackages', () => {
|
|
400
|
+
let tempDir: string;
|
|
401
|
+
|
|
402
|
+
beforeEach(async () => {
|
|
403
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
404
|
+
await initWorkspace(tempDir);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
afterEach(async () => {
|
|
408
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should list all packages', async () => {
|
|
412
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
413
|
+
const content = `---
|
|
414
|
+
Name: package
|
|
415
|
+
Type: Document
|
|
416
|
+
Description: Test
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
# Definitions
|
|
420
|
+
|
|
421
|
+
## Package Entry
|
|
422
|
+
|
|
423
|
+
| Field | Required | Description |
|
|
424
|
+
|-------|----------|-------------|
|
|
425
|
+
| Source | Yes | URL |
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
# Package Registry
|
|
430
|
+
|
|
431
|
+
## Packages
|
|
432
|
+
|
|
433
|
+
### package-one
|
|
434
|
+
|
|
435
|
+
Test one
|
|
436
|
+
|
|
437
|
+
| Field | Value |
|
|
438
|
+
|-------|-------|
|
|
439
|
+
| Source | https://example.com/one.md |
|
|
440
|
+
| Provider | url |
|
|
441
|
+
| Cached | .libraries/one.md |
|
|
442
|
+
| Version | v1.0.0 |
|
|
443
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
444
|
+
|
|
445
|
+
### package-two
|
|
446
|
+
|
|
447
|
+
Test two
|
|
448
|
+
|
|
449
|
+
| Field | Value |
|
|
450
|
+
|-------|-------|
|
|
451
|
+
| Source | https://example.com/two.md |
|
|
452
|
+
| Provider | github |
|
|
453
|
+
| Cached | .libraries/two.md |
|
|
454
|
+
| Version | v2.0.0 |
|
|
455
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
await fs.writeFile(packagePath, content);
|
|
459
|
+
|
|
460
|
+
const result = await listPackages(tempDir);
|
|
461
|
+
|
|
462
|
+
expect(result.packages).toHaveLength(2);
|
|
463
|
+
expect(result.packages.some(p => p.id === 'package-one')).toBe(true);
|
|
464
|
+
expect(result.packages.some(p => p.id === 'package-two')).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should return empty array for empty registry', async () => {
|
|
468
|
+
const result = await listPackages(tempDir);
|
|
469
|
+
|
|
470
|
+
expect(result.packages).toHaveLength(0);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('getPackageInfo', () => {
|
|
475
|
+
let tempDir: string;
|
|
476
|
+
|
|
477
|
+
beforeEach(async () => {
|
|
478
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
479
|
+
await initWorkspace(tempDir);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
afterEach(async () => {
|
|
483
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should return package details', async () => {
|
|
487
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
488
|
+
const content = `---
|
|
489
|
+
Name: package
|
|
490
|
+
Type: Document
|
|
491
|
+
Description: Test
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
# Definitions
|
|
495
|
+
|
|
496
|
+
## Package Entry
|
|
497
|
+
|
|
498
|
+
| Field | Required | Description |
|
|
499
|
+
|-------|----------|-------------|
|
|
500
|
+
| Source | Yes | URL |
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
# Package Registry
|
|
505
|
+
|
|
506
|
+
## Packages
|
|
507
|
+
|
|
508
|
+
### test-package
|
|
509
|
+
|
|
510
|
+
Test description
|
|
511
|
+
|
|
512
|
+
| Field | Value |
|
|
513
|
+
|-------|-------|
|
|
514
|
+
| Source | https://example.com/file.md |
|
|
515
|
+
| Provider | github |
|
|
516
|
+
| Cached | .libraries/file.md |
|
|
517
|
+
| Version | v1.5.0 |
|
|
518
|
+
| Fetched | 2026-01-21T10:30:00Z |
|
|
519
|
+
| Integrity | sha256:abc123 |
|
|
520
|
+
`;
|
|
521
|
+
|
|
522
|
+
await fs.writeFile(packagePath, content);
|
|
523
|
+
|
|
524
|
+
const result = await getPackageInfo(tempDir, 'test-package');
|
|
525
|
+
|
|
526
|
+
expect(result).not.toBeNull();
|
|
527
|
+
expect(result?.id).toBe('test-package');
|
|
528
|
+
expect(result?.version).toBe('v1.5.0');
|
|
529
|
+
expect(result?.provider).toBe('github');
|
|
530
|
+
expect(result?.integrity).toBe('sha256:abc123');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should return null for non-existent package', async () => {
|
|
534
|
+
const result = await getPackageInfo(tempDir, 'nonexistent');
|
|
535
|
+
expect(result).toBeNull();
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('upgradePackage', () => {
|
|
540
|
+
let tempDir: string;
|
|
541
|
+
let originalFetch: typeof global.fetch;
|
|
542
|
+
|
|
543
|
+
beforeEach(async () => {
|
|
544
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cli-test-'));
|
|
545
|
+
await initWorkspace(tempDir);
|
|
546
|
+
originalFetch = global.fetch;
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
afterEach(async () => {
|
|
550
|
+
global.fetch = originalFetch;
|
|
551
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should upgrade package to latest version', async () => {
|
|
555
|
+
// Set up existing package
|
|
556
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
557
|
+
const content = `---
|
|
558
|
+
Name: package
|
|
559
|
+
Type: Document
|
|
560
|
+
Description: Test
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
# Definitions
|
|
564
|
+
|
|
565
|
+
## Package Entry
|
|
566
|
+
|
|
567
|
+
| Field | Required | Description |
|
|
568
|
+
|-------|----------|-------------|
|
|
569
|
+
| Source | Yes | URL |
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
# Package Registry
|
|
574
|
+
|
|
575
|
+
## Packages
|
|
576
|
+
|
|
577
|
+
### test-package
|
|
578
|
+
|
|
579
|
+
Test
|
|
580
|
+
|
|
581
|
+
| Field | Value |
|
|
582
|
+
|-------|-------|
|
|
583
|
+
| Source | https://github.com/org/repo/blob/v1.0.0/file.md |
|
|
584
|
+
| Provider | github |
|
|
585
|
+
| Cached | .libraries/repo/file.md |
|
|
586
|
+
| Version | v1.0.0 |
|
|
587
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
588
|
+
`;
|
|
589
|
+
|
|
590
|
+
await fs.writeFile(packagePath, content);
|
|
591
|
+
|
|
592
|
+
// Create cached file
|
|
593
|
+
const cachePath = path.join(tempDir, '.libraries', 'repo', 'file.md');
|
|
594
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
595
|
+
await fs.writeFile(cachePath, '# Old content');
|
|
596
|
+
|
|
597
|
+
// Mock fetch for tags API and content
|
|
598
|
+
global.fetch = vi.fn().mockImplementation(async (url: string) => {
|
|
599
|
+
if (url.includes('api.github.com')) {
|
|
600
|
+
return {
|
|
601
|
+
ok: true,
|
|
602
|
+
json: () => Promise.resolve([{ name: 'v2.0.0' }, { name: 'v1.0.0' }]),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
ok: true,
|
|
607
|
+
text: () => Promise.resolve('# New content'),
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const result = await upgradePackage(tempDir, 'test-package');
|
|
612
|
+
|
|
613
|
+
expect(result.upgraded).toBe(true);
|
|
614
|
+
expect(result.oldVersion).toBe('v1.0.0');
|
|
615
|
+
expect(result.newVersion).toBe('v2.0.0');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should return not upgraded if already latest', async () => {
|
|
619
|
+
const packagePath = path.join(tempDir, 'package.busy.md');
|
|
620
|
+
const content = `---
|
|
621
|
+
Name: package
|
|
622
|
+
Type: Document
|
|
623
|
+
Description: Test
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
# Definitions
|
|
627
|
+
|
|
628
|
+
## Package Entry
|
|
629
|
+
|
|
630
|
+
| Field | Required | Description |
|
|
631
|
+
|-------|----------|-------------|
|
|
632
|
+
| Source | Yes | URL |
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
# Package Registry
|
|
637
|
+
|
|
638
|
+
## Packages
|
|
639
|
+
|
|
640
|
+
### test-package
|
|
641
|
+
|
|
642
|
+
Test
|
|
643
|
+
|
|
644
|
+
| Field | Value |
|
|
645
|
+
|-------|-------|
|
|
646
|
+
| Source | https://github.com/org/repo/blob/v2.0.0/file.md |
|
|
647
|
+
| Provider | github |
|
|
648
|
+
| Cached | .libraries/repo/file.md |
|
|
649
|
+
| Version | v2.0.0 |
|
|
650
|
+
| Fetched | 2026-01-21T00:00:00Z |
|
|
651
|
+
`;
|
|
652
|
+
|
|
653
|
+
await fs.writeFile(packagePath, content);
|
|
654
|
+
|
|
655
|
+
// Mock fetch - already at latest
|
|
656
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
657
|
+
ok: true,
|
|
658
|
+
json: () => Promise.resolve([{ name: 'v2.0.0' }]),
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const result = await upgradePackage(tempDir, 'test-package');
|
|
662
|
+
|
|
663
|
+
expect(result.upgraded).toBe(false);
|
|
664
|
+
expect(result.oldVersion).toBe('v2.0.0');
|
|
665
|
+
expect(result.newVersion).toBe('v2.0.0');
|
|
666
|
+
});
|
|
667
|
+
});
|