@vertesia/build-tools 1.3.0 → 1.4.0-dev.20260615.042033Z

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 (220) hide show
  1. package/README.md +3 -3
  2. package/lib/bin/build.d.ts +35 -0
  3. package/lib/bin/build.d.ts.map +1 -0
  4. package/lib/bin/build.js +79 -0
  5. package/lib/bin/build.js.map +1 -0
  6. package/lib/bin/config.d.ts +24 -0
  7. package/lib/bin/config.d.ts.map +1 -0
  8. package/lib/bin/config.js +91 -0
  9. package/lib/bin/config.js.map +1 -0
  10. package/lib/core/compilers/widget.d.ts +27 -0
  11. package/lib/core/compilers/widget.d.ts.map +1 -0
  12. package/lib/core/compilers/widget.js +37 -0
  13. package/lib/core/compilers/widget.js.map +1 -0
  14. package/lib/{types → core}/parsers/frontmatter.d.ts +1 -1
  15. package/lib/core/parsers/frontmatter.d.ts.map +1 -0
  16. package/lib/{esm → core}/parsers/frontmatter.js +1 -1
  17. package/lib/core/parsers/frontmatter.js.map +1 -0
  18. package/lib/core/transformers/index.d.ts +10 -0
  19. package/lib/core/transformers/index.d.ts.map +1 -0
  20. package/lib/core/transformers/index.js +10 -0
  21. package/lib/core/transformers/index.js.map +1 -0
  22. package/lib/{types/presets → core/transformers}/prompt.d.ts +7 -21
  23. package/lib/core/transformers/prompt.d.ts.map +1 -0
  24. package/lib/{esm/presets → core/transformers}/prompt.js +17 -14
  25. package/lib/core/transformers/prompt.js.map +1 -0
  26. package/lib/core/transformers/raw.d.ts.map +1 -0
  27. package/lib/{esm/presets → core/transformers}/raw.js +2 -2
  28. package/lib/core/transformers/raw.js.map +1 -0
  29. package/lib/core/transformers/skill-collection.d.ts.map +1 -0
  30. package/lib/{esm/presets → core/transformers}/skill-collection.js +5 -12
  31. package/lib/core/transformers/skill-collection.js.map +1 -0
  32. package/lib/core/transformers/skill.d.ts +111 -0
  33. package/lib/core/transformers/skill.d.ts.map +1 -0
  34. package/lib/{esm/presets → core/transformers}/skill.js +70 -51
  35. package/lib/core/transformers/skill.js.map +1 -0
  36. package/lib/core/transformers/template-collection.d.ts.map +1 -0
  37. package/lib/{esm/presets → core/transformers}/template-collection.js +5 -12
  38. package/lib/core/transformers/template-collection.js.map +1 -0
  39. package/lib/{types/presets → core/transformers}/template.d.ts +7 -22
  40. package/lib/core/transformers/template.d.ts.map +1 -0
  41. package/lib/{esm/presets → core/transformers}/template.js +16 -11
  42. package/lib/core/transformers/template.js.map +1 -0
  43. package/lib/{types → core}/types.d.ts +5 -61
  44. package/lib/core/types.d.ts.map +1 -0
  45. package/lib/core/types.js +6 -0
  46. package/lib/{cjs → core}/types.js.map +1 -1
  47. package/lib/core/utils/asset-copy.d.ts.map +1 -0
  48. package/lib/core/utils/asset-copy.js.map +1 -0
  49. package/lib/core/utils/asset-discovery.d.ts.map +1 -0
  50. package/lib/{esm → core}/utils/asset-discovery.js +4 -4
  51. package/lib/core/utils/asset-discovery.js.map +1 -0
  52. package/lib/core/utils/template-asset-discovery.d.ts.map +1 -0
  53. package/lib/{esm → core}/utils/template-asset-discovery.js +3 -7
  54. package/lib/core/utils/template-asset-discovery.js.map +1 -0
  55. package/lib/import-transform/builtins.d.ts +21 -0
  56. package/lib/import-transform/builtins.d.ts.map +1 -0
  57. package/lib/import-transform/builtins.js +50 -0
  58. package/lib/import-transform/builtins.js.map +1 -0
  59. package/lib/import-transform/chunk-emitter.d.ts +24 -0
  60. package/lib/import-transform/chunk-emitter.d.ts.map +1 -0
  61. package/lib/import-transform/chunk-emitter.js +35 -0
  62. package/lib/import-transform/chunk-emitter.js.map +1 -0
  63. package/lib/import-transform/detector.d.ts +24 -0
  64. package/lib/import-transform/detector.d.ts.map +1 -0
  65. package/lib/import-transform/detector.js +34 -0
  66. package/lib/import-transform/detector.js.map +1 -0
  67. package/lib/import-transform/index.d.ts +47 -0
  68. package/lib/import-transform/index.d.ts.map +1 -0
  69. package/lib/import-transform/index.js +115 -0
  70. package/lib/import-transform/index.js.map +1 -0
  71. package/lib/import-transform/patterns.d.ts +25 -0
  72. package/lib/import-transform/patterns.d.ts.map +1 -0
  73. package/lib/import-transform/patterns.js +27 -0
  74. package/lib/import-transform/patterns.js.map +1 -0
  75. package/lib/import-transform/resolver.d.ts +23 -0
  76. package/lib/import-transform/resolver.d.ts.map +1 -0
  77. package/lib/import-transform/resolver.js +30 -0
  78. package/lib/import-transform/resolver.js.map +1 -0
  79. package/lib/import-transform/rewriter.d.ts +21 -0
  80. package/lib/import-transform/rewriter.d.ts.map +1 -0
  81. package/lib/import-transform/rewriter.js +30 -0
  82. package/lib/import-transform/rewriter.js.map +1 -0
  83. package/lib/import-transform/scanner.d.ts +17 -0
  84. package/lib/import-transform/scanner.d.ts.map +1 -0
  85. package/lib/import-transform/scanner.js +46 -0
  86. package/lib/import-transform/scanner.js.map +1 -0
  87. package/lib/index.d.ts +39 -0
  88. package/lib/index.d.ts.map +1 -0
  89. package/lib/index.js +43 -0
  90. package/lib/index.js.map +1 -0
  91. package/lib/vite/api-server.d.ts +54 -0
  92. package/lib/vite/api-server.d.ts.map +1 -0
  93. package/lib/vite/api-server.js +94 -0
  94. package/lib/vite/api-server.js.map +1 -0
  95. package/lib/vite/dev-server.d.ts +42 -0
  96. package/lib/vite/dev-server.d.ts.map +1 -0
  97. package/lib/vite/dev-server.js +111 -0
  98. package/lib/vite/dev-server.js.map +1 -0
  99. package/lib/vite/index.d.ts +15 -0
  100. package/lib/vite/index.d.ts.map +1 -0
  101. package/lib/vite/index.js +15 -0
  102. package/lib/vite/index.js.map +1 -0
  103. package/package.json +38 -28
  104. package/src/bin/build.ts +83 -0
  105. package/src/bin/config.ts +113 -0
  106. package/src/core/compilers/widget.ts +69 -0
  107. package/src/{parsers → core/parsers}/frontmatter.ts +2 -2
  108. package/src/core/transformers/index.ts +27 -0
  109. package/src/{presets → core/transformers}/prompt.ts +35 -32
  110. package/src/{presets → core/transformers}/raw.ts +2 -2
  111. package/src/{presets → core/transformers}/skill-collection.ts +5 -12
  112. package/src/{presets → core/transformers}/skill.ts +119 -99
  113. package/src/{presets → core/transformers}/template-collection.ts +76 -83
  114. package/src/{presets → core/transformers}/template.ts +121 -116
  115. package/src/core/types.ts +71 -0
  116. package/src/{utils → core/utils}/asset-copy.ts +3 -2
  117. package/src/{utils → core/utils}/asset-discovery.ts +5 -5
  118. package/src/{utils → core/utils}/template-asset-discovery.ts +70 -77
  119. package/src/import-transform/builtins.ts +56 -0
  120. package/src/import-transform/chunk-emitter.ts +60 -0
  121. package/src/import-transform/detector.ts +56 -0
  122. package/src/import-transform/index.ts +171 -0
  123. package/src/import-transform/patterns.ts +34 -0
  124. package/src/import-transform/resolver.ts +57 -0
  125. package/src/import-transform/rewriter.ts +48 -0
  126. package/src/import-transform/scanner.ts +55 -0
  127. package/src/index.ts +67 -42
  128. package/src/vite/api-server.ts +142 -0
  129. package/src/vite/dev-server.ts +128 -0
  130. package/src/vite/index.ts +15 -0
  131. package/lib/build-tools.js +0 -2054
  132. package/lib/build-tools.js.map +0 -1
  133. package/lib/cjs/index.js +0 -43
  134. package/lib/cjs/index.js.map +0 -1
  135. package/lib/cjs/package.json +0 -3
  136. package/lib/cjs/parsers/frontmatter.js +0 -25
  137. package/lib/cjs/parsers/frontmatter.js.map +0 -1
  138. package/lib/cjs/plugin.js +0 -150
  139. package/lib/cjs/plugin.js.map +0 -1
  140. package/lib/cjs/presets/index.js +0 -25
  141. package/lib/cjs/presets/index.js.map +0 -1
  142. package/lib/cjs/presets/prompt.js +0 -185
  143. package/lib/cjs/presets/prompt.js.map +0 -1
  144. package/lib/cjs/presets/raw.js +0 -25
  145. package/lib/cjs/presets/raw.js.map +0 -1
  146. package/lib/cjs/presets/skill-collection.js +0 -83
  147. package/lib/cjs/presets/skill-collection.js.map +0 -1
  148. package/lib/cjs/presets/skill.js +0 -272
  149. package/lib/cjs/presets/skill.js.map +0 -1
  150. package/lib/cjs/presets/template-collection.js +0 -80
  151. package/lib/cjs/presets/template-collection.js.map +0 -1
  152. package/lib/cjs/presets/template.js +0 -105
  153. package/lib/cjs/presets/template.js.map +0 -1
  154. package/lib/cjs/types.js +0 -6
  155. package/lib/cjs/utils/asset-copy.js +0 -61
  156. package/lib/cjs/utils/asset-copy.js.map +0 -1
  157. package/lib/cjs/utils/asset-discovery.js +0 -100
  158. package/lib/cjs/utils/asset-discovery.js.map +0 -1
  159. package/lib/cjs/utils/template-asset-discovery.js +0 -63
  160. package/lib/cjs/utils/template-asset-discovery.js.map +0 -1
  161. package/lib/cjs/utils/widget-compiler.js +0 -115
  162. package/lib/cjs/utils/widget-compiler.js.map +0 -1
  163. package/lib/cjs/vite.js +0 -45
  164. package/lib/cjs/vite.js.map +0 -1
  165. package/lib/esm/index.js +0 -26
  166. package/lib/esm/index.js.map +0 -1
  167. package/lib/esm/parsers/frontmatter.js.map +0 -1
  168. package/lib/esm/plugin.js +0 -144
  169. package/lib/esm/plugin.js.map +0 -1
  170. package/lib/esm/presets/index.js +0 -10
  171. package/lib/esm/presets/index.js.map +0 -1
  172. package/lib/esm/presets/prompt.js.map +0 -1
  173. package/lib/esm/presets/raw.js.map +0 -1
  174. package/lib/esm/presets/skill-collection.js.map +0 -1
  175. package/lib/esm/presets/skill.js.map +0 -1
  176. package/lib/esm/presets/template-collection.js.map +0 -1
  177. package/lib/esm/presets/template.js.map +0 -1
  178. package/lib/esm/types.js +0 -5
  179. package/lib/esm/types.js.map +0 -1
  180. package/lib/esm/utils/asset-copy.js.map +0 -1
  181. package/lib/esm/utils/asset-discovery.js.map +0 -1
  182. package/lib/esm/utils/template-asset-discovery.js.map +0 -1
  183. package/lib/esm/utils/widget-compiler.js +0 -76
  184. package/lib/esm/utils/widget-compiler.js.map +0 -1
  185. package/lib/esm/vite.js +0 -42
  186. package/lib/esm/vite.js.map +0 -1
  187. package/lib/types/index.d.ts +0 -24
  188. package/lib/types/index.d.ts.map +0 -1
  189. package/lib/types/parsers/frontmatter.d.ts.map +0 -1
  190. package/lib/types/plugin.d.ts +0 -10
  191. package/lib/types/plugin.d.ts.map +0 -1
  192. package/lib/types/presets/index.d.ts +0 -10
  193. package/lib/types/presets/index.d.ts.map +0 -1
  194. package/lib/types/presets/prompt.d.ts.map +0 -1
  195. package/lib/types/presets/raw.d.ts.map +0 -1
  196. package/lib/types/presets/skill-collection.d.ts.map +0 -1
  197. package/lib/types/presets/skill.d.ts +0 -361
  198. package/lib/types/presets/skill.d.ts.map +0 -1
  199. package/lib/types/presets/template-collection.d.ts.map +0 -1
  200. package/lib/types/presets/template.d.ts.map +0 -1
  201. package/lib/types/types.d.ts.map +0 -1
  202. package/lib/types/utils/asset-copy.d.ts.map +0 -1
  203. package/lib/types/utils/asset-discovery.d.ts.map +0 -1
  204. package/lib/types/utils/template-asset-discovery.d.ts.map +0 -1
  205. package/lib/types/utils/widget-compiler.d.ts +0 -15
  206. package/lib/types/utils/widget-compiler.d.ts.map +0 -1
  207. package/lib/types/vite.d.ts +0 -32
  208. package/lib/types/vite.d.ts.map +0 -1
  209. package/src/plugin.ts +0 -166
  210. package/src/presets/index.ts +0 -10
  211. package/src/types.ts +0 -140
  212. package/src/utils/widget-compiler.ts +0 -98
  213. package/src/vite.ts +0 -45
  214. /package/lib/{types/presets → core/transformers}/raw.d.ts +0 -0
  215. /package/lib/{types/presets → core/transformers}/skill-collection.d.ts +0 -0
  216. /package/lib/{types/presets → core/transformers}/template-collection.d.ts +0 -0
  217. /package/lib/{types → core}/utils/asset-copy.d.ts +0 -0
  218. /package/lib/{esm → core}/utils/asset-copy.js +0 -0
  219. /package/lib/{types → core}/utils/asset-discovery.d.ts +0 -0
  220. /package/lib/{types → core}/utils/template-asset-discovery.d.ts +0 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Finds Vertesia query-style import specifiers inside a JavaScript source
