@vertesia/build-tools 0.24.0-dev.202601221707

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 (90) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +334 -0
  3. package/lib/build-tools.js +1730 -0
  4. package/lib/build-tools.js.map +1 -0
  5. package/lib/cjs/index.js +39 -0
  6. package/lib/cjs/index.js.map +1 -0
  7. package/lib/cjs/package.json +3 -0
  8. package/lib/cjs/parsers/frontmatter.js +25 -0
  9. package/lib/cjs/parsers/frontmatter.js.map +1 -0
  10. package/lib/cjs/plugin.js +150 -0
  11. package/lib/cjs/plugin.js.map +1 -0
  12. package/lib/cjs/presets/index.js +19 -0
  13. package/lib/cjs/presets/index.js.map +1 -0
  14. package/lib/cjs/presets/prompt.js +185 -0
  15. package/lib/cjs/presets/prompt.js.map +1 -0
  16. package/lib/cjs/presets/raw.js +25 -0
  17. package/lib/cjs/presets/raw.js.map +1 -0
  18. package/lib/cjs/presets/skill-collection.js +83 -0
  19. package/lib/cjs/presets/skill-collection.js.map +1 -0
  20. package/lib/cjs/presets/skill.js +224 -0
  21. package/lib/cjs/presets/skill.js.map +1 -0
  22. package/lib/cjs/types.js +6 -0
  23. package/lib/cjs/types.js.map +1 -0
  24. package/lib/cjs/utils/asset-copy.js +61 -0
  25. package/lib/cjs/utils/asset-copy.js.map +1 -0
  26. package/lib/cjs/utils/asset-discovery.js +100 -0
  27. package/lib/cjs/utils/asset-discovery.js.map +1 -0
  28. package/lib/cjs/utils/widget-compiler.js +115 -0
  29. package/lib/cjs/utils/widget-compiler.js.map +1 -0
  30. package/lib/esm/index.js +26 -0
  31. package/lib/esm/index.js.map +1 -0
  32. package/lib/esm/parsers/frontmatter.js +19 -0
  33. package/lib/esm/parsers/frontmatter.js.map +1 -0
  34. package/lib/esm/plugin.js +144 -0
  35. package/lib/esm/plugin.js.map +1 -0
  36. package/lib/esm/presets/index.js +8 -0
  37. package/lib/esm/presets/index.js.map +1 -0
  38. package/lib/esm/presets/prompt.js +181 -0
  39. package/lib/esm/presets/prompt.js.map +1 -0
  40. package/lib/esm/presets/raw.js +22 -0
  41. package/lib/esm/presets/raw.js.map +1 -0
  42. package/lib/esm/presets/skill-collection.js +77 -0
  43. package/lib/esm/presets/skill-collection.js.map +1 -0
  44. package/lib/esm/presets/skill.js +221 -0
  45. package/lib/esm/presets/skill.js.map +1 -0
  46. package/lib/esm/types.js +5 -0
  47. package/lib/esm/types.js.map +1 -0
  48. package/lib/esm/utils/asset-copy.js +54 -0
  49. package/lib/esm/utils/asset-copy.js.map +1 -0
  50. package/lib/esm/utils/asset-discovery.js +94 -0
  51. package/lib/esm/utils/asset-discovery.js.map +1 -0
  52. package/lib/esm/utils/widget-compiler.js +76 -0
  53. package/lib/esm/utils/widget-compiler.js.map +1 -0
  54. package/lib/types/index.d.ts +24 -0
  55. package/lib/types/index.d.ts.map +1 -0
  56. package/lib/types/parsers/frontmatter.d.ts +19 -0
  57. package/lib/types/parsers/frontmatter.d.ts.map +1 -0
  58. package/lib/types/plugin.d.ts +10 -0
  59. package/lib/types/plugin.d.ts.map +1 -0
  60. package/lib/types/presets/index.d.ts +8 -0
  61. package/lib/types/presets/index.d.ts.map +1 -0
  62. package/lib/types/presets/prompt.d.ts +63 -0
  63. package/lib/types/presets/prompt.d.ts.map +1 -0
  64. package/lib/types/presets/raw.d.ts +16 -0
  65. package/lib/types/presets/raw.d.ts.map +1 -0
  66. package/lib/types/presets/skill-collection.d.ts +26 -0
  67. package/lib/types/presets/skill-collection.d.ts.map +1 -0
  68. package/lib/types/presets/skill.d.ts +139 -0
  69. package/lib/types/presets/skill.d.ts.map +1 -0
  70. package/lib/types/types.d.ts +115 -0
  71. package/lib/types/types.d.ts.map +1 -0
  72. package/lib/types/utils/asset-copy.d.ts +20 -0
  73. package/lib/types/utils/asset-copy.d.ts.map +1 -0
  74. package/lib/types/utils/asset-discovery.d.ts +38 -0
  75. package/lib/types/utils/asset-discovery.d.ts.map +1 -0
  76. package/lib/types/utils/widget-compiler.d.ts +15 -0
  77. package/lib/types/utils/widget-compiler.d.ts.map +1 -0
  78. package/package.json +69 -0
  79. package/src/index.ts +52 -0
  80. package/src/parsers/frontmatter.ts +32 -0
  81. package/src/plugin.ts +166 -0
  82. package/src/presets/index.ts +8 -0
  83. package/src/presets/prompt.ts +227 -0
  84. package/src/presets/raw.ts +24 -0
  85. package/src/presets/skill-collection.ts +86 -0
  86. package/src/presets/skill.ts +271 -0
  87. package/src/types.ts +140 -0
  88. package/src/utils/asset-copy.ts +63 -0
  89. package/src/utils/asset-discovery.ts +138 -0
  90. package/src/utils/widget-compiler.ts +98 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Skill transformer preset for markdown files with frontmatter
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import type { TransformerPreset } from '../types.js';
7
+ import { parseFrontmatter } from '../parsers/frontmatter.js';
8
+ import { discoverSkillAssets } from '../utils/asset-discovery.js';
9
+
10
+ /**
11
+ * Content type for skill instructions
12
+ */
13
+ export type SkillContentType = 'md' | 'jst';
14
+
15
+ /**
16
+ * Context triggers for auto-injection of skills (for frontmatter validation)
17
+ */
18
+ const SkillContextTriggersFrontmatterSchema = z.object({
19
+ keywords: z.array(z.string()).optional(),
20
+ tool_names: z.array(z.string()).optional(),
21
+ data_patterns: z.array(z.string()).optional()
22
+ }).strict();
23
+
24
+ /**
25
+ * Context triggers for auto-injection of skills (for output validation)
26
+ */
27
+ const SkillContextTriggersSchema = z.object({
28
+ keywords: z.array(z.string()).optional(),
29
+ tool_names: z.array(z.string()).optional(),
30
+ data_patterns: z.array(z.string()).optional()
31
+ }).optional();
32
+
33
+ /**
34
+ * Execution configuration for skills that need code execution (for frontmatter validation)
35
+ */
36
+ const SkillExecutionFrontmatterSchema = z.object({
37
+ language: z.string(),
38
+ packages: z.array(z.string()).optional(),
39
+ system_packages: z.array(z.string()).optional(),
40
+ template: z.string().optional()
41
+ }).strict();
42
+
43
+ /**
44
+ * Execution configuration for skills that need code execution (for output validation)
45
+ */
46
+ const SkillExecutionSchema = z.object({
47
+ language: z.string(),
48
+ packages: z.array(z.string()).optional(),
49
+ system_packages: z.array(z.string()).optional(),
50
+ template: z.string().optional()
51
+ }).optional();
52
+
53
+ /**
54
+ * Zod schema for skill frontmatter validation
55
+ * This validates the YAML frontmatter before transformation
56
+ * Supports both flat and nested structures
57
+ */
58
+ const SkillFrontmatterSchema = z.object({
59
+ // Required fields
60
+ name: z.string().min(1, 'Skill name is required'),
61
+ description: z.string().min(1, 'Skill description is required'),
62
+
63
+ // Optional fields
64
+ title: z.string().optional(),
65
+ content_type: z.enum(['md', 'jst']).optional(),
66
+
67
+ // Flat structure fields (legacy)
68
+ keywords: z.array(z.string()).optional(),
69
+ tools: z.array(z.string()).optional(),
70
+ data_patterns: z.array(z.string()).optional(),
71
+ language: z.string().optional(),
72
+ packages: z.array(z.string()).optional(),
73
+ system_packages: z.array(z.string()).optional(),
74
+
75
+ // Nested structure fields
76
+ context_triggers: SkillContextTriggersFrontmatterSchema.optional(),
77
+ execution: SkillExecutionFrontmatterSchema.optional(),
78
+ related_tools: z.array(z.string()).optional(),
79
+ input_schema: z.object({
80
+ type: z.literal('object'),
81
+ properties: z.record(z.any()).optional(),
82
+ required: z.array(z.string()).optional()
83
+ }).optional(),
84
+
85
+ // Asset fields (auto-discovered but can be overridden)
86
+ scripts: z.array(z.string()).optional(),
87
+ widgets: z.array(z.string()).optional()
88
+ }).strict();
89
+
90
+ /**
91
+ * MUST be kept in sync with @vertesia/tools-sdk SkillDefinition
92
+ * Zod schema for skill definition
93
+ * This validates the structure of skill objects generated from markdown
94
+ * Matches the SkillDefinition interface from @vertesia/tools-sdk
95
+ */
96
+ export const SkillDefinitionSchema = z.object({
97
+ name: z.string().min(1, 'Skill name is required'),
98
+ title: z.string().optional(),
99
+ description: z.string().min(1, 'Skill description is required'),
100
+ instructions: z.string(),
101
+ content_type: z.enum(['md', 'jst']),
102
+ input_schema: z.object({
103
+ type: z.literal('object'),
104
+ properties: z.record(z.any()).optional(),
105
+ required: z.array(z.string()).optional()
106
+ }).optional(),
107
+ context_triggers: SkillContextTriggersSchema,
108
+ execution: SkillExecutionSchema,
109
+ related_tools: z.array(z.string()).optional(),
110
+ scripts: z.array(z.string()).optional(),
111
+ widgets: z.array(z.string()).optional()
112
+ });
113
+
114
+ /**
115
+ * TypeScript type inferred from the Zod schema
116
+ * Can also be imported from consumer packages for type safety
117
+ */
118
+ export type SkillDefinition = z.infer<typeof SkillDefinitionSchema>;
119
+
120
+ /**
121
+ * Build a SkillDefinition from frontmatter and markdown content.
122
+ * This mirrors the logic in @vertesia/tools-sdk parseSkillFile function.
123
+ *
124
+ * Supports two frontmatter structures:
125
+ *
126
+ * 1. Flat structure (matches parseSkillFile in tools-sdk):
127
+ * keywords: [...]
128
+ * tools: [...]
129
+ * language: python
130
+ * packages: [...]
131
+ *
132
+ * 2. Nested structure (for more explicit YAML):
133
+ * context_triggers:
134
+ * keywords: [...]
135
+ * tool_names: [...]
136
+ * execution:
137
+ * language: python
138
+ * packages: [...]
139
+ * related_tools: [...]
140
+ *
141
+ * @param frontmatter - Parsed frontmatter object
142
+ * @param instructions - Markdown content (body of the file)
143
+ * @param contentType - Content type ('md' or 'jst')
144
+ * @param widgets - Discovered widget names
145
+ * @param scripts - Discovered script names
146
+ * @returns Skill definition object
147
+ */
148
+ function buildSkillDefinition(
149
+ frontmatter: Record<string, any>,
150
+ instructions: string,
151
+ contentType: SkillContentType,
152
+ widgets: string[],
153
+ scripts: string[]
154
+ ): SkillDefinition {
155
+ const skill: SkillDefinition = {
156
+ name: frontmatter.name,
157
+ title: frontmatter.title,
158
+ description: frontmatter.description,
159
+ instructions,
160
+ content_type: contentType,
161
+ widgets: widgets.length > 0 ? widgets : undefined,
162
+ scripts: scripts.length > 0 ? scripts : undefined,
163
+ };
164
+
165
+ // Build context triggers - support both flat and nested structure
166
+ // Nested: context_triggers: { keywords: [...], tool_names: [...] }
167
+ // Flat: keywords: [...], tools: [...]
168
+ const contextTriggers = frontmatter.context_triggers;
169
+ const hasNestedTriggers = contextTriggers && typeof contextTriggers === 'object';
170
+ const hasFlatTriggers = frontmatter.keywords || frontmatter.tools || frontmatter.data_patterns;
171
+
172
+ if (hasNestedTriggers || hasFlatTriggers) {
173
+ skill.context_triggers = {
174
+ keywords: hasNestedTriggers ? contextTriggers.keywords : frontmatter.keywords,
175
+ tool_names: hasNestedTriggers ? contextTriggers.tool_names : frontmatter.tools,
176
+ data_patterns: hasNestedTriggers ? contextTriggers.data_patterns : frontmatter.data_patterns,
177
+ };
178
+ }
179
+
180
+ // Build execution config - support both flat and nested structure
181
+ const execution = frontmatter.execution;
182
+ const hasNestedExecution = execution && typeof execution === 'object';
183
+ const hasFlatExecution = frontmatter.language;
184
+
185
+ if (hasNestedExecution || hasFlatExecution) {
186
+ skill.execution = {
187
+ language: hasNestedExecution ? execution.language : frontmatter.language,
188
+ packages: hasNestedExecution ? execution.packages : frontmatter.packages,
189
+ system_packages: hasNestedExecution ? execution.system_packages : frontmatter.system_packages,
190
+ };
191
+
192
+ // Extract code template from instructions if present
193
+ const codeBlockMatch = instructions.match(/```(?:python|javascript|typescript|js|ts|py)\n([\s\S]*?)```/);
194
+ if (codeBlockMatch) {
195
+ skill.execution.template = codeBlockMatch[1].trim();
196
+ }
197
+ }
198
+
199
+ // Related tools - support both direct field and from tools field
200
+ if (frontmatter.related_tools) {
201
+ skill.related_tools = frontmatter.related_tools;
202
+ } else if (frontmatter.tools && !hasNestedTriggers) {
203
+ // If tools is not part of context_triggers, use it as related_tools
204
+ skill.related_tools = frontmatter.tools;
205
+ }
206
+
207
+ // Input schema from frontmatter
208
+ if (frontmatter.input_schema) {
209
+ skill.input_schema = frontmatter.input_schema;
210
+ }
211
+
212
+ return skill;
213
+ }
214
+
215
+ /**
216
+ * Skill transformer preset
217
+ * Transforms markdown files with ?skill suffix OR SKILL.md files into skill definition objects
218
+ *
219
+ * Matches:
220
+ * - Files with ?skill suffix: ./my-skill.md?skill
221
+ * - SKILL.md files: ./my-skill/SKILL.md
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import skill1 from './my-skill.md?skill';
226
+ * import skill2 from './my-skill/SKILL.md';
227
+ * // Both are SkillDefinition objects
228
+ * ```
229
+ */
230
+ export const skillTransformer: TransformerPreset = {
231
+ pattern: /(\.md\?skill$|\/SKILL\.md$)/,
232
+ schema: SkillDefinitionSchema,
233
+ transform: (content: string, filePath: string) => {
234
+ const { frontmatter, content: markdown } = parseFrontmatter(content);
235
+
236
+ // Validate frontmatter first to catch unknown properties
237
+ const frontmatterValidation = SkillFrontmatterSchema.safeParse(frontmatter);
238
+ if (!frontmatterValidation.success) {
239
+ const errors = frontmatterValidation.error.errors
240
+ .map((err) => {
241
+ const path = err.path.length > 0 ? err.path.join('.') : 'frontmatter';
242
+ return ` - ${path}: ${err.message}`;
243
+ })
244
+ .join('\n');
245
+ throw new Error(
246
+ `Invalid frontmatter in ${filePath}:\n${errors}`
247
+ );
248
+ }
249
+
250
+ // Determine content type from frontmatter or file extension
251
+ const content_type: SkillContentType = frontmatter.content_type || 'md';
252
+
253
+ // Discover assets (scripts and widgets) in the skill directory
254
+ const assets = discoverSkillAssets(filePath);
255
+
256
+ // Build skill definition using the same logic as parseSkillFile in tools-sdk
257
+ const skillData = buildSkillDefinition(
258
+ frontmatter,
259
+ markdown,
260
+ content_type,
261
+ assets.widgets,
262
+ assets.scripts
263
+ );
264
+
265
+ return {
266
+ data: skillData,
267
+ assets: assets.assetFiles,
268
+ widgets: assets.widgetMetadata
269
+ };
270
+ }
271
+ };
package/src/types.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Type definitions for the Vertesia Rollup Import Plugin
3
+ */
4
+
5
+ import type { Plugin } from 'rollup';
6
+ import type { z } from 'zod';
7
+
8
+ /**
9
+ * Asset file to be copied during build
10
+ */
11
+ export interface AssetFile {
12
+ /** Source file path (absolute) */
13
+ sourcePath: string;
14
+
15
+ /** Relative destination path within assets directory */
16
+ destPath: string;
17
+
18
+ /** Asset type for categorization */
19
+ type: 'script';
20
+ }
21
+
22
+ /**
23
+ * Result of a transform function
24
+ */
25
+ export interface TransformResult {
26
+ /** The data to export (can be text or JSON object) */
27
+ data: unknown;
28
+
29
+ /** Optional: additional imports to inject at the top of the generated module */
30
+ imports?: string[];
31
+
32
+ /** Optional: custom code to generate instead of default JSON export */
33
+ code?: string;
34
+
35
+ /** Optional: additional asset files to copy */
36
+ assets?: AssetFile[];
37
+
38
+ /** Optional: widget metadata for compilation */
39
+ widgets?: Array<{ name: string; path: string }>;
40
+ }
41
+
42
+ /**
43
+ * Transform function that converts file content into exportable data
44
+ */
45
+ export type TransformFunction = (
46
+ content: string,
47
+ filePath: string
48
+ ) => TransformResult | Promise<TransformResult>;
49
+
50
+ /**
51
+ * Configuration for a single import transformer rule
52
+ */
53
+ export interface TransformerRule {
54
+ /** Pattern to match import paths (e.g., /\.md\?skill$/ or /\?raw$/) */
55
+ pattern: RegExp;
56
+
57
+ /** Transform function to convert file content */
58
+ transform: TransformFunction;
59
+
60
+ /** Optional: Zod schema for validation */
61
+ schema?: z.ZodType<any>;
62
+
63
+ /** Optional: If true, the transformer generates virtual modules (no file to read) */
64
+ virtual?: boolean;
65
+
66
+ /** Optional: additional options for this transformer */
67
+ options?: Record<string, unknown>;
68
+ }
69
+
70
+ /**
71
+ * Widget compilation configuration
72
+ */
73
+ export interface WidgetConfig {
74
+ /**
75
+ * External dependencies that should not be bundled
76
+ * Default: ['react', 'react-dom', 'react/jsx-runtime']
77
+ */
78
+ external?: string[];
79
+
80
+ /**
81
+ * Path to tsconfig.json for widget compilation
82
+ * Default: './tsconfig.json'
83
+ */
84
+ tsconfig?: string;
85
+
86
+ /**
87
+ * Additional options to pass to @rollup/plugin-typescript
88
+ */
89
+ typescript?: Record<string, unknown>;
90
+
91
+ /**
92
+ * Minify widget output
93
+ * Default: false
94
+ */
95
+ minify?: boolean;
96
+ }
97
+
98
+ /**
99
+ * Plugin configuration
100
+ */
101
+ export interface PluginConfig {
102
+ /** Array of transformer rules to apply */
103
+ transformers: TransformerRule[];
104
+
105
+ /**
106
+ * Root directory for asset output (scripts, widgets, etc.)
107
+ * - If specified: assets will be copied to this directory
108
+ * - If false: asset copying is disabled
109
+ * - Default: './dist'
110
+ */
111
+ assetsDir?: string | false;
112
+
113
+ /**
114
+ * Directory for script files relative to assetsDir
115
+ * Default: 'scripts'
116
+ */
117
+ scriptsDir?: string;
118
+
119
+ /**
120
+ * Directory for widget files relative to assetsDir
121
+ * Default: 'widgets'
122
+ */
123
+ widgetsDir?: string;
124
+
125
+ /**
126
+ * Widget compilation configuration
127
+ * If provided, discovered widgets will be automatically compiled
128
+ */
129
+ widgetConfig?: WidgetConfig;
130
+ }
131
+
132
+ /**
133
+ * Type for transformer presets
134
+ */
135
+ export type TransformerPreset = TransformerRule;
136
+
137
+ /**
138
+ * Plugin factory return type
139
+ */
140
+ export type VertesiaImportPlugin = (config: PluginConfig) => Plugin;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Utilities for copying asset files during build
3
+ */
4
+
5
+ import { copyFileSync, mkdirSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import type { AssetFile } from '../types.js';
8
+
9
+ /**
10
+ * Ensure a directory exists, creating it recursively if needed
11
+ */
12
+ function ensureDirectory(dirPath: string): void {
13
+ try {
14
+ mkdirSync(dirPath, { recursive: true });
15
+ } catch (error) {
16
+ // Ignore if directory already exists
17
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
18
+ throw error;
19
+ }
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Copy an asset file to its destination
25
+ *
26
+ * @param asset - Asset file information
27
+ * @param assetsRoot - Root directory for assets
28
+ */
29
+ export function copyAssetFile(asset: AssetFile, assetsRoot: string): void {
30
+ const destPath = path.join(assetsRoot, asset.destPath);
31
+ const destDir = path.dirname(destPath);
32
+
33
+ // Ensure destination directory exists
34
+ ensureDirectory(destDir);
35
+
36
+ // Copy file
37
+ try {
38
+ copyFileSync(asset.sourcePath, destPath);
39
+ } catch (error) {
40
+ throw new Error(
41
+ `Failed to copy asset from ${asset.sourcePath} to ${destPath}: ${error instanceof Error ? error.message : String(error)
42
+ }`
43
+ );
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Copy multiple asset files
49
+ *
50
+ * @param assets - Array of asset files to copy
51
+ * @param assetsRoot - Root directory for assets
52
+ * @returns Number of files copied
53
+ */
54
+ export function copyAssets(assets: AssetFile[], assetsRoot: string): number {
55
+ let copied = 0;
56
+
57
+ for (const asset of assets) {
58
+ copyAssetFile(asset, assetsRoot);
59
+ copied++;
60
+ }
61
+
62
+ return copied;
63
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Utilities for discovering asset files (scripts, widgets) in skill directories
3
+ */
4
+
5
+ import { readdirSync, statSync } from 'node:fs';
6
+ import path from 'node:path';
7
+ import type { AssetFile } from '../types.js';
8
+
9
+ /**
10
+ * Widget metadata for compilation
11
+ */
12
+ export interface WidgetMetadata {
13
+ /** Widget name (without .tsx extension) */
14
+ name: string;
15
+
16
+ /** Absolute path to widget file */
17
+ path: string;
18
+ }
19
+
20
+ /**
21
+ * Discovered assets in a skill directory
22
+ */
23
+ export interface DiscoveredAssets {
24
+ /** Script file names (with extensions: .js, .py) */
25
+ scripts: string[];
26
+
27
+ /** Widget file names (without .tsx extension) */
28
+ widgets: string[];
29
+
30
+ /** Widget metadata for compilation */
31
+ widgetMetadata: WidgetMetadata[];
32
+
33
+ /** Asset files to be copied */
34
+ assetFiles: AssetFile[];
35
+ }
36
+
37
+ /**
38
+ * Check if a file exists and is a regular file
39
+ */
40
+ function isFile(filePath: string): boolean {
41
+ try {
42
+ return statSync(filePath).isFile();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Get all files in a directory (non-recursive)
50
+ */
51
+ function getFilesInDirectory(dirPath: string): string[] {
52
+ try {
53
+ return readdirSync(dirPath).filter(file => {
54
+ const fullPath = path.join(dirPath, file);
55
+ return isFile(fullPath);
56
+ });
57
+ } catch {
58
+ return [];
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Check if a file is a script file (.js or .py)
64
+ */
65
+ function isScriptFile(fileName: string): boolean {
66
+ return /\.(js|py)$/.test(fileName);
67
+ }
68
+
69
+ /**
70
+ * Check if a file is a widget file (.tsx)
71
+ */
72
+ function isWidgetFile(fileName: string): boolean {
73
+ return /\.tsx$/.test(fileName);
74
+ }
75
+
76
+ /**
77
+ * Extract widget name from .tsx file (remove extension)
78
+ */
79
+ function getWidgetName(fileName: string): string {
80
+ return fileName.replace(/\.tsx$/, '');
81
+ }
82
+
83
+ /**
84
+ * Discover assets (scripts and widgets) in a skill directory
85
+ *
86
+ * @param skillFilePath - Absolute path to the skill.md file
87
+ * @param options - Asset discovery options
88
+ * @returns Discovered assets and metadata
89
+ */
90
+ export function discoverSkillAssets(
91
+ skillFilePath: string,
92
+ options: {
93
+ scriptsDir?: string;
94
+ widgetsDir?: string;
95
+ } = {}
96
+ ): DiscoveredAssets {
97
+ const skillDir = path.dirname(skillFilePath);
98
+ const files = getFilesInDirectory(skillDir);
99
+
100
+ const scripts: string[] = [];
101
+ const widgets: string[] = [];
102
+ const widgetMetadata: WidgetMetadata[] = [];
103
+ const assetFiles: AssetFile[] = [];
104
+
105
+ const scriptsDir = options.scriptsDir || 'scripts';
106
+
107
+ for (const file of files) {
108
+ const fullPath = path.join(skillDir, file);
109
+
110
+ if (isScriptFile(file)) {
111
+ // Script file (.js or .py)
112
+ scripts.push(file);
113
+ assetFiles.push({
114
+ sourcePath: fullPath,
115
+ destPath: path.join(scriptsDir, file),
116
+ type: 'script'
117
+ });
118
+ } else if (isWidgetFile(file)) {
119
+ // Widget file (.tsx)
120
+ const widgetName = getWidgetName(file);
121
+ widgets.push(widgetName);
122
+ widgetMetadata.push({
123
+ name: widgetName,
124
+ path: fullPath
125
+ });
126
+
127
+ // Note: We don't add widget .tsx files to assetFiles
128
+ // Widgets are compiled by the plugin if widgetConfig is provided
129
+ }
130
+ }
131
+
132
+ return {
133
+ scripts,
134
+ widgets,
135
+ widgetMetadata,
136
+ assetFiles
137
+ };
138
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Widget compilation utility using Rollup
3
+ */
4
+
5
+ import { rollup, type RollupOptions, type Plugin } from 'rollup';
6
+ import path from 'node:path';
7
+ import type { WidgetConfig } from '../types.js';
8
+ import type { WidgetMetadata } from './asset-discovery.js';
9
+
10
+ /**
11
+ * Default external dependencies for widgets
12
+ */
13
+ const DEFAULT_EXTERNALS = [
14
+ 'react',
15
+ 'react-dom',
16
+ 'react/jsx-runtime',
17
+ 'react/jsx-dev-runtime',
18
+ 'react-dom/client'
19
+ ];
20
+
21
+ /**
22
+ * Compile widgets using Rollup
23
+ *
24
+ * @param widgets - Array of widget metadata to compile
25
+ * @param outputDir - Directory to write compiled widgets
26
+ * @param config - Widget compilation configuration
27
+ * @returns Number of widgets compiled
28
+ */
29
+ export async function compileWidgets(
30
+ widgets: WidgetMetadata[],
31
+ outputDir: string,
32
+ config: WidgetConfig = {}
33
+ ): Promise<number> {
34
+ if (widgets.length === 0) {
35
+ return 0;
36
+ }
37
+
38
+ const {
39
+ external = DEFAULT_EXTERNALS,
40
+ tsconfig = './tsconfig.json',
41
+ typescript: typescriptOptions = {},
42
+ minify = false
43
+ } = config;
44
+
45
+ // Build each widget separately to get individual bundles
46
+ const buildPromises = widgets.map(async (widget) => {
47
+ // Dynamically import plugins - use any to bypass TypeScript module resolution issues
48
+ const typescript = (await import('@rollup/plugin-typescript' as any)).default as any;
49
+ const nodeResolve = (await import('@rollup/plugin-node-resolve' as any)).default as any;
50
+ const commonjs = (await import('@rollup/plugin-commonjs' as any)).default as any;
51
+
52
+ const plugins: Plugin[] = [
53
+ typescript({
54
+ tsconfig,
55
+ declaration: false,
56
+ sourceMap: true,
57
+ ...typescriptOptions
58
+ }),
59
+ nodeResolve({
60
+ browser: true,
61
+ preferBuiltins: false,
62
+ extensions: ['.tsx', '.ts', '.jsx', '.js']
63
+ }),
64
+ commonjs()
65
+ ];
66
+
67
+ // Add minification if requested
68
+ if (minify) {
69
+ const { terser } = await import('rollup-plugin-terser' as any);
70
+ plugins.push(
71
+ terser({
72
+ compress: {
73
+ drop_console: false
74
+ }
75
+ })
76
+ );
77
+ }
78
+
79
+ const rollupConfig: RollupOptions = {
80
+ input: widget.path,
81
+ output: {
82
+ file: path.join(outputDir, `${widget.name}.js`),
83
+ format: 'es',
84
+ sourcemap: true,
85
+ inlineDynamicImports: true
86
+ },
87
+ external,
88
+ plugins
89
+ };
90
+
91
+ const bundle = await rollup(rollupConfig);
92
+ await bundle.write(rollupConfig.output as any);
93
+ await bundle.close();
94
+ });
95
+
96
+ await Promise.all(buildPromises);
97
+ return widgets.length;
98
+ }