functional-examples 0.0.0-alpha.1 → 0.0.0-alpha.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 +168 -26
- package/README.md.template +106 -0
- package/dist/cli/commands/scan.d.ts +1 -1
- package/dist/cli/commands/validate.d.ts +2 -2
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +92 -25
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/index.d.ts +3 -3
- package/dist/config/resolver.d.ts +10 -10
- package/dist/config/resolver.d.ts.map +1 -1
- package/dist/config/resolver.js +156 -57
- package/dist/config/resolver.js.map +1 -1
- package/dist/config/types.d.ts +1 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/plugins/pipeline.d.ts +4 -0
- package/dist/plugins/pipeline.d.ts.map +1 -1
- package/dist/plugins/pipeline.js +10 -1
- package/dist/plugins/pipeline.js.map +1 -1
- package/dist/plugins/registry.d.ts +1 -0
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +2 -3
- package/dist/plugins/registry.js.map +1 -1
- package/dist/scanner/scan.d.ts +3 -3
- package/dist/scanner/scan.d.ts.map +1 -1
- package/dist/scanner/scan.js.map +1 -1
- package/dist/scanner/scanner.d.ts.map +1 -1
- package/dist/scanner/scanner.js +5 -5
- package/dist/scanner/scanner.js.map +1 -1
- package/dist/schema/merger.d.ts.map +1 -1
- package/dist/schema/merger.js +43 -5
- package/dist/schema/merger.js.map +1 -1
- package/dist/schema/validator.d.ts.map +1 -1
- package/dist/schema/validator.js +7 -1
- package/dist/schema/validator.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -24,27 +24,33 @@ yarn add functional-examples
|
|
|
24
24
|
### Scanning for Examples
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Basic example: Scanning for examples in a directory
|
|
29
|
+
*/
|
|
30
|
+
import { resolveConfig, scanExamples } from 'functional-examples';
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
async function main() {
|
|
33
|
+
// Resolve config (auto-detects installed plugins)
|
|
34
|
+
const config = await resolveConfig({ root: './examples' });
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log(example.files.map(f => f.path));
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
+
// Scan for examples
|
|
37
|
+
const result = await scanExamples(config);
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
console.log(`Found ${result.examples.length} examples:`);
|
|
40
|
+
for (const example of result.examples) {
|
|
41
|
+
console.log(` - ${example.title} (${example.id})`);
|
|
42
|
+
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
if (result.errors.length > 0) {
|
|
45
|
+
console.log(`\n${result.errors.length} errors occurred:`);
|
|
46
|
+
for (const error of result.errors) {
|
|
47
|
+
console.log(` - ${error.path}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
const setupCode = extractRegion(code, 'setup', { extension: 'ts' });
|
|
52
|
+
main().catch(console.error);
|
|
44
53
|
|
|
45
|
-
// Get all regions
|
|
46
|
-
const regions = parseRegions(code, { extension: 'py' });
|
|
47
|
-
console.log(regions['main']?.content);
|
|
48
54
|
```
|
|
49
55
|
|
|
50
56
|
## Example Formats
|
|
@@ -64,7 +70,6 @@ examples/
|
|
|
64
70
|
id: my-example
|
|
65
71
|
title: My Example
|
|
66
72
|
description: Demonstrates something useful
|
|
67
|
-
entryPoint: main.ts
|
|
68
73
|
```
|
|
69
74
|
|
|
70
75
|
### File-based (YAML frontmatter)
|
|
@@ -97,24 +102,161 @@ Supports 30+ languages with automatic comment syntax detection.
|
|
|
97
102
|
## Custom Extractors
|
|
98
103
|
|
|
99
104
|
```typescript
|
|
100
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Custom TOML-based extractor example.
|
|
107
|
+
*
|
|
108
|
+
* This demonstrates how to create your own extractor that:
|
|
109
|
+
* 1. Scans for a specific file pattern (meta.toml)
|
|
110
|
+
* 2. Parses metadata from that file format
|
|
111
|
+
* 3. Claims files and returns Example objects
|
|
112
|
+
*/
|
|
113
|
+
import {
|
|
114
|
+
type Example,
|
|
115
|
+
type Extractor,
|
|
116
|
+
type ExtractorResult,
|
|
117
|
+
} from 'functional-examples';
|
|
118
|
+
import { readdirSync, type Dirent } from 'node:fs';
|
|
119
|
+
import { readFile } from 'node:fs/promises';
|
|
120
|
+
import path, { join } from 'node:path';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Metadata structure for TOML examples.
|
|
124
|
+
* You can define any shape that fits your use case.
|
|
125
|
+
*/
|
|
126
|
+
export interface TomlMetadata {
|
|
127
|
+
id: string;
|
|
128
|
+
title: string;
|
|
129
|
+
description?: string;
|
|
130
|
+
author?: string;
|
|
131
|
+
}
|
|
101
132
|
|
|
102
|
-
|
|
103
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Create a custom extractor that reads TOML metadata files.
|
|
135
|
+
*
|
|
136
|
+
* Extractors implement a candidate-based pattern: they're called with
|
|
137
|
+
* pre-filtered candidates (files and directories) and decide which to handle.
|
|
138
|
+
*/
|
|
139
|
+
export function createTomlExtractor(): Extractor<TomlMetadata> {
|
|
140
|
+
return {
|
|
141
|
+
name: 'toml-extractor',
|
|
142
|
+
|
|
143
|
+
async extract(
|
|
144
|
+
candidates: Dirent[]
|
|
145
|
+
): Promise<ExtractorResult<TomlMetadata>> {
|
|
146
|
+
const examples: Example<TomlMetadata>[] = [];
|
|
147
|
+
const claimedFiles = new Set<string>();
|
|
148
|
+
const errors: { path: string; message: string }[] = [];
|
|
149
|
+
|
|
150
|
+
// Find meta.toml files from candidates
|
|
151
|
+
const tomlFiles: string[] = [];
|
|
152
|
+
|
|
153
|
+
for (const candidate of candidates) {
|
|
154
|
+
const fullPath = path.join(candidate.parentPath, candidate.name);
|
|
155
|
+
|
|
156
|
+
if (candidate.isFile()) {
|
|
157
|
+
// Direct file candidate: check if it's a meta.toml
|
|
158
|
+
if (candidate.name === 'meta.toml') {
|
|
159
|
+
tomlFiles.push(fullPath);
|
|
160
|
+
}
|
|
161
|
+
} else if (candidate.isDirectory()) {
|
|
162
|
+
// Directory candidate: look for meta.toml inside
|
|
163
|
+
const metaPath = path.join(fullPath, 'meta.toml');
|
|
164
|
+
try {
|
|
165
|
+
await readFile(metaPath, 'utf-8');
|
|
166
|
+
tomlFiles.push(metaPath);
|
|
167
|
+
} catch {
|
|
168
|
+
// No meta.toml in this directory, skip
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const tomlFile of tomlFiles) {
|
|
174
|
+
try {
|
|
175
|
+
const content = await readFile(tomlFile, 'utf-8');
|
|
176
|
+
const metadata = parseSimpleToml(content);
|
|
177
|
+
|
|
178
|
+
const exampleDir = path.dirname(tomlFile);
|
|
179
|
+
|
|
180
|
+
// Collect all files in the example directory
|
|
181
|
+
const files = collectExampleFiles(exampleDir);
|
|
182
|
+
|
|
183
|
+
// Claim all files
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
claimedFiles.add(file);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
examples.push({
|
|
189
|
+
id: metadata.id,
|
|
190
|
+
title: metadata.title,
|
|
191
|
+
description: metadata.description,
|
|
192
|
+
rootPath: exampleDir,
|
|
193
|
+
files: files.map((f) => ({
|
|
194
|
+
absolutePath: f,
|
|
195
|
+
relativePath: path.relative(exampleDir, f),
|
|
196
|
+
})),
|
|
197
|
+
metadata,
|
|
198
|
+
extractorName: 'toml-extractor',
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
errors.push({
|
|
202
|
+
path: tomlFile,
|
|
203
|
+
message: `Failed to parse: ${(err as Error).message}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { examples, errors, claimedFiles };
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
104
212
|
|
|
105
|
-
|
|
106
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Simplified TOML parser for demonstration.
|
|
215
|
+
* In production, use a proper TOML library like @iarna/toml.
|
|
216
|
+
*/
|
|
217
|
+
function parseSimpleToml(content: string): TomlMetadata {
|
|
218
|
+
const lines = content.split('\n');
|
|
219
|
+
const result: Record<string, string> = {};
|
|
220
|
+
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
// Match: key = "value"
|
|
223
|
+
const match = line.match(/^(\w+)\s*=\s*"(.*)"/);
|
|
224
|
+
if (match) {
|
|
225
|
+
result[match[1]] = match[2];
|
|
226
|
+
}
|
|
107
227
|
}
|
|
108
228
|
|
|
109
|
-
|
|
110
|
-
|
|
229
|
+
if (!result['id'] || !result['title']) {
|
|
230
|
+
throw new Error('TOML must have id and title fields');
|
|
111
231
|
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
id: result['id'],
|
|
235
|
+
title: result['title'],
|
|
236
|
+
description: result['description'],
|
|
237
|
+
author: result['author'],
|
|
238
|
+
};
|
|
112
239
|
}
|
|
113
240
|
|
|
114
|
-
|
|
115
|
-
.
|
|
241
|
+
function collectExampleFiles(root: string) {
|
|
242
|
+
if (root.endsWith('node_modules')) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let files: string[] = [];
|
|
247
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
248
|
+
for (const entry of entries) {
|
|
249
|
+
if (entry.isDirectory()) {
|
|
250
|
+
files = files.concat(
|
|
251
|
+
collectExampleFiles(join(entry.parentPath, entry.name))
|
|
252
|
+
);
|
|
253
|
+
} else {
|
|
254
|
+
files.push(join(entry.parentPath, entry.name));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return files;
|
|
258
|
+
}
|
|
116
259
|
|
|
117
|
-
const scanner = new ExampleScanner({ extractors: registry });
|
|
118
260
|
```
|
|
119
261
|
|
|
120
262
|
## API Reference
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# functional-examples
|
|
2
|
+
|
|
3
|
+
A language-agnostic library for treating code examples as first-class citizens.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Pluggable metadata extraction**: YAML frontmatter and meta.yml built-in
|
|
8
|
+
- **Language-aware region parsing**: Extracts code regions with comment-style detection
|
|
9
|
+
- **Flexible scanning**: Directory-based and file-based example discovery
|
|
10
|
+
- **Type-safe API**: Full TypeScript support with generic metadata types
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install functional-examples
|
|
16
|
+
# or
|
|
17
|
+
pnpm add functional-examples
|
|
18
|
+
# or
|
|
19
|
+
yarn add functional-examples
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Scanning for Examples
|
|
25
|
+
|
|
26
|
+
<%= example('basic-usage').file('scan.ts') %>
|
|
27
|
+
|
|
28
|
+
## Example Formats
|
|
29
|
+
|
|
30
|
+
### Directory-based (meta.yml)
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
examples/
|
|
34
|
+
my-example/
|
|
35
|
+
meta.yml
|
|
36
|
+
main.ts
|
|
37
|
+
helper.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**meta.yml:**
|
|
41
|
+
```yaml
|
|
42
|
+
id: my-example
|
|
43
|
+
title: My Example
|
|
44
|
+
description: Demonstrates something useful
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### File-based (YAML frontmatter)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// ---
|
|
51
|
+
// title: My Example
|
|
52
|
+
// description: A single-file example
|
|
53
|
+
// ---
|
|
54
|
+
|
|
55
|
+
console.log('Hello, world!');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Region Markers
|
|
59
|
+
|
|
60
|
+
Mark regions in your code for extraction:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// #region setup
|
|
64
|
+
const db = createDatabase();
|
|
65
|
+
// #endregion setup
|
|
66
|
+
|
|
67
|
+
// #region main
|
|
68
|
+
await db.query('SELECT * FROM users');
|
|
69
|
+
// #endregion main
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Supports 30+ languages with automatic comment syntax detection.
|
|
73
|
+
|
|
74
|
+
## Custom Extractors
|
|
75
|
+
|
|
76
|
+
<%= example('custom-extractor').file('toml-extractor.ts') %>
|
|
77
|
+
|
|
78
|
+
## API Reference
|
|
79
|
+
|
|
80
|
+
### Scanner
|
|
81
|
+
|
|
82
|
+
- `scanExamples(directory, options?)` - Scan a directory for examples
|
|
83
|
+
- `ExampleScanner` - Class for customized scanning
|
|
84
|
+
|
|
85
|
+
### Extractors
|
|
86
|
+
|
|
87
|
+
- `createDefaultRegistry()` - Create registry with built-in extractors
|
|
88
|
+
- `YamlFrontmatterExtractor` - Single-file YAML frontmatter
|
|
89
|
+
- `MetaYmlExtractor` - Directory-based meta.yml
|
|
90
|
+
|
|
91
|
+
### Regions
|
|
92
|
+
|
|
93
|
+
- `parseRegions(code, options?)` - Parse all regions
|
|
94
|
+
- `extractRegion(code, regionId, options?)` - Extract single region
|
|
95
|
+
- `stripRegionMarkers(code, options?)` - Remove all markers
|
|
96
|
+
- `listRegions(code, options?)` - List region IDs
|
|
97
|
+
- `LANGUAGE_CONFIGS` - Language comment syntax mappings
|
|
98
|
+
|
|
99
|
+
### File Helpers
|
|
100
|
+
|
|
101
|
+
- `readExampleFile(path, options?)` - Read file with optional region
|
|
102
|
+
- `readExampleFiles(directory, files)` - Read multiple files
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate command - validate configuration
|
|
2
|
+
* Validate command - validate configuration and example metadata
|
|
3
3
|
*/
|
|
4
4
|
export declare const validateCommand: import("cli-forge").CLI<{
|
|
5
5
|
unmatched: string[];
|
|
6
6
|
'--'?: string[];
|
|
7
7
|
} & {
|
|
8
8
|
strict?: boolean;
|
|
9
|
-
} & {}, Promise<
|
|
9
|
+
} & {}, Promise<any>, {}, undefined>;
|
|
10
10
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,eAAO,MAAM,eAAe;;;;;oCAsJ1B,CAAC"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate command - validate configuration
|
|
2
|
+
* Validate command - validate configuration and example metadata
|
|
3
3
|
*/
|
|
4
4
|
import { cli } from 'cli-forge';
|
|
5
5
|
import { resolveConfig } from '../../config/resolver.js';
|
|
6
6
|
import { validateConfig } from '../../config/validator.js';
|
|
7
|
+
import { scanExamples } from '../../scanner/index.js';
|
|
8
|
+
import { mergeMetadataSchemas } from '../../schema/merger.js';
|
|
9
|
+
import { createSchemaValidator } from '../../schema/validator.js';
|
|
7
10
|
export const validateCommand = cli('validate', {
|
|
8
|
-
description: 'Validate
|
|
11
|
+
description: 'Validate configuration and example metadata',
|
|
9
12
|
builder: (cmd) => cmd.option('strict', {
|
|
10
13
|
type: 'boolean',
|
|
11
14
|
description: 'Treat warnings as errors',
|
|
@@ -22,44 +25,108 @@ export const validateCommand = cli('validate', {
|
|
|
22
25
|
}
|
|
23
26
|
console.log(`Validating: ${configPath}`);
|
|
24
27
|
console.log('');
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// Phase 1: Validate config structure
|
|
29
|
+
const configErrors = validateConfig(config);
|
|
30
|
+
if (configErrors.length > 0) {
|
|
27
31
|
console.error('Configuration errors:');
|
|
28
|
-
for (const error of
|
|
32
|
+
for (const error of configErrors) {
|
|
29
33
|
console.error(` - ${error.path}: ${error.message}`);
|
|
30
34
|
}
|
|
31
35
|
process.exit(1);
|
|
32
36
|
}
|
|
37
|
+
let resolved;
|
|
33
38
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
resolved = await resolveConfig(config);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Failed to resolve configuration:');
|
|
43
|
+
console.error(` ${error.message}`);
|
|
44
|
+
return void process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log('Configuration is valid!');
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(`Extractors: ${resolved.extractors.length}`);
|
|
49
|
+
for (const extractor of resolved.extractors) {
|
|
50
|
+
console.log(` - ${extractor.name}`);
|
|
51
|
+
}
|
|
52
|
+
if (resolved.pathMappings.length > 0) {
|
|
36
53
|
console.log('');
|
|
37
|
-
console.log(`
|
|
38
|
-
for (const
|
|
39
|
-
console.log(` - ${
|
|
54
|
+
console.log(`Path mappings: ${resolved.pathMappings.length}`);
|
|
55
|
+
for (const mapping of resolved.pathMappings) {
|
|
56
|
+
console.log(` - ${mapping.pattern} -> ${mapping.extractor}`);
|
|
40
57
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
console.log('');
|
|
49
|
-
console.log('Scan config:');
|
|
50
|
-
console.log(` include: ${resolved.scan.include.join(', ') || '(all)'}`);
|
|
51
|
-
console.log(` exclude: ${resolved.scan.exclude.join(', ') || '(none)'}`);
|
|
52
|
-
if (resolved.extractors.length === 0 && options.strict) {
|
|
58
|
+
}
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log('Scan config:');
|
|
61
|
+
console.log(` include: ${resolved.scan.include.join(', ') || '(all)'}`);
|
|
62
|
+
console.log(` exclude: ${resolved.scan.exclude.join(', ') || '(none)'}`);
|
|
63
|
+
if (resolved.extractors.length === 0) {
|
|
64
|
+
if (options.strict) {
|
|
53
65
|
console.error('');
|
|
54
66
|
console.error('Warning: No extractors could be loaded.');
|
|
55
67
|
process.exit(1);
|
|
56
68
|
}
|
|
69
|
+
return;
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
// Phase 2: Scan examples and validate metadata
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('Scanning examples...');
|
|
74
|
+
const scanResult = await scanExamples(resolved);
|
|
75
|
+
console.log(`Found ${scanResult.examples.length} example(s) in ${scanResult.stats.durationMs}ms`);
|
|
76
|
+
if (scanResult.examples.length === 0) {
|
|
77
|
+
if (options.strict) {
|
|
78
|
+
console.error('');
|
|
79
|
+
console.error('Warning: No examples found.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Phase 3: Validate metadata against the combined schema
|
|
85
|
+
const pluginSchemas = resolved.registry.getSchemas();
|
|
86
|
+
const mergedSchema = mergeMetadataSchemas({
|
|
87
|
+
configSchema: resolved.metadata,
|
|
88
|
+
pluginSchemas,
|
|
89
|
+
});
|
|
90
|
+
delete mergedSchema['$schema'];
|
|
91
|
+
const validateSchema = createSchemaValidator(mergedSchema);
|
|
92
|
+
let metadataErrorCount = 0;
|
|
93
|
+
const exampleErrors = [];
|
|
94
|
+
for (const example of scanResult.examples) {
|
|
95
|
+
const result = validateSchema(example.metadata);
|
|
96
|
+
if (!result.success) {
|
|
97
|
+
const messages = result.errors.map((e) => `${e.path || '(root)'}: ${e.message}`);
|
|
98
|
+
exampleErrors.push({ displayPath: example.displayPath, messages });
|
|
99
|
+
metadataErrorCount += messages.length;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Report scan-level errors (extractor failures, conflicts, etc.)
|
|
103
|
+
if (scanResult.errors.length > 0) {
|
|
104
|
+
console.error('');
|
|
105
|
+
console.error(`Scan errors (${scanResult.errors.length}):`);
|
|
106
|
+
for (const error of scanResult.errors) {
|
|
107
|
+
console.error(` ${error.path}:`);
|
|
108
|
+
console.error(` ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Report metadata validation errors
|
|
112
|
+
if (exampleErrors.length > 0) {
|
|
113
|
+
console.error('');
|
|
114
|
+
console.error(`Metadata validation errors (${metadataErrorCount} across ${exampleErrors.length} example(s)):`);
|
|
115
|
+
for (const { displayPath, messages } of exampleErrors) {
|
|
116
|
+
console.error(` ${displayPath}:`);
|
|
117
|
+
for (const msg of messages) {
|
|
118
|
+
console.error(` - ${msg}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const totalErrors = scanResult.errors.length + metadataErrorCount;
|
|
123
|
+
if (totalErrors > 0) {
|
|
124
|
+
console.error('');
|
|
125
|
+
console.error(`Validation failed with ${totalErrors} error(s).`);
|
|
61
126
|
process.exit(1);
|
|
62
127
|
}
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log('All examples pass metadata validation!');
|
|
63
130
|
},
|
|
64
131
|
});
|
|
65
132
|
//# sourceMappingURL=validate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/cli/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,aAAa,EAAkB,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/cli/commands/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ declare const app: import("cli-forge").CLI<{
|
|
|
17
17
|
} & {} & {
|
|
18
18
|
config?: string;
|
|
19
19
|
} & {} & {
|
|
20
|
-
format?: "
|
|
20
|
+
format?: "table" | "json" | "yaml";
|
|
21
21
|
} & {} & {
|
|
22
22
|
output?: string;
|
|
23
23
|
} & {} & {
|
|
@@ -39,7 +39,7 @@ declare const app: import("cli-forge").CLI<{
|
|
|
39
39
|
'--'?: string[];
|
|
40
40
|
} & {
|
|
41
41
|
strict?: boolean;
|
|
42
|
-
} & {}, Promise<
|
|
42
|
+
} & {}, Promise<any>, {}, undefined>, import("cli-forge").CLI<{
|
|
43
43
|
unmatched: string[];
|
|
44
44
|
'--'?: string[];
|
|
45
45
|
} & {
|
|
@@ -53,7 +53,7 @@ declare const app: import("cli-forge").CLI<{
|
|
|
53
53
|
unmatched: string[];
|
|
54
54
|
'--'?: string[];
|
|
55
55
|
} & {
|
|
56
|
-
format?: "
|
|
56
|
+
format?: "json" | "ts";
|
|
57
57
|
} & {} & {
|
|
58
58
|
output?: string;
|
|
59
59
|
} & {} & {
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Configuration resolver - converts config references to actual extractors
|
|
2
|
+
* Configuration resolver - converts config references to actual plugins/extractors
|
|
3
3
|
*/
|
|
4
4
|
import type { ResolvedConfig } from '../types/index.js';
|
|
5
5
|
import type { ConfigWithRoot } from './types.js';
|
|
6
6
|
export type { ConfigValidationError, ResolvedConfig } from '../types/index.js';
|
|
7
7
|
/**
|
|
8
|
-
* Resolve a configuration to actual extractor instances.
|
|
8
|
+
* Resolve a configuration to actual plugin and extractor instances.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Plugin resolution follows this priority:
|
|
11
|
+
* 1. If `config.plugins` contains Plugin instances (from TS configs), use directly
|
|
12
|
+
* 2. If `config.plugins` contains string/tuple references (from JSON configs),
|
|
13
|
+
* resolve them by dynamically importing the package and calling its factory
|
|
14
|
+
* 3. If no plugins specified at all, auto-detect installed plugin packages
|
|
11
15
|
*
|
|
12
16
|
* @example
|
|
13
17
|
* ```typescript
|
|
14
18
|
* import { resolveConfig } from 'functional-examples';
|
|
15
19
|
*
|
|
16
|
-
* // Auto-detect
|
|
17
|
-
* const config = await resolveConfig({});
|
|
20
|
+
* // Auto-detect plugins from installed packages
|
|
21
|
+
* const config = await resolveConfig({ root: './examples' });
|
|
18
22
|
*
|
|
19
23
|
* // Use with scanExamples
|
|
20
|
-
* const result = await scanExamples(
|
|
21
|
-
* root: './examples',
|
|
22
|
-
* extractors: config.extractors,
|
|
23
|
-
* pathMappings: config.pathMappings,
|
|
24
|
-
* });
|
|
24
|
+
* const result = await scanExamples(config);
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveConfig<TMetadata = Record<string, unknown>>(config: ConfigWithRoot<TMetadata>): Promise<ResolvedConfig<TMetadata>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/config/resolver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/config/resolver.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAKV,cAAc,EAEf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAmB/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,GAChC,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CA0EpC"}
|