3
+ * file and maps each one to the transformer that should handle it.
4
+ *
5
+ * The detector is regex-based and works on `tsc` output where import
6
+ * specifiers are preserved literally. It identifies any string literal that
7
+ * contains a known marker (`?skill`, `?raw`, …, or `/SKILL.md`) and then
8
+ * matches the specifier against the configured transformer patterns.
9
+ */
10
+
11
+ import type { TransformerRule } from '../core/types.js';
12
+ import { QUERY_STRING_LITERAL } from './patterns.js';
13
+
14
+ export interface ImportOccurrence {
15
+ /** The transformer whose pattern matched the specifier. */
16
+ transformer: TransformerRule;
17
+
18
+ /** The original specifier text (the value between the quotes). */
19
+ specifier: string;
20
+
21
+ /** Offset in the source where the opening quote begins. */
22
+ quoteStart: number;
23
+
24
+ /** Offset in the source where the closing quote ends (exclusive). */
25
+ quoteEnd: number;
26
+
27
+ /** The quote character used in the source. */
28
+ quote: "'" | '"' | '`';
29
+ }
30
+
31
+ export function detectQueryImports(content: string, transformers: TransformerRule[]): ImportOccurrence[] {
32
+ const occurrences: ImportOccurrence[] = [];
33
+ QUERY_STRING_LITERAL.lastIndex = 0;
34
+
35
+ let match: RegExpExecArray | null;
36
+ // biome-ignore lint/suspicious/noAssignInExpressions: standard regex.exec loop
37
+ while ((match = QUERY_STRING_LITERAL.exec(content)) !== null) {
38
+ const quote = match[1] as "'" | '"' | '`';
39
+ const specifier = match[2];
40
+
41
+ for (const transformer of transformers) {
42
+ if (transformer.pattern.test(specifier)) {
43
+ occurrences.push({
44
+ transformer,
45
+ specifier,
46
+ quoteStart: match.index,
47
+ quoteEnd: match.index + match[0].length,
48
+ quote,
49
+ });
50
+ break;
51
+ }
52
+ }
53
+ }
54
+
55
+ return occurrences;
56
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Standalone import transformer for Vertesia query-style imports.
3
+ *
4
+ * Operates directly on the JavaScript output of `tsc`:
5
+ *
6
+ * 1. Walk `libDir` for `.js` files containing query imports.
7
+ * 2. For each occurrence, resolve back to the corresponding source asset
8
+ * under `srcDir`, run the transformer, and write the generated module
9
+ * as a sibling chunk in `libDir`.
10
+ * 3. Rewrite the importing file in place so the original `?skill` /
11
+ * `SKILL.md` specifier points at the new chunk.
12
+ * 4. Copy any transformer-declared assets (scripts, handlebars, …) and
13
+ * bundle any discovered widget `.tsx` siblings via esbuild.
14
+ *
15
+ * Emitted chunks are re-scanned for nested query imports (e.g. a `?skills`
16
+ * collection chunk that imports `SKILL.md` siblings), so the pipeline
17
+ * matches rollup's recursive resolveId/load behavior.
18
+ */
19
+
20
+ import path from 'node:path';
21
+ import { compileWidgets, type WidgetCompilerConfig } from '../core/compilers/widget.js';
22
+ import type { AssetFile, TransformerRule } from '../core/types.js';
23
+ import { copyAssets } from '../core/utils/asset-copy.js';
24
+ import type { WidgetMetadata } from '../core/utils/asset-discovery.js';
25
+ import { emitChunk } from './chunk-emitter.js';
26
+ import { detectQueryImports } from './detector.js';
27
+ import { SNIFF_PATTERN } from './patterns.js';
28
+ import { resolveImport } from './resolver.js';
29
+ import { type ImportReplacement, writeRewrittenFile } from './rewriter.js';
30
+ import { scanLibForQueryImports } from './scanner.js';
31
+
32
+ export interface TransformImportsOptions {
33
+ /** Root of the compiled JavaScript output (e.g. `./lib`). */
34
+ libDir: string;
35
+
36
+ /** Root of the original sources, mirroring `libDir` (e.g. `./src`). */
37
+ srcDir: string;
38
+
39
+ /** Transformers to apply. */
40
+ transformers: TransformerRule[];
41
+
42
+ /**
43
+ * Root directory where script assets are copied and widgets are written.
44
+ * - String: assets and widgets are emitted under this directory.
45
+ * - `false`: asset copying and widget compilation are skipped.
46
+ * - Default: `libDir` (so widgets land in `lib/<widgetsDir>/`).
47
+ */
48
+ assetsDir?: string | false;
49
+
50
+ /** Sub-directory under `assetsDir` for widget bundles. Default: 'widgets'. */
51
+ widgetsDir?: string;
52
+
53
+ /** Configuration forwarded to the esbuild widget bundler. */
54
+ widgetConfig?: WidgetCompilerConfig;
55
+ }
56
+
57
+ export interface TransformImportsResult {
58
+ filesProcessed: number;
59
+ chunksEmitted: number;
60
+ assetsCopied: number;
61
+ widgetsCompiled: number;
62
+ }
63
+
64
+ interface WorkItem {
65
+ path: string;
66
+ content: string;
67
+ }
68
+
69
+ export async function transformImports(opts: TransformImportsOptions): Promise<TransformImportsResult> {
70
+ const { libDir, srcDir, transformers, widgetsDir = 'widgets', widgetConfig } = opts;
71
+ const assetsDir = opts.assetsDir === undefined ? libDir : opts.assetsDir;
72
+ const shouldCopyAssets = assetsDir !== false;
73
+ const shouldCompileWidgets = assetsDir !== false;
74
+
75
+ if (!transformers || transformers.length === 0) {
76
+ throw new Error('transformImports: At least one transformer must be configured');
77
+ }
78
+
79
+ const seenFiles = new Set<string>();
80
+ const workQueue: WorkItem[] = [];
81
+
82
+ for (const file of scanLibForQueryImports(libDir)) {
83
+ if (!seenFiles.has(file.path)) {
84
+ seenFiles.add(file.path);
85
+ workQueue.push(file);
86
+ }
87
+ }
88
+
89
+ const emittedChunks = new Set<string>();
90
+ const seenWidgets = new Map<string, WidgetMetadata>();
91
+ const assetsToCopy: AssetFile[] = [];
92
+ let chunksEmitted = 0;
93
+ let filesProcessed = 0;
94
+
95
+ while (workQueue.length > 0) {
96
+ const file = workQueue.shift() as WorkItem;
97
+ const occurrences = detectQueryImports(file.content, transformers);
98
+ if (occurrences.length === 0) {
99
+ continue;
100
+ }
101
+
102
+ const replacements: ImportReplacement[] = [];
103
+
104
+ for (const occ of occurrences) {
105
+ const resolved = resolveImport(file.path, occ.specifier, libDir, srcDir);
106
+
107
+ if (!emittedChunks.has(resolved.chunkLibPath)) {
108
+ const emitted = await emitChunk(resolved.resolvedSrcPath, resolved.chunkLibPath, occ.transformer);
109
+ emittedChunks.add(resolved.chunkLibPath);
110
+ chunksEmitted++;
111
+
112
+ if (emitted.assets.length > 0 && shouldCopyAssets) {
113
+ assetsToCopy.push(...emitted.assets);
114
+ }
115
+ if (emitted.widgets.length > 0 && shouldCompileWidgets) {
116
+ for (const widget of emitted.widgets) {
117
+ if (!seenWidgets.has(widget.path)) {
118
+ seenWidgets.set(widget.path, widget);
119
+ }
120
+ }
121
+ }
122
+
123
+ // Chunks may themselves contain query imports (e.g. ?skills
124
+ // collection chunks emit `import './foo/SKILL.md'` lines).
125
+ if (SNIFF_PATTERN.test(emitted.content) && !seenFiles.has(resolved.chunkLibPath)) {
126
+ seenFiles.add(resolved.chunkLibPath);
127
+ workQueue.push({ path: resolved.chunkLibPath, content: emitted.content });
128
+ }
129
+ }
130
+
131
+ replacements.push({
132
+ quoteStart: occ.quoteStart,
133
+ quoteEnd: occ.quoteEnd,
134
+ quote: occ.quote,
135
+ newSpecifier: resolved.chunkSpecifier,
136
+ });
137
+ }
138
+
139
+ if (writeRewrittenFile(file.path, file.content, replacements)) {
140
+ filesProcessed++;
141
+ }
142
+ }
143
+
144
+ let assetsCopied = 0;
145
+ if (shouldCopyAssets && assetsToCopy.length > 0) {
146
+ assetsCopied = copyAssets(assetsToCopy, assetsDir as string);
147
+ }
148
+
149
+ let widgetsCompiled = 0;
150
+ if (shouldCompileWidgets && seenWidgets.size > 0) {
151
+ const widgetInputs = Array.from(seenWidgets.values()).map((widget) => ({
152
+ name: widget.name,
153
+ entry: mapSrcWidgetToLib(widget.path, srcDir, libDir),
154
+ }));
155
+ const widgetOutput = path.join(assetsDir as string, widgetsDir);
156
+ widgetsCompiled = await compileWidgets(widgetInputs, widgetOutput, widgetConfig);
157
+ }
158
+
159
+ return { filesProcessed, chunksEmitted, assetsCopied, widgetsCompiled };
160
+ }
161
+
162
+ /**
163
+ * Map a widget `.tsx` source path to its tsc-compiled `.js` counterpart in lib/.
164
+ */
165
+ function mapSrcWidgetToLib(srcTsxPath: string, srcDir: string, libDir: string): string {
166
+ const srcRoot = path.resolve(srcDir);
167
+ const libRoot = path.resolve(libDir);
168
+ const rel = path.relative(srcRoot, srcTsxPath);
169
+ const libRel = rel.replace(/\.tsx$/, '.js');
170
+ return path.join(libRoot, libRel);
171
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared regex fragments used by the scanner, detector, and orchestrator to
3
+ * identify Vertesia query-style imports. Keep these in sync with the
4
+ * `pattern` field of each transformer in `core/transformers/`.
5
+ */
6
+
7
+ /**
8
+ * Suffix tokens for `?xxx` query imports.
9
+ */
10
+ export const QUERY_SUFFIXES = ['skill', 'raw', 'prompt', 'template', 'skills', 'templates'] as const;
11
+
12
+ /**
13
+ * Bare filename tokens that mark an import as a transform target without a
14
+ * `?query` suffix (e.g. `import skill from './my-skill/SKILL.md'`).
15
+ */
16
+ export const BARE_FILENAMES = ['SKILL.md', 'TEMPLATE.md'] as const;
17
+
18
+ const QUERY_GROUP = QUERY_SUFFIXES.join('|');
19
+ const BARE_GROUP = BARE_FILENAMES.map((name) => `\\/${name.replace('.', '\\.')}`).join('|');
20
+
21
+ /**
22
+ * Coarse sniff for whether a file body could contain any query-style imports.
23
+ * Cheap regex check before invoking the more expensive detector.
24
+ */
25
+ export const SNIFF_PATTERN = new RegExp(`\\?(?:${QUERY_GROUP})\\b|(?:${BARE_GROUP})\\b`);
26
+
27
+ /**
28
+ * Matches a quoted string literal whose contents end with a query marker.
29
+ * Capture group 1 is the quote character; capture group 2 is the specifier.
30
+ */
31
+ export const QUERY_STRING_LITERAL = new RegExp(
32
+ `(['"\`])([^'"\`]*?(?:\\?(?:${QUERY_GROUP})|${BARE_GROUP})[^'"\`]*)\\1`,
33
+ 'g',
34
+ );
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Resolves a query-style import specifier to:
3
+ * 1. the source file path in `src/` (where the original asset lives), and
4
+ * 2. the chunk path in `lib/` (where the generated module will be written),
5
+ * and the new relative specifier to write back into the importing file.
6
+ *
7
+ * Path mapping rule: lib/ and src/ are mirror trees, so a file at
8
+ * `<libDir>/<rel>` corresponds to `<srcDir>/<rel>`.
9
+ */
10
+
11
+ import path from 'node:path';
12
+
13
+ export interface ResolvedImport {
14
+ /** Specifier with any `?query` suffix stripped. */
15
+ cleanSpecifier: string;
16
+
17
+ /** Absolute lib/ path of the resolved (extension-preserved) target. */
18
+ resolvedLibPath: string;
19
+
20
+ /** Absolute src/ path of the same target. */
21
+ resolvedSrcPath: string;
22
+
23
+ /** Absolute lib/ path where the generated chunk will be written. */
24
+ chunkLibPath: string;
25
+
26
+ /** Relative specifier (importer-relative) that should replace the original. */
27
+ chunkSpecifier: string;
28
+ }
29
+
30
+ export function resolveImport(
31
+ importerLibPath: string,
32
+ specifier: string,
33
+ libDir: string,
34
+ srcDir: string,
35
+ ): ResolvedImport {
36
+ const queryIndex = specifier.indexOf('?');
37
+ const cleanSpecifier = queryIndex >= 0 ? specifier.substring(0, queryIndex) : specifier;
38
+
39
+ const importerDir = path.dirname(importerLibPath);
40
+ const resolvedLibPath = path.resolve(importerDir, cleanSpecifier);
41
+
42
+ const libRoot = path.resolve(libDir);
43
+ const srcRoot = path.resolve(srcDir);
44
+ const relFromLib = path.relative(libRoot, resolvedLibPath);
45
+ const resolvedSrcPath = path.join(srcRoot, relFromLib);
46
+
47
+ const chunkLibPath = `${resolvedLibPath}.js`;
48
+ const chunkSpecifier = `${cleanSpecifier}.js`;
49
+
50
+ return {
51
+ cleanSpecifier,
52
+ resolvedLibPath,
53
+ resolvedSrcPath,
54
+ chunkLibPath,
55
+ chunkSpecifier,
56
+ };
57
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Rewrites query-style import specifiers inside a JavaScript source file to
3
+ * point at the chunks emitted by the chunk-emitter.
4
+ *
5
+ * All replacements are intra-line (only the quoted string changes), which
6
+ * preserves line numbers — so existing source maps remain valid at the line
7
+ * level without re-emitting them.
8
+ */
9
+
10
+ import { writeFileSync } from 'node:fs';
11
+
12
+ export interface ImportReplacement {
13
+ /** Offset where the opening quote begins in the original content. */
14
+ quoteStart: number;
15
+
16
+ /** Offset where the closing quote ends (exclusive). */
17
+ quoteEnd: number;
18
+
19
+ /** Quote character to wrap the new specifier with. */
20
+ quote: "'" | '"' | '`';
21
+
22
+ /** Replacement specifier (without quotes). */
23
+ newSpecifier: string;
24
+ }
25
+
26
+ export function rewriteImports(content: string, replacements: ImportReplacement[]): string {
27
+ if (replacements.length === 0) {
28
+ return content;
29
+ }
30
+
31
+ const sorted = [...replacements].sort((a, b) => b.quoteStart - a.quoteStart);
32
+
33
+ let out = content;
34
+ for (const r of sorted) {
35
+ const literal = `${r.quote}${r.newSpecifier}${r.quote}`;
36
+ out = out.slice(0, r.quoteStart) + literal + out.slice(r.quoteEnd);
37
+ }
38
+ return out;
39
+ }
40
+
41
+ export function writeRewrittenFile(filePath: string, content: string, replacements: ImportReplacement[]): boolean {
42
+ if (replacements.length === 0) {
43
+ return false;
44
+ }
45
+ const next = rewriteImports(content, replacements);
46
+ writeFileSync(filePath, next, 'utf-8');
47
+ return true;
48
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Walks a compiled library directory to find candidate JavaScript files
3
+ * that may contain Vertesia query-style imports (`?skill`, `?raw`, …) or
4
+ * `SKILL.md` imports.
5
+ *
6
+ * Returns absolute paths to `.js` files whose content matches a quick
7
+ * content sniff for any of the trigger tokens, avoiding a re-read in the
8
+ * detector.
9
+ */
10
+
11
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
12
+ import path from 'node:path';
13
+ import { SNIFF_PATTERN } from './patterns.js';
14
+
15
+ export interface ScannedFile {
16
+ /** Absolute path to the file. */
17
+ path: string;
18
+
19
+ /** File content, captured during scanning. */
20
+ content: string;
21
+ }
22
+
23
+ export function scanLibForQueryImports(libDir: string): ScannedFile[] {
24
+ const results: ScannedFile[] = [];
25
+ walk(libDir, results);
26
+ return results;
27
+ }
28
+
29
+ function walk(dir: string, out: ScannedFile[]): void {
30
+ let entries: string[];
31
+ try {
32
+ entries = readdirSync(dir);
33
+ } catch {
34
+ return;
35
+ }
36
+
37
+ for (const entry of entries) {
38
+ const full = path.join(dir, entry);
39
+ let stats: ReturnType<typeof statSync>;
40
+ try {
41
+ stats = statSync(full);
42
+ } catch {
43
+ continue;
44
+ }
45
+
46
+ if (stats.isDirectory()) {
47
+ walk(full, out);
48
+ } else if (stats.isFile() && full.endsWith('.js')) {
49
+ const content = readFileSync(full, 'utf-8');
50
+ if (SNIFF_PATTERN.test(content)) {
51
+ out.push({ path: full, content });
52
+ }
53
+ }
54
+ }
55
+ }
package/src/index.ts CHANGED
@@ -1,57 +1,82 @@
1
1
  /**
2
- * Vertesia Rollup Import Plugin
2
+ * Vertesia Build Tools
3
3
  *
4
- * A flexible Rollup plugin for transforming imports with custom compilers and validation.
5
- * Supports preset transformers for common use cases (skills, raw files) and custom transformers.
4
+ * Transformers for custom import syntaxes (`?skill`, `?raw`, `?prompt`,
5
+ * `?template`, `?skills`, `?templates`, and bare `SKILL.md` / `TEMPLATE.md`),
6
+ * a standalone CLI (`vertesia-build`) that runs them as a post-`tsc` step,
7
+ * and Vite plugins for dev-mode integration.
8
+ *
9
+ * Two consumer entry points:
10
+ *
11
+ * - **Build-time:** invoke the `vertesia-build` CLI from your package
12
+ * scripts (after `tsc`). Config lives under `vertesia-build` in your
13
+ * `package.json`. The CLI calls `transformImports()` internally — you
14
+ * can also call it directly if you need finer control.
15
+ *
16
+ * - **Dev-time (Vite):** import `vertesiaDevServerPlugin` (or
17
+ * `apiServerPlugin` for full Hono tool-server wiring) from
18
+ * `@vertesia/build-tools/vite`. Same transformers, same behavior,
19
+ * applied to source files at request time.
6
20
  *
7
21
  * @example
8
22
  * ```typescript
9
- * import { vertesiaImportPlugin, skillTransformer, rawTransformer } from '@vertesia/build-tools';
23
+ * import { transformImports } from '@vertesia/build-tools';
10
24
  *
11
- * export default {
12
- * plugins: [
13
- * vertesiaImportPlugin({
14
- * transformers: [skillTransformer, rawTransformer]
15
- * })
16
- * ]
17
- * };
25
+ * await transformImports({
26
+ * libDir: './lib',
27
+ * srcDir: './src',
28
+ * transformers: ['skill', 'raw'],
29
+ * assetsDir: './dist',
30
+ * });
18
31
  * ```
19
32
  */
20
33
 
21
- // Core plugin
22
- export { vertesiaImportPlugin } from './plugin.js';
23
-
34
+ // esbuild-based widget bundler
35
+ export {
36
+ compileWidget,
37
+ compileWidgets,
38
+ type WidgetCompilerConfig,
39
+ type WidgetInput,
40
+ } from './core/compilers/widget.js';
41
+ // Parsers
42
+ export { type FrontmatterResult, parseFrontmatter } from './core/parsers/frontmatter.js';
43
+ // Transformers (the pure transformation functions)
44
+ export {
45
+ type PromptContentType,
46
+ type PromptDefinition,
47
+ PromptDefinitionSchema,
48
+ PromptRole,
49
+ promptTransformer,
50
+ type RenderingTemplateDefinition,
51
+ RenderingTemplateDefinitionSchema,
52
+ rawTransformer,
53
+ type SkillContentType,
54
+ type SkillDefinition,
55
+ SkillDefinitionSchema,
56
+ SkillPropertiesSchema,
57
+ skillCollectionTransformer,
58
+ skillTransformer,
59
+ TemplateType,
60
+ templateCollectionTransformer,
61
+ templateTransformer,
62
+ } from './core/transformers/index.js';
24
63
  // Types
25
64
  export type {
26
- PluginConfig,
27
- TransformerRule,
65
+ AssetFile,
28
66
  TransformerPreset,
67
+ TransformerRule,
29
68
  TransformFunction,
30
69
  TransformResult,
31
- AssetFile,
32
- WidgetConfig
33
- } from './types.js';
34
-
35
- // Presets
70
+ } from './core/types.js';
71
+ // CLI-friendly transformer name registry
36
72
  export {
37
- skillTransformer,
38
- rawTransformer,
39
- skillCollectionTransformer,
40
- templateTransformer,
41
- templateCollectionTransformer,
42
- promptTransformer,
43
- SkillDefinitionSchema,
44
- SkillPropertiesSchema,
45
- RenderingTemplateDefinitionSchema,
46
- PromptDefinitionSchema,
47
- type SkillDefinition,
48
- type SkillContentType,
49
- type RenderingTemplateDefinition,
50
- type PromptDefinition,
51
- type PromptContentType,
52
- PromptRole,
53
- TemplateType
54
- } from './presets/index.js';
55
-
56
- // Utilities
57
- export { parseFrontmatter, type FrontmatterResult } from './parsers/frontmatter.js';
73
+ BUILTIN_TRANSFORMER_NAMES,
74
+ BUILTIN_TRANSFORMERS,
75
+ resolveTransformerNames,
76
+ } from './import-transform/builtins.js';
77
+ // Standalone import transformer (tsc → vertesia-build pipeline)
78
+ export {
79
+ type TransformImportsOptions,
80
+ type TransformImportsResult,
81
+ transformImports,
82
+ } from './import-transform/index.js';
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Vite plugin set that mounts a Vertesia Hono tool server as middleware on
3
+ * the Vite dev server. Includes the Vertesia import transformers needed by
4
+ * `ssrLoadModule` to handle `?skill` / `?template` / `?prompt` / `?raw`
5
+ * imports in the tool server source.
6
+ *
7
+ * - `vite dev` → loads the server entry via `ssrLoadModule` (source,
8
+ * with HMR invalidation)
9
+ * - `vite preview` → loads the compiled server from `lib/` (validates
10
+ * production output)
11
+ *
12
+ * The Hono app is served on the same port as Vite under `/api`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // vite.config.ts
17
+ * import { defineConfig } from 'vite';
18
+ * import { apiServerPlugin } from '@vertesia/build-tools/vite';
19
+ *
20
+ * export default defineConfig({
21
+ * plugins: [
22
+ * apiServerPlugin({ entry: './src/tool-server/server.ts' }),
23
+ * ],
24
+ * });
25
+ * ```
26
+ */
27
+
28
+ import type { IncomingMessage, ServerResponse } from 'node:http';
29
+ import path from 'node:path';
30
+ import { getRequestListener } from '@hono/node-server';
31
+ import type { Plugin, ViteDevServer } from 'vite';
32
+ import { vertesiaDevServerPlugin } from './dev-server.js';
33
+
34
+ interface HonoApp {
35
+ fetch: (request: Request, env?: unknown, executionCtx?: unknown) => Response | Promise<Response>;
36
+ }
37
+
38
+ type NextHandleFunction = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
39
+
40
+ export interface ApiServerPluginOptions {
41
+ /**
42
+ * Tool server entry point (TypeScript source). Resolved by Vite from the
43
+ * project root in dev mode.
44
+ * @default './src/tool-server/server.ts'
45
+ */
46
+ entry?: string;
47
+
48
+ /**
49
+ * Compiled server module path used in preview mode. Resolved relative to
50
+ * `process.cwd()` (the consuming project's root when running `vite preview`).
51
+ * @default './lib/server.js'
52
+ */
53
+ compiledEntry?: string;
54
+
55
+ /**
56
+ * Path prefix that routes requests to the Hono server. Requests whose URL
57
+ * starts with this prefix are forwarded; others fall through to Vite.
58
+ * @default '/api'
59
+ */
60
+ apiPrefix?: string;
61
+
62
+ /**
63
+ * Names of transformers to enable in dev mode. Defaults to all built-ins
64
+ * (`skill`, `skills`, `template`, `templates`, `prompt`, `raw`).
65
+ */
66
+ transformers?: readonly string[];
67
+ }
68
+
69
+ export function apiServerPlugin(options: ApiServerPluginOptions = {}): Plugin[] {
70
+ const {
71
+ entry = './src/tool-server/server.ts',
72
+ compiledEntry = './lib/server.js',
73
+ apiPrefix = '/api',
74
+ transformers,
75
+ } = options;
76
+
77
+ // Resolve compiled entry relative to the consumer's cwd. Vite is run from
78
+ // the project root by convention, so this lands at the right file.
79
+ const absoluteCompiledEntry = path.resolve(process.cwd(), compiledEntry);
80
+
81
+ return [
82
+ // Vertesia query-import transformer (skill / raw / prompt / template etc.).
83
+ vertesiaDevServerPlugin(transformers ? { transformers } : undefined),
84
+
85
+ // Hono middleware bridge for dev + preview.
86
+ {
87
+ name: 'vertesia-api-server',
88
+
89
+ configureServer(server: ViteDevServer) {
90
+ server.middlewares.use(createDevListener(server, entry, apiPrefix));
91
+ },
92
+
93
+ configurePreviewServer(server) {
94
+ server.middlewares.use(createPreviewListener(absoluteCompiledEntry, apiPrefix));
95
+ },
96
+ },
97
+ ];
98
+ }
99
+
100
+ /**
101
+ * Connect-compatible middleware that loads the Hono app from source via
102
+ * Vite's `ssrLoadModule` (preserving HMR invalidation on changes).
103
+ */
104
+ function createDevListener(server: ViteDevServer, entry: string, apiPrefix: string): NextHandleFunction {
105
+ return async (req, res, next) => {
106
+ if (!req.url?.startsWith(apiPrefix)) {
107
+ return next();
108
+ }
109
+ try {
110
+ const mod = await server.ssrLoadModule(entry);
111
+ const app = mod.default as HonoApp;
112
+ const requestListener = getRequestListener(app.fetch);
113
+ void requestListener(req, res);
114
+ } catch (e) {
115
+ next(e);
116
+ }
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Connect-compatible middleware that loads the compiled Hono app from `lib/`
122
+ * for production validation. The compiled module is cached after first load
123
+ * (no HMR in preview mode).
124
+ */
125
+ function createPreviewListener(compiledEntry: string, apiPrefix: string): NextHandleFunction {
126
+ let appPromise: Promise<HonoApp> | null = null;
127
+ return async (req, res, next) => {
128
+ if (!req.url?.startsWith(apiPrefix)) {
129
+ return next();
130
+ }
131
+ try {
132
+ if (!appPromise) {
133
+ appPromise = import(compiledEntry).then((mod) => mod.default);
134
+ }
135
+ const app = await appPromise;
136
+ const requestListener = getRequestListener(app.fetch);
137
+ void requestListener(req, res);
138
+ } catch (e) {
139
+ next(e);
140
+ }
141
+ };
142
+ }