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.
Files changed (128) hide show
  1. package/README.md +129 -0
  2. package/dist/builders/context.d.ts +50 -0
  3. package/dist/builders/context.d.ts.map +1 -0
  4. package/dist/builders/context.js +190 -0
  5. package/dist/cache/index.d.ts +100 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +270 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +463 -0
  11. package/dist/commands/package.d.ts +96 -0
  12. package/dist/commands/package.d.ts.map +1 -0
  13. package/dist/commands/package.js +285 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/loader.d.ts +6 -0
  18. package/dist/loader.d.ts.map +1 -0
  19. package/dist/loader.js +361 -0
  20. package/dist/merge.d.ts +16 -0
  21. package/dist/merge.d.ts.map +1 -0
  22. package/dist/merge.js +102 -0
  23. package/dist/package/manifest.d.ts +59 -0
  24. package/dist/package/manifest.d.ts.map +1 -0
  25. package/dist/package/manifest.js +265 -0
  26. package/dist/parser.d.ts +28 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +220 -0
  29. package/dist/parsers/frontmatter.d.ts +14 -0
  30. package/dist/parsers/frontmatter.d.ts.map +1 -0
  31. package/dist/parsers/frontmatter.js +110 -0
  32. package/dist/parsers/imports.d.ts +48 -0
  33. package/dist/parsers/imports.d.ts.map +1 -0
  34. package/dist/parsers/imports.js +147 -0
  35. package/dist/parsers/links.d.ts +12 -0
  36. package/dist/parsers/links.d.ts.map +1 -0
  37. package/dist/parsers/links.js +79 -0
  38. package/dist/parsers/localdefs.d.ts +6 -0
  39. package/dist/parsers/localdefs.d.ts.map +1 -0
  40. package/dist/parsers/localdefs.js +132 -0
  41. package/dist/parsers/operations.d.ts +32 -0
  42. package/dist/parsers/operations.d.ts.map +1 -0
  43. package/dist/parsers/operations.js +313 -0
  44. package/dist/parsers/sections.d.ts +15 -0
  45. package/dist/parsers/sections.d.ts.map +1 -0
  46. package/dist/parsers/sections.js +173 -0
  47. package/dist/parsers/tools.d.ts +30 -0
  48. package/dist/parsers/tools.d.ts.map +1 -0
  49. package/dist/parsers/tools.js +178 -0
  50. package/dist/parsers/triggers.d.ts +35 -0
  51. package/dist/parsers/triggers.d.ts.map +1 -0
  52. package/dist/parsers/triggers.js +219 -0
  53. package/dist/providers/base.d.ts +60 -0
  54. package/dist/providers/base.d.ts.map +1 -0
  55. package/dist/providers/base.js +34 -0
  56. package/dist/providers/github.d.ts +18 -0
  57. package/dist/providers/github.d.ts.map +1 -0
  58. package/dist/providers/github.js +109 -0
  59. package/dist/providers/gitlab.d.ts +18 -0
  60. package/dist/providers/gitlab.d.ts.map +1 -0
  61. package/dist/providers/gitlab.js +101 -0
  62. package/dist/providers/index.d.ts +13 -0
  63. package/dist/providers/index.d.ts.map +1 -0
  64. package/dist/providers/index.js +17 -0
  65. package/dist/providers/local.d.ts +31 -0
  66. package/dist/providers/local.d.ts.map +1 -0
  67. package/dist/providers/local.js +116 -0
  68. package/dist/providers/url.d.ts +16 -0
  69. package/dist/providers/url.d.ts.map +1 -0
  70. package/dist/providers/url.js +45 -0
  71. package/dist/registry/index.d.ts +99 -0
  72. package/dist/registry/index.d.ts.map +1 -0
  73. package/dist/registry/index.js +320 -0
  74. package/dist/types/schema.d.ts +3259 -0
  75. package/dist/types/schema.d.ts.map +1 -0
  76. package/dist/types/schema.js +258 -0
  77. package/dist/utils/logger.d.ts +19 -0
  78. package/dist/utils/logger.d.ts.map +1 -0
  79. package/dist/utils/logger.js +23 -0
  80. package/dist/utils/slugify.d.ts +14 -0
  81. package/dist/utils/slugify.d.ts.map +1 -0
  82. package/dist/utils/slugify.js +28 -0
  83. package/package.json +61 -0
  84. package/src/__tests__/cache.test.ts +393 -0
  85. package/src/__tests__/cli-package.test.ts +667 -0
  86. package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
  87. package/src/__tests__/fixtures/concept.busy.md +30 -0
  88. package/src/__tests__/fixtures/document.busy.md +44 -0
  89. package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
  90. package/src/__tests__/fixtures/tool-document.busy.md +71 -0
  91. package/src/__tests__/fixtures/tool.busy.md +54 -0
  92. package/src/__tests__/imports.test.ts +244 -0
  93. package/src/__tests__/integration.test.ts +432 -0
  94. package/src/__tests__/operations.test.ts +408 -0
  95. package/src/__tests__/package-manifest.test.ts +455 -0
  96. package/src/__tests__/providers.test.ts +672 -0
  97. package/src/__tests__/registry.test.ts +402 -0
  98. package/src/__tests__/schema.test.ts +467 -0
  99. package/src/__tests__/tools.test.ts +376 -0
  100. package/src/__tests__/triggers.test.ts +312 -0
  101. package/src/builders/context.ts +294 -0
  102. package/src/cache/index.ts +312 -0
  103. package/src/cli/index.ts +514 -0
  104. package/src/commands/package.ts +392 -0
  105. package/src/index.ts +46 -0
  106. package/src/loader.ts +474 -0
  107. package/src/merge.ts +126 -0
  108. package/src/package/manifest.ts +349 -0
  109. package/src/parser.ts +278 -0
  110. package/src/parsers/frontmatter.ts +135 -0
  111. package/src/parsers/imports.ts +196 -0
  112. package/src/parsers/links.ts +108 -0
  113. package/src/parsers/localdefs.ts +166 -0
  114. package/src/parsers/operations.ts +404 -0
  115. package/src/parsers/sections.ts +230 -0
  116. package/src/parsers/tools.ts +215 -0
  117. package/src/parsers/triggers.ts +252 -0
  118. package/src/providers/base.ts +77 -0
  119. package/src/providers/github.ts +129 -0
  120. package/src/providers/gitlab.ts +121 -0
  121. package/src/providers/index.ts +25 -0
  122. package/src/providers/local.ts +129 -0
  123. package/src/providers/url.ts +56 -0
  124. package/src/registry/index.ts +408 -0
  125. package/src/types/schema.ts +369 -0
  126. package/src/utils/logger.ts +25 -0
  127. package/src/utils/slugify.ts +31 -0
  128. 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
+ });