@vertesia/build-tools 0.80.0 → 0.80.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/lib/build-tools.js +286 -8
- package/lib/build-tools.js.map +1 -1
- package/lib/cjs/index.js +6 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/plugin.js +14 -6
- package/lib/cjs/plugin.js.map +1 -1
- package/lib/cjs/presets/index.js +8 -1
- package/lib/cjs/presets/index.js.map +1 -1
- package/lib/cjs/presets/prompt.js +203 -0
- package/lib/cjs/presets/prompt.js.map +1 -0
- package/lib/cjs/presets/skill-collection.js +83 -0
- package/lib/cjs/presets/skill-collection.js.map +1 -0
- package/lib/esm/index.js +1 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/plugin.js +14 -6
- package/lib/esm/plugin.js.map +1 -1
- package/lib/esm/presets/index.js +2 -0
- package/lib/esm/presets/index.js.map +1 -1
- package/lib/esm/presets/prompt.js +197 -0
- package/lib/esm/presets/prompt.js.map +1 -0
- package/lib/esm/presets/skill-collection.js +77 -0
- package/lib/esm/presets/skill-collection.js.map +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/plugin.d.ts.map +1 -1
- package/lib/types/presets/index.d.ts +2 -0
- package/lib/types/presets/index.d.ts.map +1 -1
- package/lib/types/presets/prompt.d.ts +77 -0
- package/lib/types/presets/prompt.d.ts.map +1 -0
- package/lib/types/presets/skill-collection.d.ts +26 -0
- package/lib/types/presets/skill-collection.d.ts.map +1 -0
- package/lib/types/types.d.ts +2 -0
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +8 -1
- package/src/plugin.ts +14 -6
- package/src/presets/index.ts +2 -0
- package/src/presets/prompt.ts +242 -0
- package/src/presets/skill-collection.ts +86 -0
- package/src/types.ts +3 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt transformer preset for template files with frontmatter
|
|
3
|
+
* Supports .jst, .hbs, and plain text files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { TransformerPreset } from '../types.js';
|
|
8
|
+
import { parseFrontmatter } from '../parsers/frontmatter.js';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Template type for prompt content
|
|
13
|
+
* MUST match TemplateType from @vertesia/common
|
|
14
|
+
*/
|
|
15
|
+
export enum TemplateType {
|
|
16
|
+
jst = "jst",
|
|
17
|
+
handlebars = "handlebars",
|
|
18
|
+
text = "text",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Template type alias
|
|
23
|
+
*/
|
|
24
|
+
export type PromptContentType = TemplateType;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Prompt role enum
|
|
28
|
+
* MUST match PromptRole from @llumiverse/common
|
|
29
|
+
*/
|
|
30
|
+
export enum PromptRole {
|
|
31
|
+
safety = "safety",
|
|
32
|
+
system = "system",
|
|
33
|
+
user = "user",
|
|
34
|
+
assistant = "assistant",
|
|
35
|
+
negative = "negative",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Zod schema for prompt frontmatter validation
|
|
40
|
+
*/
|
|
41
|
+
const PromptFrontmatterSchema = z.object({
|
|
42
|
+
// Required fields
|
|
43
|
+
role: z.nativeEnum(PromptRole, {
|
|
44
|
+
errorMap: () => ({ message: 'Role must be one of: safety, system, user, assistant, negative' })
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
// Optional fields
|
|
48
|
+
content_type: z.nativeEnum(TemplateType).optional(),
|
|
49
|
+
schema: z.string().optional(),
|
|
50
|
+
name: z.string().optional(),
|
|
51
|
+
externalId: z.string().optional(),
|
|
52
|
+
}).strict();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* MUST be kept in sync with @vertesia/common InCodePrompt
|
|
56
|
+
* Zod schema for prompt definition
|
|
57
|
+
*/
|
|
58
|
+
export const PromptDefinitionSchema = z.object({
|
|
59
|
+
role: z.nativeEnum(PromptRole),
|
|
60
|
+
content: z.string(),
|
|
61
|
+
content_type: z.nativeEnum(TemplateType),
|
|
62
|
+
schema: z.any().optional(),
|
|
63
|
+
name: z.string().optional(),
|
|
64
|
+
externalId: z.string().optional(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* TypeScript type inferred from the Zod schema
|
|
69
|
+
*/
|
|
70
|
+
export type PromptDefinition = z.infer<typeof PromptDefinitionSchema>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalize schema path for import
|
|
74
|
+
* - Adds './' prefix if not a relative path
|
|
75
|
+
* - Replaces .ts with .js
|
|
76
|
+
* - Adds .js if no extension
|
|
77
|
+
*
|
|
78
|
+
* @param schemaPath - Original schema path from frontmatter
|
|
79
|
+
* @returns Normalized path for ES module import
|
|
80
|
+
*/
|
|
81
|
+
function normalizeSchemaPath(schemaPath: string): string {
|
|
82
|
+
let normalized = schemaPath.trim();
|
|
83
|
+
|
|
84
|
+
// Add './' prefix if not already a relative path
|
|
85
|
+
if (!normalized.startsWith('.')) {
|
|
86
|
+
normalized = './' + normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get the extension
|
|
90
|
+
const ext = path.extname(normalized);
|
|
91
|
+
|
|
92
|
+
if (ext === '.ts') {
|
|
93
|
+
// Replace .ts with .js
|
|
94
|
+
normalized = normalized.slice(0, -3) + '.js';
|
|
95
|
+
} else if (!ext) {
|
|
96
|
+
// No extension, add .js
|
|
97
|
+
normalized = normalized + '.js';
|
|
98
|
+
}
|
|
99
|
+
// If extension is already .js or something else, leave as is
|
|
100
|
+
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Infer content type from file extension
|
|
106
|
+
*
|
|
107
|
+
* @param filePath - Path to the prompt file
|
|
108
|
+
* @returns Inferred content type
|
|
109
|
+
*/
|
|
110
|
+
function inferContentType(filePath: string): TemplateType {
|
|
111
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
112
|
+
|
|
113
|
+
switch (ext) {
|
|
114
|
+
case '.jst':
|
|
115
|
+
return TemplateType.jst;
|
|
116
|
+
case '.hbs':
|
|
117
|
+
return TemplateType.handlebars;
|
|
118
|
+
default:
|
|
119
|
+
return TemplateType.text;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a PromptDefinition from frontmatter and content
|
|
125
|
+
*
|
|
126
|
+
* @param frontmatter - Parsed frontmatter object
|
|
127
|
+
* @param content - Prompt content (body of the file)
|
|
128
|
+
* @param filePath - Path to the prompt file (for content type inference)
|
|
129
|
+
* @returns Prompt definition object and optional imports
|
|
130
|
+
*/
|
|
131
|
+
function buildPromptDefinition(
|
|
132
|
+
frontmatter: Record<string, any>,
|
|
133
|
+
content: string,
|
|
134
|
+
filePath: string
|
|
135
|
+
): { prompt: PromptDefinition; imports?: string[]; schemaImportName?: string } {
|
|
136
|
+
// Determine content type from frontmatter or file extension
|
|
137
|
+
const content_type: TemplateType =
|
|
138
|
+
frontmatter.content_type || inferContentType(filePath);
|
|
139
|
+
|
|
140
|
+
const prompt: PromptDefinition = {
|
|
141
|
+
role: frontmatter.role,
|
|
142
|
+
content,
|
|
143
|
+
content_type,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Add optional fields
|
|
147
|
+
if (frontmatter.name) {
|
|
148
|
+
prompt.name = frontmatter.name;
|
|
149
|
+
}
|
|
150
|
+
if (frontmatter.externalId) {
|
|
151
|
+
prompt.externalId = frontmatter.externalId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Handle schema import if specified
|
|
155
|
+
let imports: string[] | undefined;
|
|
156
|
+
let schemaImportName: string | undefined;
|
|
157
|
+
|
|
158
|
+
if (frontmatter.schema) {
|
|
159
|
+
const normalizedPath = normalizeSchemaPath(frontmatter.schema);
|
|
160
|
+
schemaImportName = '__promptSchema';
|
|
161
|
+
imports = [`import ${schemaImportName} from '${normalizedPath}';`];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { prompt, imports, schemaImportName };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Prompt transformer preset
|
|
169
|
+
* Transforms template files with ?prompt suffix into prompt definition objects
|
|
170
|
+
*
|
|
171
|
+
* Supported file types:
|
|
172
|
+
* - .jst (JavaScript template literals) → content_type: 'jst'
|
|
173
|
+
* - .hbs (Handlebars templates) → content_type: 'handlebars'
|
|
174
|
+
* - .txt or other → content_type: 'text'
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* import PROMPT from './prompt.hbs?prompt';
|
|
179
|
+
* // PROMPT is an InCodePrompt object
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export const promptTransformer: TransformerPreset = {
|
|
183
|
+
pattern: /\?prompt$/,
|
|
184
|
+
schema: PromptDefinitionSchema,
|
|
185
|
+
transform: (content: string, filePath: string) => {
|
|
186
|
+
const { frontmatter, content: promptContent } = parseFrontmatter(content);
|
|
187
|
+
|
|
188
|
+
// Validate frontmatter
|
|
189
|
+
const frontmatterValidation = PromptFrontmatterSchema.safeParse(frontmatter);
|
|
190
|
+
if (!frontmatterValidation.success) {
|
|
191
|
+
const errors = frontmatterValidation.error.errors
|
|
192
|
+
.map((err) => {
|
|
193
|
+
const path = err.path.length > 0 ? err.path.join('.') : 'frontmatter';
|
|
194
|
+
return ` - ${path}: ${err.message}`;
|
|
195
|
+
})
|
|
196
|
+
.join('\n');
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Invalid frontmatter in ${filePath}:\n${errors}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Build prompt definition
|
|
203
|
+
const { prompt, imports, schemaImportName } = buildPromptDefinition(
|
|
204
|
+
frontmatter,
|
|
205
|
+
promptContent,
|
|
206
|
+
filePath
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// If schema is specified, generate custom code with schema reference
|
|
210
|
+
if (schemaImportName) {
|
|
211
|
+
// Build the code manually to avoid JSON.stringify issues with schema reference
|
|
212
|
+
const lines = [
|
|
213
|
+
'export default {',
|
|
214
|
+
` role: "${prompt.role}",`,
|
|
215
|
+
` content: ${JSON.stringify(prompt.content)},`,
|
|
216
|
+
` content_type: "${prompt.content_type}",`,
|
|
217
|
+
` schema: ${schemaImportName}`,
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
if (prompt.name) {
|
|
221
|
+
lines.splice(4, 0, ` name: ${JSON.stringify(prompt.name)},`);
|
|
222
|
+
}
|
|
223
|
+
if (prompt.externalId) {
|
|
224
|
+
lines.splice(4, 0, ` externalId: ${JSON.stringify(prompt.externalId)},`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
lines.push('};');
|
|
228
|
+
const code = lines.join('\n');
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
data: prompt,
|
|
232
|
+
imports,
|
|
233
|
+
code,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Standard case without schema
|
|
238
|
+
return {
|
|
239
|
+
data: prompt,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill collection transformer for directory-based skill imports
|
|
3
|
+
* Scans a directory for subdirectories containing SKILL.md files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, statSync, existsSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import type { TransformerPreset } from '../types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Skill collection transformer preset
|
|
12
|
+
* Transforms directory imports with ?skills suffix into an array of skill imports
|
|
13
|
+
*
|
|
14
|
+
* Matches:
|
|
15
|
+
* - ./all?skills (recommended - generates all.js in the directory)
|
|
16
|
+
* - ./_skills?skills (generates _skills.js in the directory)
|
|
17
|
+
* - Any path ending with a filename and ?skills
|
|
18
|
+
*
|
|
19
|
+
* NOTE: A filename before ?skills is REQUIRED to avoid naming conflicts.
|
|
20
|
+
* The filename becomes the output module name.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import skills from './all?skills';
|
|
25
|
+
* // Scans current directory for subdirectories with SKILL.md
|
|
26
|
+
* // Generates all.js containing array of all skills
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const skillCollectionTransformer: TransformerPreset = {
|
|
30
|
+
pattern: /\/[^/?]+\?skills$/,
|
|
31
|
+
virtual: true, // Indicates this doesn't transform a real file
|
|
32
|
+
transform: (_content: string, filePath: string) => {
|
|
33
|
+
// Remove ?skills suffix and the filename to get directory path
|
|
34
|
+
// Example: /path/code/all?skills -> /path/code/all -> /path/code/
|
|
35
|
+
const pathWithoutQuery = filePath.replace(/\?skills$/, '');
|
|
36
|
+
const dirPath = path.dirname(pathWithoutQuery);
|
|
37
|
+
|
|
38
|
+
if (!existsSync(dirPath)) {
|
|
39
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!statSync(dirPath).isDirectory()) {
|
|
43
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Scan for subdirectories containing SKILL.md
|
|
47
|
+
const entries = readdirSync(dirPath);
|
|
48
|
+
const imports: string[] = [];
|
|
49
|
+
const names: string[] = [];
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const entryPath = path.join(dirPath, entry);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (statSync(entryPath).isDirectory()) {
|
|
56
|
+
const skillFile = path.join(entryPath, 'SKILL.md');
|
|
57
|
+
if (existsSync(skillFile)) {
|
|
58
|
+
// Generate unique identifier from directory name
|
|
59
|
+
const identifier = `Skill_${entry.replace(/[^a-zA-Z0-9_]/g, '_')}`;
|
|
60
|
+
imports.push(`import ${identifier} from './${entry}/SKILL.md';`);
|
|
61
|
+
names.push(identifier);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// Skip entries that can't be read
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (names.length === 0) {
|
|
71
|
+
console.warn(`No SKILL.md files found in subdirectories of ${dirPath}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Generate code that imports all skills and exports as array
|
|
75
|
+
const code = [
|
|
76
|
+
...imports,
|
|
77
|
+
'',
|
|
78
|
+
`export default [${names.join(', ')}];`
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
data: null, // Not used when custom code is provided
|
|
83
|
+
code
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -60,6 +60,9 @@ export interface TransformerRule {
|
|
|
60
60
|
/** Optional: Zod schema for validation */
|
|
61
61
|
schema?: z.ZodType<any>;
|
|
62
62
|
|
|
63
|
+
/** Optional: If true, the transformer generates virtual modules (no file to read) */
|
|
64
|
+
virtual?: boolean;
|
|
65
|
+
|
|
63
66
|
/** Optional: additional options for this transformer */
|
|
64
67
|
options?: Record<string, unknown>;
|
|
65
68
|
}
|