@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.
Files changed (40) hide show
  1. package/lib/build-tools.js +286 -8
  2. package/lib/build-tools.js.map +1 -1
  3. package/lib/cjs/index.js +6 -1
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/cjs/plugin.js +14 -6
  6. package/lib/cjs/plugin.js.map +1 -1
  7. package/lib/cjs/presets/index.js +8 -1
  8. package/lib/cjs/presets/index.js.map +1 -1
  9. package/lib/cjs/presets/prompt.js +203 -0
  10. package/lib/cjs/presets/prompt.js.map +1 -0
  11. package/lib/cjs/presets/skill-collection.js +83 -0
  12. package/lib/cjs/presets/skill-collection.js.map +1 -0
  13. package/lib/esm/index.js +1 -1
  14. package/lib/esm/index.js.map +1 -1
  15. package/lib/esm/plugin.js +14 -6
  16. package/lib/esm/plugin.js.map +1 -1
  17. package/lib/esm/presets/index.js +2 -0
  18. package/lib/esm/presets/index.js.map +1 -1
  19. package/lib/esm/presets/prompt.js +197 -0
  20. package/lib/esm/presets/prompt.js.map +1 -0
  21. package/lib/esm/presets/skill-collection.js +77 -0
  22. package/lib/esm/presets/skill-collection.js.map +1 -0
  23. package/lib/types/index.d.ts +1 -1
  24. package/lib/types/index.d.ts.map +1 -1
  25. package/lib/types/plugin.d.ts.map +1 -1
  26. package/lib/types/presets/index.d.ts +2 -0
  27. package/lib/types/presets/index.d.ts.map +1 -1
  28. package/lib/types/presets/prompt.d.ts +77 -0
  29. package/lib/types/presets/prompt.d.ts.map +1 -0
  30. package/lib/types/presets/skill-collection.d.ts +26 -0
  31. package/lib/types/presets/skill-collection.d.ts.map +1 -0
  32. package/lib/types/types.d.ts +2 -0
  33. package/lib/types/types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/index.ts +8 -1
  36. package/src/plugin.ts +14 -6
  37. package/src/presets/index.ts +2 -0
  38. package/src/presets/prompt.ts +242 -0
  39. package/src/presets/skill-collection.ts +86 -0
  40. 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
  }