tlc-claude-code 1.3.0 → 1.4.1
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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +0 -16
- package/dashboard/dist/components/WorkspacePane.d.ts +1 -1
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const { ReadmeGenerator, generateReadme, createReadmeGenerator } = await import('./readme-generator.js');
|
|
7
|
+
|
|
8
|
+
describe('ReadmeGenerator', () => {
|
|
9
|
+
let tempDir;
|
|
10
|
+
let generator;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-gen-test-'));
|
|
14
|
+
generator = new ReadmeGenerator(tempDir);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('extractProjectInfo', () => {
|
|
22
|
+
it('extracts project name and description from package.json', () => {
|
|
23
|
+
fs.writeFileSync(
|
|
24
|
+
path.join(tempDir, 'package.json'),
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
name: 'my-awesome-project',
|
|
27
|
+
description: 'A project that does awesome things',
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const info = generator.extractProjectInfo();
|
|
32
|
+
|
|
33
|
+
expect(info.name).toBe('my-awesome-project');
|
|
34
|
+
expect(info.description).toBe('A project that does awesome things');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('handles scoped package names', () => {
|
|
38
|
+
fs.writeFileSync(
|
|
39
|
+
path.join(tempDir, 'package.json'),
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
name: '@myorg/my-package',
|
|
42
|
+
description: 'Scoped package',
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const info = generator.extractProjectInfo();
|
|
47
|
+
|
|
48
|
+
expect(info.name).toBe('@myorg/my-package');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns defaults when package.json is missing', () => {
|
|
52
|
+
const info = generator.extractProjectInfo();
|
|
53
|
+
|
|
54
|
+
expect(info.name).toBe(path.basename(tempDir));
|
|
55
|
+
expect(info.description).toBe('');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('handles package.json with missing fields', () => {
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(tempDir, 'package.json'),
|
|
61
|
+
JSON.stringify({ version: '1.0.0' })
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const info = generator.extractProjectInfo();
|
|
65
|
+
|
|
66
|
+
expect(info.name).toBe(path.basename(tempDir));
|
|
67
|
+
expect(info.description).toBe('');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('extractScripts', () => {
|
|
72
|
+
it('detects and documents scripts (build, test, start)', () => {
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(tempDir, 'package.json'),
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
name: 'test-project',
|
|
77
|
+
scripts: {
|
|
78
|
+
build: 'tsc',
|
|
79
|
+
test: 'vitest',
|
|
80
|
+
start: 'node dist/index.js',
|
|
81
|
+
lint: 'eslint .',
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const scripts = generator.extractScripts();
|
|
87
|
+
|
|
88
|
+
expect(scripts).toContainEqual({ name: 'build', command: 'tsc' });
|
|
89
|
+
expect(scripts).toContainEqual({ name: 'test', command: 'vitest' });
|
|
90
|
+
expect(scripts).toContainEqual({ name: 'start', command: 'node dist/index.js' });
|
|
91
|
+
expect(scripts).toContainEqual({ name: 'lint', command: 'eslint .' });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns empty array when no scripts', () => {
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(tempDir, 'package.json'),
|
|
97
|
+
JSON.stringify({ name: 'no-scripts' })
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const scripts = generator.extractScripts();
|
|
101
|
+
|
|
102
|
+
expect(scripts).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns empty array when package.json is missing', () => {
|
|
106
|
+
const scripts = generator.extractScripts();
|
|
107
|
+
|
|
108
|
+
expect(scripts).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('extractDependencies', () => {
|
|
113
|
+
it('lists runtime dependencies', () => {
|
|
114
|
+
fs.writeFileSync(
|
|
115
|
+
path.join(tempDir, 'package.json'),
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
name: 'dep-test',
|
|
118
|
+
dependencies: {
|
|
119
|
+
express: '^4.18.0',
|
|
120
|
+
lodash: '^4.17.21',
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const deps = generator.extractDependencies();
|
|
126
|
+
|
|
127
|
+
expect(deps.runtime).toContainEqual({ name: 'express', version: '^4.18.0' });
|
|
128
|
+
expect(deps.runtime).toContainEqual({ name: 'lodash', version: '^4.17.21' });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('lists dev dependencies', () => {
|
|
132
|
+
fs.writeFileSync(
|
|
133
|
+
path.join(tempDir, 'package.json'),
|
|
134
|
+
JSON.stringify({
|
|
135
|
+
name: 'dev-dep-test',
|
|
136
|
+
devDependencies: {
|
|
137
|
+
vitest: '^1.0.0',
|
|
138
|
+
typescript: '^5.0.0',
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const deps = generator.extractDependencies();
|
|
144
|
+
|
|
145
|
+
expect(deps.dev).toContainEqual({ name: 'vitest', version: '^1.0.0' });
|
|
146
|
+
expect(deps.dev).toContainEqual({ name: 'typescript', version: '^5.0.0' });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('returns empty arrays when no dependencies', () => {
|
|
150
|
+
fs.writeFileSync(
|
|
151
|
+
path.join(tempDir, 'package.json'),
|
|
152
|
+
JSON.stringify({ name: 'no-deps' })
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const deps = generator.extractDependencies();
|
|
156
|
+
|
|
157
|
+
expect(deps.runtime).toEqual([]);
|
|
158
|
+
expect(deps.dev).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('extractEnvVars', () => {
|
|
163
|
+
it('documents environment variables from .env.example', () => {
|
|
164
|
+
fs.writeFileSync(
|
|
165
|
+
path.join(tempDir, '.env.example'),
|
|
166
|
+
`# Database configuration
|
|
167
|
+
DATABASE_URL=postgres://localhost:5432/mydb
|
|
168
|
+
# API keys
|
|
169
|
+
API_KEY=your-api-key-here
|
|
170
|
+
SECRET_TOKEN=
|
|
171
|
+
PORT=3000
|
|
172
|
+
`
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const envVars = generator.extractEnvVars();
|
|
176
|
+
|
|
177
|
+
expect(envVars).toContainEqual({
|
|
178
|
+
name: 'DATABASE_URL',
|
|
179
|
+
example: 'postgres://localhost:5432/mydb',
|
|
180
|
+
comment: 'Database configuration',
|
|
181
|
+
});
|
|
182
|
+
expect(envVars).toContainEqual({
|
|
183
|
+
name: 'API_KEY',
|
|
184
|
+
example: 'your-api-key-here',
|
|
185
|
+
comment: 'API keys',
|
|
186
|
+
});
|
|
187
|
+
expect(envVars).toContainEqual({
|
|
188
|
+
name: 'SECRET_TOKEN',
|
|
189
|
+
example: '',
|
|
190
|
+
comment: '',
|
|
191
|
+
});
|
|
192
|
+
expect(envVars).toContainEqual({
|
|
193
|
+
name: 'PORT',
|
|
194
|
+
example: '3000',
|
|
195
|
+
comment: '',
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns empty array when .env.example is missing', () => {
|
|
200
|
+
const envVars = generator.extractEnvVars();
|
|
201
|
+
|
|
202
|
+
expect(envVars).toEqual([]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('handles .env.example with no variables', () => {
|
|
206
|
+
fs.writeFileSync(
|
|
207
|
+
path.join(tempDir, '.env.example'),
|
|
208
|
+
'# Just comments\n# No actual variables\n'
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const envVars = generator.extractEnvVars();
|
|
212
|
+
|
|
213
|
+
expect(envVars).toEqual([]);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('detectApiEndpoints', () => {
|
|
218
|
+
it('documents API endpoints if detected', () => {
|
|
219
|
+
// Create a simple route file
|
|
220
|
+
fs.mkdirSync(path.join(tempDir, 'src', 'routes'), { recursive: true });
|
|
221
|
+
fs.writeFileSync(
|
|
222
|
+
path.join(tempDir, 'src', 'routes', 'users.js'),
|
|
223
|
+
`
|
|
224
|
+
const express = require('express');
|
|
225
|
+
const router = express.Router();
|
|
226
|
+
|
|
227
|
+
router.get('/users', (req, res) => {
|
|
228
|
+
res.json([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
router.post('/users', (req, res) => {
|
|
232
|
+
res.status(201).json({ id: 1 });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
router.get('/users/:id', (req, res) => {
|
|
236
|
+
res.json({ id: req.params.id });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
module.exports = router;
|
|
240
|
+
`
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const endpoints = generator.detectApiEndpoints();
|
|
244
|
+
|
|
245
|
+
expect(endpoints.length).toBeGreaterThan(0);
|
|
246
|
+
expect(endpoints.some(e => e.method === 'GET' && e.path.includes('users'))).toBe(true);
|
|
247
|
+
expect(endpoints.some(e => e.method === 'POST' && e.path.includes('users'))).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('returns empty array when no routes found', () => {
|
|
251
|
+
const endpoints = generator.detectApiEndpoints();
|
|
252
|
+
|
|
253
|
+
expect(endpoints).toEqual([]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('generateInstallationSection', () => {
|
|
258
|
+
it('includes installation instructions', () => {
|
|
259
|
+
fs.writeFileSync(
|
|
260
|
+
path.join(tempDir, 'package.json'),
|
|
261
|
+
JSON.stringify({ name: 'test-project' })
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const section = generator.generateInstallationSection();
|
|
265
|
+
|
|
266
|
+
expect(section).toContain('npm install');
|
|
267
|
+
expect(section).toContain('Installation');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('includes git clone if repo is specified', () => {
|
|
271
|
+
fs.writeFileSync(
|
|
272
|
+
path.join(tempDir, 'package.json'),
|
|
273
|
+
JSON.stringify({
|
|
274
|
+
name: 'test-project',
|
|
275
|
+
repository: {
|
|
276
|
+
url: 'https://github.com/user/repo.git',
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const section = generator.generateInstallationSection();
|
|
282
|
+
|
|
283
|
+
expect(section).toContain('git clone');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('generate', () => {
|
|
288
|
+
it('generates README with project name and description', () => {
|
|
289
|
+
fs.writeFileSync(
|
|
290
|
+
path.join(tempDir, 'package.json'),
|
|
291
|
+
JSON.stringify({
|
|
292
|
+
name: 'my-project',
|
|
293
|
+
description: 'Does wonderful things',
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const readme = generator.generate();
|
|
298
|
+
|
|
299
|
+
expect(readme).toContain('# my-project');
|
|
300
|
+
expect(readme).toContain('Does wonderful things');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('includes npm scripts documentation', () => {
|
|
304
|
+
fs.writeFileSync(
|
|
305
|
+
path.join(tempDir, 'package.json'),
|
|
306
|
+
JSON.stringify({
|
|
307
|
+
name: 'scripted-project',
|
|
308
|
+
scripts: {
|
|
309
|
+
test: 'vitest',
|
|
310
|
+
build: 'tsc',
|
|
311
|
+
},
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const readme = generator.generate();
|
|
316
|
+
|
|
317
|
+
expect(readme).toContain('npm run test');
|
|
318
|
+
expect(readme).toContain('npm run build');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('lists key dependencies', () => {
|
|
322
|
+
fs.writeFileSync(
|
|
323
|
+
path.join(tempDir, 'package.json'),
|
|
324
|
+
JSON.stringify({
|
|
325
|
+
name: 'with-deps',
|
|
326
|
+
dependencies: {
|
|
327
|
+
express: '^4.18.0',
|
|
328
|
+
pg: '^8.11.0',
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const readme = generator.generate();
|
|
334
|
+
|
|
335
|
+
expect(readme).toContain('express');
|
|
336
|
+
expect(readme).toContain('Dependencies');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('documents env vars from .env.example', () => {
|
|
340
|
+
fs.writeFileSync(
|
|
341
|
+
path.join(tempDir, 'package.json'),
|
|
342
|
+
JSON.stringify({ name: 'env-project' })
|
|
343
|
+
);
|
|
344
|
+
fs.writeFileSync(
|
|
345
|
+
path.join(tempDir, '.env.example'),
|
|
346
|
+
'DATABASE_URL=postgres://localhost/db\nAPI_KEY=your-key\n'
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const readme = generator.generate();
|
|
350
|
+
|
|
351
|
+
expect(readme).toContain('DATABASE_URL');
|
|
352
|
+
expect(readme).toContain('Environment Variables');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('includes installation section', () => {
|
|
356
|
+
fs.writeFileSync(
|
|
357
|
+
path.join(tempDir, 'package.json'),
|
|
358
|
+
JSON.stringify({ name: 'installable' })
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const readme = generator.generate();
|
|
362
|
+
|
|
363
|
+
expect(readme).toContain('## Installation');
|
|
364
|
+
expect(readme).toContain('npm install');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('skips sections with no content', () => {
|
|
368
|
+
fs.writeFileSync(
|
|
369
|
+
path.join(tempDir, 'package.json'),
|
|
370
|
+
JSON.stringify({ name: 'minimal-project' })
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const readme = generator.generate();
|
|
374
|
+
|
|
375
|
+
// Should have title but skip empty sections
|
|
376
|
+
expect(readme).toContain('# minimal-project');
|
|
377
|
+
// Should not have Environment Variables section if no .env.example
|
|
378
|
+
expect(readme).not.toContain('## Environment Variables');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('formats markdown correctly', () => {
|
|
382
|
+
fs.writeFileSync(
|
|
383
|
+
path.join(tempDir, 'package.json'),
|
|
384
|
+
JSON.stringify({
|
|
385
|
+
name: 'formatted-project',
|
|
386
|
+
description: 'Test project',
|
|
387
|
+
scripts: { test: 'vitest' },
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const readme = generator.generate();
|
|
392
|
+
|
|
393
|
+
// Should have proper markdown structure
|
|
394
|
+
expect(readme).toMatch(/^# formatted-project/);
|
|
395
|
+
expect(readme).toMatch(/## \w+/); // At least one h2 section
|
|
396
|
+
expect(readme).toContain('```'); // Code blocks for commands
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
describe('write', () => {
|
|
401
|
+
it('writes README.md to disk', () => {
|
|
402
|
+
fs.writeFileSync(
|
|
403
|
+
path.join(tempDir, 'package.json'),
|
|
404
|
+
JSON.stringify({ name: 'writeable-project' })
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
generator.write();
|
|
408
|
+
|
|
409
|
+
const readmePath = path.join(tempDir, 'README.md');
|
|
410
|
+
expect(fs.existsSync(readmePath)).toBe(true);
|
|
411
|
+
|
|
412
|
+
const content = fs.readFileSync(readmePath, 'utf-8');
|
|
413
|
+
expect(content).toContain('# writeable-project');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('accepts custom output path', () => {
|
|
417
|
+
fs.writeFileSync(
|
|
418
|
+
path.join(tempDir, 'package.json'),
|
|
419
|
+
JSON.stringify({ name: 'custom-path-project' })
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const customPath = path.join(tempDir, 'docs', 'README.md');
|
|
423
|
+
fs.mkdirSync(path.join(tempDir, 'docs'), { recursive: true });
|
|
424
|
+
|
|
425
|
+
generator.write(customPath);
|
|
426
|
+
|
|
427
|
+
expect(fs.existsSync(customPath)).toBe(true);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('generateReadme', () => {
|
|
433
|
+
let tempDir;
|
|
434
|
+
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-fn-test-'));
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
afterEach(() => {
|
|
440
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('is a convenience function that generates readme for a path', () => {
|
|
444
|
+
fs.writeFileSync(
|
|
445
|
+
path.join(tempDir, 'package.json'),
|
|
446
|
+
JSON.stringify({
|
|
447
|
+
name: 'convenience-project',
|
|
448
|
+
description: 'Quick generation',
|
|
449
|
+
})
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const readme = generateReadme(tempDir);
|
|
453
|
+
|
|
454
|
+
expect(readme).toContain('# convenience-project');
|
|
455
|
+
expect(readme).toContain('Quick generation');
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe('createReadmeGenerator', () => {
|
|
460
|
+
let tempDir;
|
|
461
|
+
|
|
462
|
+
beforeEach(() => {
|
|
463
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readme-factory-test-'));
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
afterEach(() => {
|
|
467
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('creates a generator with methods', () => {
|
|
471
|
+
const gen = createReadmeGenerator(tempDir);
|
|
472
|
+
|
|
473
|
+
expect(gen.generate).toBeDefined();
|
|
474
|
+
expect(gen.write).toBeDefined();
|
|
475
|
+
expect(gen.extractProjectInfo).toBeDefined();
|
|
476
|
+
expect(gen.extractScripts).toBeDefined();
|
|
477
|
+
expect(gen.extractDependencies).toBeDefined();
|
|
478
|
+
expect(gen.extractEnvVars).toBeDefined();
|
|
479
|
+
expect(gen.detectApiEndpoints).toBeDefined();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('generates readme through factory instance', () => {
|
|
483
|
+
fs.writeFileSync(
|
|
484
|
+
path.join(tempDir, 'package.json'),
|
|
485
|
+
JSON.stringify({ name: 'factory-project' })
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const gen = createReadmeGenerator(tempDir);
|
|
489
|
+
const readme = gen.generate();
|
|
490
|
+
|
|
491
|
+
expect(readme).toContain('# factory-project');
|
|
492
|
+
});
|
|
493
|
+
});
|