@fractary/core 0.3.2 → 0.4.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/__tests__/factories.test.d.ts +5 -0
- package/dist/__tests__/factories.test.d.ts.map +1 -0
- package/dist/__tests__/factories.test.js +66 -0
- package/dist/__tests__/factories.test.js.map +1 -0
- package/dist/auth/__tests__/create-token-provider.test.d.ts +5 -0
- package/dist/auth/__tests__/create-token-provider.test.d.ts.map +1 -0
- package/dist/auth/__tests__/create-token-provider.test.js +104 -0
- package/dist/auth/__tests__/create-token-provider.test.js.map +1 -0
- package/dist/auth/__tests__/github-app-auth.test.d.ts +5 -0
- package/dist/auth/__tests__/github-app-auth.test.d.ts.map +1 -0
- package/dist/auth/__tests__/github-app-auth.test.js +293 -0
- package/dist/auth/__tests__/github-app-auth.test.js.map +1 -0
- package/dist/auth/__tests__/static-token-provider.test.d.ts +5 -0
- package/dist/auth/__tests__/static-token-provider.test.d.ts.map +1 -0
- package/dist/auth/__tests__/static-token-provider.test.js +54 -0
- package/dist/auth/__tests__/static-token-provider.test.js.map +1 -0
- package/dist/auth/github-app-auth.d.ts +109 -0
- package/dist/auth/github-app-auth.d.ts.map +1 -0
- package/dist/auth/github-app-auth.js +262 -0
- package/dist/auth/github-app-auth.js.map +1 -0
- package/dist/auth/github-app-token-provider.d.ts +59 -0
- package/dist/auth/github-app-token-provider.d.ts.map +1 -0
- package/dist/auth/github-app-token-provider.js +68 -0
- package/dist/auth/github-app-token-provider.js.map +1 -0
- package/dist/auth/index.d.ts +45 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +74 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/static-token-provider.d.ts +35 -0
- package/dist/auth/static-token-provider.d.ts.map +1 -0
- package/dist/auth/static-token-provider.js +45 -0
- package/dist/auth/static-token-provider.js.map +1 -0
- package/dist/auth/types.d.ts +49 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +8 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/common/yaml-config.d.ts +10 -0
- package/dist/common/yaml-config.d.ts.map +1 -1
- package/dist/common/yaml-config.js.map +1 -1
- package/dist/config/__tests__/loader.test.d.ts +5 -0
- package/dist/config/__tests__/loader.test.d.ts.map +1 -0
- package/dist/config/__tests__/loader.test.js +129 -0
- package/dist/config/__tests__/loader.test.js.map +1 -0
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +27 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +126 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +277 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/docs/index.d.ts +5 -0
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +6 -1
- package/dist/docs/index.js.map +1 -1
- package/dist/docs/manager.d.ts +27 -0
- package/dist/docs/manager.d.ts.map +1 -1
- package/dist/docs/manager.js +168 -15
- package/dist/docs/manager.js.map +1 -1
- package/dist/docs/type-registry.d.ts +123 -0
- package/dist/docs/type-registry.d.ts.map +1 -0
- package/dist/docs/type-registry.js +393 -0
- package/dist/docs/type-registry.js.map +1 -0
- package/dist/docs/types.d.ts +93 -0
- package/dist/docs/types.d.ts.map +1 -1
- package/dist/factories.d.ts +89 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +228 -0
- package/dist/factories.js.map +1 -0
- package/dist/file/factory.d.ts +41 -0
- package/dist/file/factory.d.ts.map +1 -0
- package/dist/file/factory.js +237 -0
- package/dist/file/factory.js.map +1 -0
- package/dist/file/gcs.d.ts +66 -0
- package/dist/file/gcs.d.ts.map +1 -0
- package/dist/file/gcs.js +226 -0
- package/dist/file/gcs.js.map +1 -0
- package/dist/file/gdrive.d.ts +78 -0
- package/dist/file/gdrive.d.ts.map +1 -0
- package/dist/file/gdrive.js +302 -0
- package/dist/file/gdrive.js.map +1 -0
- package/dist/file/index.d.ts +13 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +25 -1
- package/dist/file/index.js.map +1 -1
- package/dist/file/manager.d.ts +83 -2
- package/dist/file/manager.d.ts.map +1 -1
- package/dist/file/manager.js +125 -4
- package/dist/file/manager.js.map +1 -1
- package/dist/file/r2.d.ts +56 -0
- package/dist/file/r2.d.ts.map +1 -0
- package/dist/file/r2.js +96 -0
- package/dist/file/r2.js.map +1 -0
- package/dist/file/s3.d.ts +61 -0
- package/dist/file/s3.d.ts.map +1 -0
- package/dist/file/s3.js +258 -0
- package/dist/file/s3.js.map +1 -0
- package/dist/file/types.d.ts +145 -2
- package/dist/file/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/logs/index.d.ts +1 -0
- package/dist/logs/index.d.ts.map +1 -1
- package/dist/logs/index.js +3 -1
- package/dist/logs/index.js.map +1 -1
- package/dist/logs/manager.d.ts +29 -2
- package/dist/logs/manager.d.ts.map +1 -1
- package/dist/logs/manager.js +48 -7
- package/dist/logs/manager.js.map +1 -1
- package/dist/logs/type-registry.d.ts +180 -0
- package/dist/logs/type-registry.d.ts.map +1 -0
- package/dist/logs/type-registry.js +421 -0
- package/dist/logs/type-registry.js.map +1 -0
- package/dist/logs/type-registry.test.d.ts +5 -0
- package/dist/logs/type-registry.test.d.ts.map +1 -0
- package/dist/logs/type-registry.test.js +671 -0
- package/dist/logs/type-registry.test.js.map +1 -0
- package/dist/logs/types.d.ts +2 -0
- package/dist/logs/types.d.ts.map +1 -1
- package/package.json +62 -8
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for LogTypeRegistry
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const type_registry_1 = require("./type-registry");
|
|
43
|
+
describe('LogTypeRegistry', () => {
|
|
44
|
+
let tempDir;
|
|
45
|
+
let originalCwd;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// Create temp directory for tests
|
|
48
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fractary-log-registry-test-'));
|
|
49
|
+
originalCwd = process.cwd();
|
|
50
|
+
});
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
// Restore original state
|
|
53
|
+
process.chdir(originalCwd);
|
|
54
|
+
// Clean up temp directory
|
|
55
|
+
if (fs.existsSync(tempDir)) {
|
|
56
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Helper to create a minimal log type directory
|
|
61
|
+
*/
|
|
62
|
+
function createLogTypeDirectory(basePath, id, overrides) {
|
|
63
|
+
const typeDir = path.join(basePath, id);
|
|
64
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
65
|
+
const displayName = overrides?.displayName || `${id.charAt(0).toUpperCase()}${id.slice(1)} Log`;
|
|
66
|
+
const description = overrides?.description || `Description for ${id}`;
|
|
67
|
+
// Write type.yaml
|
|
68
|
+
const typeYaml = `
|
|
69
|
+
id: ${id}
|
|
70
|
+
display_name: "${displayName}"
|
|
71
|
+
description: "${description}"
|
|
72
|
+
output_path: .fractary/logs/${id}
|
|
73
|
+
file_naming:
|
|
74
|
+
pattern: "{date}-{id}.md"
|
|
75
|
+
date_format: "YYYYMMDD"
|
|
76
|
+
slug_source: id
|
|
77
|
+
slug_max_length: 50
|
|
78
|
+
frontmatter:
|
|
79
|
+
required_fields:
|
|
80
|
+
- log_type
|
|
81
|
+
- title
|
|
82
|
+
- date
|
|
83
|
+
- status
|
|
84
|
+
optional_fields:
|
|
85
|
+
- tags
|
|
86
|
+
defaults:
|
|
87
|
+
log_type: ${id}
|
|
88
|
+
status: active
|
|
89
|
+
structure:
|
|
90
|
+
required_sections:
|
|
91
|
+
- Content
|
|
92
|
+
optional_sections:
|
|
93
|
+
- Notes
|
|
94
|
+
status:
|
|
95
|
+
allowed_values:
|
|
96
|
+
- active
|
|
97
|
+
- completed
|
|
98
|
+
- archived
|
|
99
|
+
default: active
|
|
100
|
+
retention:
|
|
101
|
+
default_local_days: 30
|
|
102
|
+
default_cloud_days: 90
|
|
103
|
+
auto_archive: true
|
|
104
|
+
cleanup_after_archive: false
|
|
105
|
+
`;
|
|
106
|
+
fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
|
|
107
|
+
// Write template.md
|
|
108
|
+
const template = overrides?.template || `---
|
|
109
|
+
log_type: {{log_type}}
|
|
110
|
+
title: {{title}}
|
|
111
|
+
date: {{date}}
|
|
112
|
+
status: {{status}}
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
# {{title}}
|
|
116
|
+
|
|
117
|
+
## Content
|
|
118
|
+
|
|
119
|
+
{{content}}
|
|
120
|
+
`;
|
|
121
|
+
fs.writeFileSync(path.join(typeDir, 'template.md'), template);
|
|
122
|
+
// Write standards.md
|
|
123
|
+
const standards = overrides?.standards || `# ${displayName} Standards
|
|
124
|
+
|
|
125
|
+
Best practices for ${id} logs.
|
|
126
|
+
`;
|
|
127
|
+
fs.writeFileSync(path.join(typeDir, 'standards.md'), standards);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Helper to create a manifest file
|
|
131
|
+
*/
|
|
132
|
+
function createManifest(basePath, types) {
|
|
133
|
+
const manifest = `
|
|
134
|
+
version: "1.0"
|
|
135
|
+
base_url: https://example.com/logs
|
|
136
|
+
log_types:
|
|
137
|
+
${types.map((t) => ` - id: ${t.id}
|
|
138
|
+
display_name: "${t.displayName}"
|
|
139
|
+
description: "${t.description}"
|
|
140
|
+
path: ./${t.id}`).join('\n')}
|
|
141
|
+
`;
|
|
142
|
+
fs.writeFileSync(path.join(basePath, 'manifest.yaml'), manifest);
|
|
143
|
+
}
|
|
144
|
+
describe('constructor', () => {
|
|
145
|
+
it('should warn if specified coreTypesPath has no manifest', () => {
|
|
146
|
+
// Test that a non-existent coreTypesPath logs a warning
|
|
147
|
+
// (it doesn't throw because loadCoreTypes catches errors)
|
|
148
|
+
const nonExistentPath = path.join(tempDir, 'non-existent-types');
|
|
149
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
150
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: nonExistentPath });
|
|
151
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('manifest not found'));
|
|
152
|
+
expect(registry.getCoreTypes()).toEqual([]);
|
|
153
|
+
consoleSpy.mockRestore();
|
|
154
|
+
});
|
|
155
|
+
it('should not throw when skipCoreTypes is true', () => {
|
|
156
|
+
process.chdir(tempDir);
|
|
157
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
158
|
+
expect(registry.getAllTypes()).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
it('should load from explicit coreTypesPath', () => {
|
|
161
|
+
// Create test types directory
|
|
162
|
+
const typesDir = path.join(tempDir, 'types');
|
|
163
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
164
|
+
createLogTypeDirectory(typesDir, 'test-type');
|
|
165
|
+
createManifest(typesDir, [
|
|
166
|
+
{ id: 'test-type', displayName: 'Test Type', description: 'A test type' },
|
|
167
|
+
]);
|
|
168
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
169
|
+
expect(registry.hasType('test-type')).toBe(true);
|
|
170
|
+
expect(registry.getAllTypes().length).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
it('should use provided baseDir for custom types', () => {
|
|
173
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
174
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
175
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
176
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
177
|
+
// Create core type
|
|
178
|
+
createLogTypeDirectory(typesDir, 'core');
|
|
179
|
+
createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core type' }]);
|
|
180
|
+
// Create custom type in custom directory
|
|
181
|
+
createLogTypeDirectory(customDir, 'custom');
|
|
182
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
183
|
+
coreTypesPath: typesDir,
|
|
184
|
+
baseDir: tempDir,
|
|
185
|
+
customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
|
|
186
|
+
});
|
|
187
|
+
expect(registry.hasType('core')).toBe(true);
|
|
188
|
+
expect(registry.hasType('custom')).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe('loadCoreTypes', () => {
|
|
192
|
+
it('should load all types from manifest', () => {
|
|
193
|
+
const typesDir = path.join(tempDir, 'types');
|
|
194
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
195
|
+
createLogTypeDirectory(typesDir, 'type-a');
|
|
196
|
+
createLogTypeDirectory(typesDir, 'type-b');
|
|
197
|
+
createLogTypeDirectory(typesDir, 'type-c');
|
|
198
|
+
createManifest(typesDir, [
|
|
199
|
+
{ id: 'type-a', displayName: 'Type A', description: 'First type' },
|
|
200
|
+
{ id: 'type-b', displayName: 'Type B', description: 'Second type' },
|
|
201
|
+
{ id: 'type-c', displayName: 'Type C', description: 'Third type' },
|
|
202
|
+
]);
|
|
203
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
204
|
+
expect(registry.getCoreTypes().length).toBe(3);
|
|
205
|
+
expect(registry.hasType('type-a')).toBe(true);
|
|
206
|
+
expect(registry.hasType('type-b')).toBe(true);
|
|
207
|
+
expect(registry.hasType('type-c')).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
it('should skip types with missing directories', () => {
|
|
210
|
+
const typesDir = path.join(tempDir, 'types');
|
|
211
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
212
|
+
createLogTypeDirectory(typesDir, 'existing');
|
|
213
|
+
// Note: 'missing' is in manifest but has no directory
|
|
214
|
+
createManifest(typesDir, [
|
|
215
|
+
{ id: 'existing', displayName: 'Existing', description: 'Exists' },
|
|
216
|
+
{ id: 'missing', displayName: 'Missing', description: 'Does not exist' },
|
|
217
|
+
]);
|
|
218
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
219
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
220
|
+
expect(registry.hasType('existing')).toBe(true);
|
|
221
|
+
expect(registry.hasType('missing')).toBe(false);
|
|
222
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
223
|
+
consoleSpy.mockRestore();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe('getType', () => {
|
|
227
|
+
it('should return type definition by id', () => {
|
|
228
|
+
const typesDir = path.join(tempDir, 'types');
|
|
229
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
230
|
+
createLogTypeDirectory(typesDir, 'session', {
|
|
231
|
+
displayName: 'Session Log',
|
|
232
|
+
description: 'Claude Code sessions',
|
|
233
|
+
});
|
|
234
|
+
createManifest(typesDir, [
|
|
235
|
+
{ id: 'session', displayName: 'Session Log', description: 'Claude Code sessions' },
|
|
236
|
+
]);
|
|
237
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
238
|
+
const logType = registry.getType('session');
|
|
239
|
+
expect(logType).not.toBeNull();
|
|
240
|
+
expect(logType?.id).toBe('session');
|
|
241
|
+
expect(logType?.displayName).toBe('Session Log');
|
|
242
|
+
expect(logType?.description).toBe('Claude Code sessions');
|
|
243
|
+
});
|
|
244
|
+
it('should return null for unknown type', () => {
|
|
245
|
+
const typesDir = path.join(tempDir, 'types');
|
|
246
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
247
|
+
createLogTypeDirectory(typesDir, 'known');
|
|
248
|
+
createManifest(typesDir, [{ id: 'known', displayName: 'Known', description: 'Known type' }]);
|
|
249
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
250
|
+
expect(registry.getType('unknown')).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
it('should prefer custom type over core type with same id', () => {
|
|
253
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
254
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
255
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
256
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
257
|
+
// Create core type
|
|
258
|
+
createLogTypeDirectory(typesDir, 'override', {
|
|
259
|
+
displayName: 'Core Override',
|
|
260
|
+
description: 'Core version',
|
|
261
|
+
});
|
|
262
|
+
createManifest(typesDir, [
|
|
263
|
+
{ id: 'override', displayName: 'Core Override', description: 'Core version' },
|
|
264
|
+
]);
|
|
265
|
+
// Create custom type with same id
|
|
266
|
+
createLogTypeDirectory(customDir, 'override', {
|
|
267
|
+
displayName: 'Custom Override',
|
|
268
|
+
description: 'Custom version',
|
|
269
|
+
});
|
|
270
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
271
|
+
coreTypesPath: typesDir,
|
|
272
|
+
baseDir: tempDir,
|
|
273
|
+
customTypes: [{ id: 'override', path: 'custom-types/override' }],
|
|
274
|
+
});
|
|
275
|
+
const logType = registry.getType('override');
|
|
276
|
+
expect(logType?.displayName).toBe('Custom Override');
|
|
277
|
+
expect(logType?.description).toBe('Custom version');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('hasType', () => {
|
|
281
|
+
it('should return true for existing core type', () => {
|
|
282
|
+
const typesDir = path.join(tempDir, 'types');
|
|
283
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
284
|
+
createLogTypeDirectory(typesDir, 'session');
|
|
285
|
+
createManifest(typesDir, [
|
|
286
|
+
{ id: 'session', displayName: 'Session', description: 'Session type' },
|
|
287
|
+
]);
|
|
288
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
289
|
+
expect(registry.hasType('session')).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
it('should return true for existing custom type', () => {
|
|
292
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
293
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
294
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
295
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
296
|
+
createLogTypeDirectory(typesDir, 'core');
|
|
297
|
+
createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core type' }]);
|
|
298
|
+
createLogTypeDirectory(customDir, 'custom');
|
|
299
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
300
|
+
coreTypesPath: typesDir,
|
|
301
|
+
baseDir: tempDir,
|
|
302
|
+
customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
|
|
303
|
+
});
|
|
304
|
+
expect(registry.hasType('custom')).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
it('should return false for non-existent type', () => {
|
|
307
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
308
|
+
expect(registry.hasType('nonexistent')).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe('getAllTypes', () => {
|
|
312
|
+
it('should return combined core and custom types sorted by id', () => {
|
|
313
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
314
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
315
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
316
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
317
|
+
createLogTypeDirectory(typesDir, 'beta');
|
|
318
|
+
createLogTypeDirectory(typesDir, 'alpha');
|
|
319
|
+
createManifest(typesDir, [
|
|
320
|
+
{ id: 'beta', displayName: 'Beta', description: 'Beta type' },
|
|
321
|
+
{ id: 'alpha', displayName: 'Alpha', description: 'Alpha type' },
|
|
322
|
+
]);
|
|
323
|
+
createLogTypeDirectory(customDir, 'gamma');
|
|
324
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
325
|
+
coreTypesPath: typesDir,
|
|
326
|
+
baseDir: tempDir,
|
|
327
|
+
customTypes: [{ id: 'gamma', path: 'custom-types/gamma' }],
|
|
328
|
+
});
|
|
329
|
+
const types = registry.getAllTypes();
|
|
330
|
+
expect(types.length).toBe(3);
|
|
331
|
+
expect(types[0].id).toBe('alpha');
|
|
332
|
+
expect(types[1].id).toBe('beta');
|
|
333
|
+
expect(types[2].id).toBe('gamma');
|
|
334
|
+
});
|
|
335
|
+
it('should override core type with custom type of same id', () => {
|
|
336
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
337
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
338
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
339
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
340
|
+
createLogTypeDirectory(typesDir, 'shared', { description: 'Core description' });
|
|
341
|
+
createManifest(typesDir, [
|
|
342
|
+
{ id: 'shared', displayName: 'Shared', description: 'Core description' },
|
|
343
|
+
]);
|
|
344
|
+
createLogTypeDirectory(customDir, 'shared', { description: 'Custom description' });
|
|
345
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
346
|
+
coreTypesPath: typesDir,
|
|
347
|
+
baseDir: tempDir,
|
|
348
|
+
customTypes: [{ id: 'shared', path: 'custom-types/shared' }],
|
|
349
|
+
});
|
|
350
|
+
const types = registry.getAllTypes();
|
|
351
|
+
expect(types.length).toBe(1);
|
|
352
|
+
expect(types[0].description).toBe('Custom description');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
describe('getCoreTypes', () => {
|
|
356
|
+
it('should return only core types sorted by id', () => {
|
|
357
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
358
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
359
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
360
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
361
|
+
createLogTypeDirectory(typesDir, 'core-b');
|
|
362
|
+
createLogTypeDirectory(typesDir, 'core-a');
|
|
363
|
+
createManifest(typesDir, [
|
|
364
|
+
{ id: 'core-b', displayName: 'Core B', description: 'Core B' },
|
|
365
|
+
{ id: 'core-a', displayName: 'Core A', description: 'Core A' },
|
|
366
|
+
]);
|
|
367
|
+
createLogTypeDirectory(customDir, 'custom');
|
|
368
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
369
|
+
coreTypesPath: typesDir,
|
|
370
|
+
baseDir: tempDir,
|
|
371
|
+
customTypes: [{ id: 'custom', path: 'custom-types/custom' }],
|
|
372
|
+
});
|
|
373
|
+
const coreTypes = registry.getCoreTypes();
|
|
374
|
+
expect(coreTypes.length).toBe(2);
|
|
375
|
+
expect(coreTypes[0].id).toBe('core-a');
|
|
376
|
+
expect(coreTypes[1].id).toBe('core-b');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
describe('getCustomTypes', () => {
|
|
380
|
+
it('should return only custom types sorted by id', () => {
|
|
381
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
382
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
383
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
384
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
385
|
+
createLogTypeDirectory(typesDir, 'core');
|
|
386
|
+
createManifest(typesDir, [{ id: 'core', displayName: 'Core', description: 'Core' }]);
|
|
387
|
+
createLogTypeDirectory(customDir, 'custom-b');
|
|
388
|
+
createLogTypeDirectory(customDir, 'custom-a');
|
|
389
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
390
|
+
coreTypesPath: typesDir,
|
|
391
|
+
baseDir: tempDir,
|
|
392
|
+
customTypes: [
|
|
393
|
+
{ id: 'custom-b', path: 'custom-types/custom-b' },
|
|
394
|
+
{ id: 'custom-a', path: 'custom-types/custom-a' },
|
|
395
|
+
],
|
|
396
|
+
});
|
|
397
|
+
const customTypes = registry.getCustomTypes();
|
|
398
|
+
expect(customTypes.length).toBe(2);
|
|
399
|
+
expect(customTypes[0].id).toBe('custom-a');
|
|
400
|
+
expect(customTypes[1].id).toBe('custom-b');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
describe('getTypeIds', () => {
|
|
404
|
+
it('should return unique sorted type ids', () => {
|
|
405
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
406
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
407
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
408
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
409
|
+
createLogTypeDirectory(typesDir, 'gamma');
|
|
410
|
+
createLogTypeDirectory(typesDir, 'alpha');
|
|
411
|
+
createManifest(typesDir, [
|
|
412
|
+
{ id: 'gamma', displayName: 'Gamma', description: 'Gamma' },
|
|
413
|
+
{ id: 'alpha', displayName: 'Alpha', description: 'Alpha' },
|
|
414
|
+
]);
|
|
415
|
+
createLogTypeDirectory(customDir, 'beta');
|
|
416
|
+
createLogTypeDirectory(customDir, 'alpha'); // Duplicate id
|
|
417
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
418
|
+
coreTypesPath: typesDir,
|
|
419
|
+
baseDir: tempDir,
|
|
420
|
+
customTypes: [
|
|
421
|
+
{ id: 'beta', path: 'custom-types/beta' },
|
|
422
|
+
{ id: 'alpha', path: 'custom-types/alpha' },
|
|
423
|
+
],
|
|
424
|
+
});
|
|
425
|
+
const ids = registry.getTypeIds();
|
|
426
|
+
expect(ids).toEqual(['alpha', 'beta', 'gamma']);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
describe('registerType', () => {
|
|
430
|
+
it('should register a custom type programmatically', () => {
|
|
431
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
432
|
+
const logType = {
|
|
433
|
+
id: 'programmatic',
|
|
434
|
+
displayName: 'Programmatic Log',
|
|
435
|
+
description: 'Added via registerType',
|
|
436
|
+
template: '# {{title}}',
|
|
437
|
+
outputPath: '.fractary/logs/programmatic',
|
|
438
|
+
fileNaming: {
|
|
439
|
+
pattern: '{date}-{id}.md',
|
|
440
|
+
dateFormat: 'YYYYMMDD',
|
|
441
|
+
},
|
|
442
|
+
frontmatter: {
|
|
443
|
+
requiredFields: ['log_type', 'title'],
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
registry.registerType(logType);
|
|
447
|
+
expect(registry.hasType('programmatic')).toBe(true);
|
|
448
|
+
expect(registry.getType('programmatic')?.displayName).toBe('Programmatic Log');
|
|
449
|
+
});
|
|
450
|
+
it('should override existing type with same id', () => {
|
|
451
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
452
|
+
const original = {
|
|
453
|
+
id: 'test',
|
|
454
|
+
displayName: 'Original',
|
|
455
|
+
description: 'Original description',
|
|
456
|
+
template: '',
|
|
457
|
+
outputPath: '',
|
|
458
|
+
fileNaming: { pattern: '' },
|
|
459
|
+
frontmatter: { requiredFields: [] },
|
|
460
|
+
};
|
|
461
|
+
const updated = {
|
|
462
|
+
id: 'test',
|
|
463
|
+
displayName: 'Updated',
|
|
464
|
+
description: 'Updated description',
|
|
465
|
+
template: '',
|
|
466
|
+
outputPath: '',
|
|
467
|
+
fileNaming: { pattern: '' },
|
|
468
|
+
frontmatter: { requiredFields: [] },
|
|
469
|
+
};
|
|
470
|
+
registry.registerType(original);
|
|
471
|
+
registry.registerType(updated);
|
|
472
|
+
expect(registry.getType('test')?.displayName).toBe('Updated');
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
describe('unregisterType', () => {
|
|
476
|
+
it('should remove a custom type', () => {
|
|
477
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
478
|
+
const logType = {
|
|
479
|
+
id: 'removable',
|
|
480
|
+
displayName: 'Removable',
|
|
481
|
+
description: '',
|
|
482
|
+
template: '',
|
|
483
|
+
outputPath: '',
|
|
484
|
+
fileNaming: { pattern: '' },
|
|
485
|
+
frontmatter: { requiredFields: [] },
|
|
486
|
+
};
|
|
487
|
+
registry.registerType(logType);
|
|
488
|
+
expect(registry.hasType('removable')).toBe(true);
|
|
489
|
+
const result = registry.unregisterType('removable');
|
|
490
|
+
expect(result).toBe(true);
|
|
491
|
+
expect(registry.hasType('removable')).toBe(false);
|
|
492
|
+
});
|
|
493
|
+
it('should return false for non-existent type', () => {
|
|
494
|
+
const registry = new type_registry_1.LogTypeRegistry({ skipCoreTypes: true });
|
|
495
|
+
const result = registry.unregisterType('nonexistent');
|
|
496
|
+
expect(result).toBe(false);
|
|
497
|
+
});
|
|
498
|
+
it('should not affect core types', () => {
|
|
499
|
+
const typesDir = path.join(tempDir, 'types');
|
|
500
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
501
|
+
createLogTypeDirectory(typesDir, 'core-type');
|
|
502
|
+
createManifest(typesDir, [
|
|
503
|
+
{ id: 'core-type', displayName: 'Core Type', description: 'A core type' },
|
|
504
|
+
]);
|
|
505
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
506
|
+
expect(registry.hasType('core-type')).toBe(true);
|
|
507
|
+
// unregisterType only affects custom types
|
|
508
|
+
const result = registry.unregisterType('core-type');
|
|
509
|
+
expect(result).toBe(false);
|
|
510
|
+
expect(registry.hasType('core-type')).toBe(true); // Still exists in core
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
describe('loadCustomTypesFromManifest', () => {
|
|
514
|
+
it('should load custom types from manifest file', () => {
|
|
515
|
+
const typesDir = path.join(tempDir, 'core-types');
|
|
516
|
+
const customDir = path.join(tempDir, 'custom-types');
|
|
517
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
518
|
+
fs.mkdirSync(customDir, { recursive: true });
|
|
519
|
+
// Create empty core types
|
|
520
|
+
createManifest(typesDir, []);
|
|
521
|
+
// Create custom manifest with types
|
|
522
|
+
createLogTypeDirectory(customDir, 'custom-a');
|
|
523
|
+
createLogTypeDirectory(customDir, 'custom-b');
|
|
524
|
+
createManifest(customDir, [
|
|
525
|
+
{ id: 'custom-a', displayName: 'Custom A', description: 'First custom' },
|
|
526
|
+
{ id: 'custom-b', displayName: 'Custom B', description: 'Second custom' },
|
|
527
|
+
]);
|
|
528
|
+
const registry = new type_registry_1.LogTypeRegistry({
|
|
529
|
+
coreTypesPath: typesDir,
|
|
530
|
+
baseDir: tempDir,
|
|
531
|
+
customManifestPath: 'custom-types/manifest.yaml',
|
|
532
|
+
});
|
|
533
|
+
expect(registry.hasType('custom-a')).toBe(true);
|
|
534
|
+
expect(registry.hasType('custom-b')).toBe(true);
|
|
535
|
+
expect(registry.getCustomTypes().length).toBe(2);
|
|
536
|
+
});
|
|
537
|
+
it('should not error when custom manifest does not exist', () => {
|
|
538
|
+
const typesDir = path.join(tempDir, 'types');
|
|
539
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
540
|
+
createManifest(typesDir, []);
|
|
541
|
+
// Should not throw even if custom manifest doesn't exist
|
|
542
|
+
expect(() => {
|
|
543
|
+
new type_registry_1.LogTypeRegistry({
|
|
544
|
+
coreTypesPath: typesDir,
|
|
545
|
+
baseDir: tempDir,
|
|
546
|
+
customManifestPath: 'nonexistent/manifest.yaml',
|
|
547
|
+
});
|
|
548
|
+
}).not.toThrow();
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
describe('type definition structure', () => {
|
|
552
|
+
it('should load all expected fields from type.yaml', () => {
|
|
553
|
+
const typesDir = path.join(tempDir, 'types');
|
|
554
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
555
|
+
createLogTypeDirectory(typesDir, 'full-type');
|
|
556
|
+
createManifest(typesDir, [
|
|
557
|
+
{ id: 'full-type', displayName: 'Full Type', description: 'Full type definition' },
|
|
558
|
+
]);
|
|
559
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
560
|
+
const logType = registry.getType('full-type');
|
|
561
|
+
expect(logType).not.toBeNull();
|
|
562
|
+
expect(logType?.fileNaming).toBeDefined();
|
|
563
|
+
expect(logType?.fileNaming.pattern).toBeDefined();
|
|
564
|
+
expect(logType?.frontmatter).toBeDefined();
|
|
565
|
+
expect(logType?.frontmatter.requiredFields).toBeDefined();
|
|
566
|
+
expect(logType?.structure).toBeDefined();
|
|
567
|
+
expect(logType?.status).toBeDefined();
|
|
568
|
+
expect(logType?.retention).toBeDefined();
|
|
569
|
+
});
|
|
570
|
+
it('should include template content', () => {
|
|
571
|
+
const typesDir = path.join(tempDir, 'types');
|
|
572
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
573
|
+
const customTemplate = '# Custom Template\n\n{{content}}';
|
|
574
|
+
createLogTypeDirectory(typesDir, 'with-template', { template: customTemplate });
|
|
575
|
+
createManifest(typesDir, [
|
|
576
|
+
{ id: 'with-template', displayName: 'With Template', description: 'Has template' },
|
|
577
|
+
]);
|
|
578
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
579
|
+
const logType = registry.getType('with-template');
|
|
580
|
+
expect(logType?.template).toBe(customTemplate);
|
|
581
|
+
});
|
|
582
|
+
it('should include standards content', () => {
|
|
583
|
+
const typesDir = path.join(tempDir, 'types');
|
|
584
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
585
|
+
const customStandards = '# Standards\n\nFollow these rules.';
|
|
586
|
+
createLogTypeDirectory(typesDir, 'with-standards', { standards: customStandards });
|
|
587
|
+
createManifest(typesDir, [
|
|
588
|
+
{ id: 'with-standards', displayName: 'With Standards', description: 'Has standards' },
|
|
589
|
+
]);
|
|
590
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
591
|
+
const logType = registry.getType('with-standards');
|
|
592
|
+
expect(logType?.standards).toBe(customStandards);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
describe('static methods', () => {
|
|
596
|
+
it('should return core manifest URL', () => {
|
|
597
|
+
const url = type_registry_1.LogTypeRegistry.getCoreManifestUrl();
|
|
598
|
+
expect(url).toContain('githubusercontent.com/fractary/core');
|
|
599
|
+
expect(url).toContain('manifest.yaml');
|
|
600
|
+
});
|
|
601
|
+
it('should return core type URL for given id', () => {
|
|
602
|
+
const url = type_registry_1.LogTypeRegistry.getCoreTypeUrl('session');
|
|
603
|
+
expect(url).toContain('githubusercontent.com/fractary/core');
|
|
604
|
+
expect(url).toContain('/session');
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
describe('edge cases', () => {
|
|
608
|
+
it('should handle empty manifest', () => {
|
|
609
|
+
const typesDir = path.join(tempDir, 'types');
|
|
610
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
611
|
+
createManifest(typesDir, []);
|
|
612
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
613
|
+
expect(registry.getAllTypes()).toEqual([]);
|
|
614
|
+
});
|
|
615
|
+
it('should handle missing template and standards files', () => {
|
|
616
|
+
const typesDir = path.join(tempDir, 'types');
|
|
617
|
+
const typeDir = path.join(typesDir, 'minimal');
|
|
618
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
619
|
+
// Only create type.yaml, no template.md or standards.md
|
|
620
|
+
const typeYaml = `
|
|
621
|
+
id: minimal
|
|
622
|
+
display_name: "Minimal"
|
|
623
|
+
description: "Minimal type"
|
|
624
|
+
output_path: .fractary/logs/minimal
|
|
625
|
+
file_naming:
|
|
626
|
+
pattern: "{date}.md"
|
|
627
|
+
frontmatter:
|
|
628
|
+
required_fields:
|
|
629
|
+
- log_type
|
|
630
|
+
`;
|
|
631
|
+
fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
|
|
632
|
+
createManifest(typesDir, [
|
|
633
|
+
{ id: 'minimal', displayName: 'Minimal', description: 'Minimal' },
|
|
634
|
+
]);
|
|
635
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
636
|
+
const logType = registry.getType('minimal');
|
|
637
|
+
expect(logType).not.toBeNull();
|
|
638
|
+
expect(logType?.template).toBe('');
|
|
639
|
+
expect(logType?.standards).toBe('');
|
|
640
|
+
});
|
|
641
|
+
it('should warn and correct mismatched type id', () => {
|
|
642
|
+
const typesDir = path.join(tempDir, 'types');
|
|
643
|
+
const typeDir = path.join(typesDir, 'expected-id');
|
|
644
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
645
|
+
// Type YAML has different id than directory name
|
|
646
|
+
const typeYaml = `
|
|
647
|
+
id: different-id
|
|
648
|
+
display_name: "Mismatched"
|
|
649
|
+
description: "Id in yaml differs from manifest"
|
|
650
|
+
output_path: .fractary/logs/mismatched
|
|
651
|
+
file_naming:
|
|
652
|
+
pattern: "{date}.md"
|
|
653
|
+
frontmatter:
|
|
654
|
+
required_fields:
|
|
655
|
+
- log_type
|
|
656
|
+
`;
|
|
657
|
+
fs.writeFileSync(path.join(typeDir, 'type.yaml'), typeYaml);
|
|
658
|
+
fs.writeFileSync(path.join(typeDir, 'template.md'), '');
|
|
659
|
+
fs.writeFileSync(path.join(typeDir, 'standards.md'), '');
|
|
660
|
+
createManifest(typesDir, [
|
|
661
|
+
{ id: 'expected-id', displayName: 'Expected', description: 'Expected id' },
|
|
662
|
+
]);
|
|
663
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
664
|
+
const registry = new type_registry_1.LogTypeRegistry({ coreTypesPath: typesDir });
|
|
665
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('ID mismatch'));
|
|
666
|
+
expect(registry.hasType('expected-id')).toBe(true);
|
|
667
|
+
consoleSpy.mockRestore();
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
//# sourceMappingURL=type-registry.test.js.map
|