@hashrock/ono 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@hashrock/ono",
3
- "version": "0.1.1",
4
- "description": "A lightweight JSX library for static site generation with UnoCSS support",
3
+ "version": "0.1.3",
4
+ "description": "Minimalist SSG framework with JSX, powered by TypeScript's JSX transformer",
5
5
  "type": "module",
6
6
  "main": "src/jsx-runtime.js",
7
+ "exports": {
8
+ ".": "./src/jsx-runtime.js",
9
+ "./jsx-runtime": "./src/jsx-runtime.js",
10
+ "./renderer": "./src/renderer.js",
11
+ "./transformer": "./src/transformer.js",
12
+ "./bundler": "./src/bundler.js",
13
+ "./content": "./src/content.js",
14
+ "./browser/compiler": "./src/browser/compiler.js",
15
+ "./browser/unocss": "./src/browser/unocss.js"
16
+ },
7
17
  "bin": {
8
18
  "ono": "src/cli.js"
9
19
  },
@@ -14,7 +24,10 @@
14
24
  ],
15
25
  "scripts": {
16
26
  "test": "node --test test/**/*.test.js",
17
- "test:watch": "node --test --watch test/**/*.test.js"
27
+ "test:watch": "node --test --watch test/**/*.test.js",
28
+ "test:snapshot": "node --test test/**/*.snapshot.test.js",
29
+ "test:snapshot:update": "UPDATE_SNAPSHOTS=true node --test test/**/*.snapshot.test.js",
30
+ "test:all": "npm run test && npm run test:snapshot"
18
31
  },
19
32
  "keywords": [
20
33
  "jsx",
@@ -38,9 +51,10 @@
38
51
  "engines": {
39
52
  "node": ">=18.0.0"
40
53
  },
41
- "devDependencies": {},
42
54
  "dependencies": {
43
55
  "@unocss/preset-uno": "^66.5.4",
56
+ "h3": "^2.0.1-rc.5",
57
+ "marked": "^16.4.1",
44
58
  "typescript": "^5.9.3",
45
59
  "unocss": "^66.5.4",
46
60
  "ws": "^8.18.3"
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Browser compiler utilities shared with the REPL worker.
3
+ */
4
+
5
+ import { transformJSX } from '../transformer.js';
6
+ import { renderToString } from '../renderer.js';
7
+ import { h } from '../jsx-runtime.js';
8
+ import { getUnoGenerator } from './unocss.js';
9
+
10
+ function resolveImport(from, to) {
11
+ if (to.startsWith('./') || to.startsWith('../')) {
12
+ const fromParts = from.split('/').slice(0, -1);
13
+ const targetParts = to.split('/');
14
+ const resolved = [...fromParts];
15
+
16
+ for (const part of targetParts) {
17
+ if (!part || part === '.') continue;
18
+ if (part === '..') {
19
+ resolved.pop();
20
+ } else {
21
+ resolved.push(part);
22
+ }
23
+ }
24
+
25
+ return resolved.join('/');
26
+ }
27
+
28
+ return to;
29
+ }
30
+
31
+ function topoSortModules(files, entryPoint) {
32
+ const filenames = Object.keys(files);
33
+ const dependencyMap = new Map();
34
+ const importPattern = /import\s+(?:[\s\S]+?)?from\s+['"]([^'"]+)['"]/g;
35
+
36
+ for (const filename of filenames) {
37
+ const source = files[filename] || '';
38
+ const deps = [];
39
+ source.replace(importPattern, (match, specifier) => {
40
+ const resolved = resolveImport(filename, specifier);
41
+ if (files[resolved]) {
42
+ deps.push(resolved);
43
+ }
44
+ return match;
45
+ });
46
+ dependencyMap.set(filename, deps);
47
+ }
48
+
49
+ const visited = new Set();
50
+ const order = [];
51
+
52
+ const visit = (file) => {
53
+ if (!files[file] || visited.has(file)) return;
54
+ visited.add(file);
55
+ const deps = dependencyMap.get(file) || [];
56
+ for (const dep of deps) {
57
+ visit(dep);
58
+ }
59
+ order.push(file);
60
+ };
61
+
62
+ if (entryPoint) {
63
+ visit(entryPoint);
64
+ }
65
+
66
+ for (const filename of filenames) {
67
+ if (!visited.has(filename)) {
68
+ visit(filename);
69
+ }
70
+ }
71
+
72
+ return order;
73
+ }
74
+
75
+ function bundleModules(files, entryPoint) {
76
+ const order = topoSortModules(files, entryPoint);
77
+ let bundledCode = 'const __modules = {};\n';
78
+
79
+ for (const filename of order) {
80
+ const transformedCode = transformJSX(files[filename], filename);
81
+ let code = transformedCode;
82
+ const exportMappings = new Map();
83
+
84
+ const addExport = (exportName, localName = exportName) => {
85
+ if (!exportName || exportMappings.has(exportName)) return;
86
+ exportMappings.set(exportName, localName);
87
+ };
88
+
89
+ code = code.replace(/import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g, (match, imports, path) => {
90
+ const resolvedPath = resolveImport(filename, path);
91
+ const importNames = imports.split(',').map(part => part.trim()).filter(Boolean);
92
+
93
+ return importNames
94
+ .map(name => {
95
+ const [local, alias] = name.split(/\s+as\s+/).map(token => token && token.trim());
96
+ const localName = alias || local;
97
+ const exportName = local;
98
+ return `const ${localName} = __modules['${resolvedPath}']['${exportName}'];`;
99
+ })
100
+ .join('\n');
101
+ });
102
+
103
+ code = code.replace(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
104
+ const resolvedPath = resolveImport(filename, path);
105
+ return `const ${localName} = __modules['${resolvedPath}']['default'];`;
106
+ });
107
+
108
+ code = code.replace(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, (match, localName, path) => {
109
+ const resolvedPath = resolveImport(filename, path);
110
+ return `const ${localName} = __modules['${resolvedPath}'];`;
111
+ });
112
+
113
+ code = code.replace(/export\s+default\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
114
+ addExport('default', name);
115
+ return `function ${name}`;
116
+ });
117
+
118
+ code = code.replace(/export\s+default\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
119
+ addExport('default', name);
120
+ return `${name}`;
121
+ });
122
+
123
+ code = code.replace(/export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g, (match, kind, name) => {
124
+ addExport(name);
125
+ return `${kind} ${name}`;
126
+ });
127
+
128
+ code = code.replace(/export\s+function\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
129
+ addExport(name);
130
+ return `function ${name}`;
131
+ });
132
+
133
+ code = code.replace(/export\s+class\s+([A-Za-z_$][\w$]*)/g, (match, name) => {
134
+ addExport(name);
135
+ return `class ${name}`;
136
+ });
137
+
138
+ code = code.replace(/export\s*{\s*([^}]+)\s*};?/g, (match, names) => {
139
+ names
140
+ .split(',')
141
+ .map(part => part.trim())
142
+ .filter(Boolean)
143
+ .forEach(part => {
144
+ const [local, alias] = part.split(/\s+as\s+/).map(token => token && token.trim());
145
+ addExport(alias || local, local);
146
+ });
147
+ return '';
148
+ });
149
+
150
+ code = code.replace(/export\s*{\s*};?/g, '');
151
+
152
+ if (filename === entryPoint) {
153
+ const functionMatches = code.matchAll(/function\s+([A-Za-z_$][\w$]*)/g);
154
+ for (const match of functionMatches) {
155
+ const name = match[1];
156
+ addExport(name);
157
+ }
158
+ }
159
+
160
+ const exportEntries = Array.from(exportMappings.entries()).map(([exportName, localName]) => {
161
+ if (exportName === 'default') {
162
+ return `'default': ${localName}`;
163
+ }
164
+ if (exportName === localName) {
165
+ return exportName;
166
+ }
167
+ return `'${exportName}': ${localName}`;
168
+ });
169
+
170
+ const exportsObject = exportEntries.length > 0 ? `{ ${exportEntries.join(', ')} }` : '{}';
171
+
172
+ bundledCode += `
173
+ __modules['${filename}'] = (() => {
174
+ ${code}
175
+ return ${exportsObject};
176
+ })();
177
+ `;
178
+ }
179
+
180
+ bundledCode += `const __entry = __modules['${entryPoint}'];\n`;
181
+
182
+ return bundledCode;
183
+ }
184
+
185
+ function extractRenderCandidate(entryModule, entryCode) {
186
+ if (!entryModule || typeof entryModule !== 'object') {
187
+ return null;
188
+ }
189
+
190
+ const defaultExport = entryModule.default;
191
+ if (typeof defaultExport === 'function') {
192
+ return defaultExport;
193
+ }
194
+ if (defaultExport !== undefined) {
195
+ return () => defaultExport;
196
+ }
197
+
198
+ const lastCallMatch = entryCode.match(/([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*$/m);
199
+ if (lastCallMatch) {
200
+ const lastCallName = lastCallMatch[1];
201
+ const candidate = entryModule[lastCallName];
202
+ if (typeof candidate === 'function') {
203
+ return candidate;
204
+ }
205
+ if (candidate !== undefined) {
206
+ return () => candidate;
207
+ }
208
+ }
209
+
210
+ const fallbackFunction = Object.values(entryModule).find(value => typeof value === 'function');
211
+ if (fallbackFunction) {
212
+ return fallbackFunction;
213
+ }
214
+
215
+ const firstValue = Object.values(entryModule)[0];
216
+ if (firstValue !== undefined) {
217
+ return () => firstValue;
218
+ }
219
+
220
+ return null;
221
+ }
222
+
223
+ function evaluateEntryModule(bundledCode) {
224
+ const getEntryModule = new Function('h', `
225
+ ${bundledCode}
226
+ return __entry;
227
+ `);
228
+
229
+ return getEntryModule(h);
230
+ }
231
+
232
+ export async function compileProject(files, entryPoint = 'index.jsx', options = {}) {
233
+ const bundled = bundleModules(files, entryPoint);
234
+ const entryModule = evaluateEntryModule(bundled);
235
+ const entryCode = files[entryPoint] || '';
236
+ const renderCandidate = extractRenderCandidate(entryModule, entryCode);
237
+
238
+ if (!renderCandidate) {
239
+ throw new Error('Unable to determine a render function in the entry module.');
240
+ }
241
+
242
+ const vnode = renderCandidate();
243
+ const html = renderToString(vnode);
244
+
245
+ let css = '';
246
+ if (options.enableUno !== false) {
247
+ const uno = await getUnoGenerator(options.unoConfig);
248
+ const result = await uno.generate(html, { preflights: true });
249
+ css = result.css;
250
+ }
251
+
252
+ return { html, css };
253
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Lightweight UnoCSS helpers for browser environments.
3
+ */
4
+
5
+ import { createGenerator } from '@unocss/core';
6
+ import { presetUno } from '@unocss/preset-uno';
7
+
8
+ let cachedGeneratorKey = null;
9
+ let cachedGeneratorPromise = null;
10
+
11
+ function serializeConfig(config = {}) {
12
+ try {
13
+ return JSON.stringify(config);
14
+ } catch {
15
+ return '__dynamic__';
16
+ }
17
+ }
18
+
19
+ export async function getUnoGenerator(config = {}) {
20
+ const key = serializeConfig(config);
21
+ if (!cachedGeneratorPromise || cachedGeneratorKey !== key) {
22
+ cachedGeneratorKey = key;
23
+ cachedGeneratorPromise = createGenerator({
24
+ presets: [presetUno()],
25
+ ...config,
26
+ });
27
+ }
28
+ return cachedGeneratorPromise;
29
+ }
package/src/builder.js ADDED
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Build utilities for Ono SSG
3
+ */
4
+ import { readFile, writeFile, mkdir, readdir, stat } from "node:fs/promises";
5
+ import { resolve, join, dirname, basename, relative, extname } from "node:path";
6
+ import { transformJSX } from "./transformer.js";
7
+ import { renderToString } from "./renderer.js";
8
+ import { generateCSSFromFiles } from "./unocss.js";
9
+ import { bundle } from "./bundler.js";
10
+
11
+ /**
12
+ * Check if a route is dynamic (contains [param])
13
+ */
14
+ export function isDynamicRoute(filePath) {
15
+ return /\[([^\]]+)\]/.test(filePath);
16
+ }
17
+
18
+ // Inline JSX runtime for bundled output
19
+ const INLINE_JSX_RUNTIME = `
20
+ function flattenChildren(children) {
21
+ const result = [];
22
+ for (const child of children) {
23
+ if (child === null || child === undefined || typeof child === 'boolean') continue;
24
+ if (Array.isArray(child)) {
25
+ result.push(...flattenChildren(child));
26
+ } else {
27
+ result.push(child);
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+ function h(tag, props, ...children) {
33
+ return { tag, props: props || {}, children: flattenChildren(children) };
34
+ }
35
+ `;
36
+
37
+ /**
38
+ * Build a single JSX file
39
+ */
40
+ export async function buildFile(inputFile, options = {}) {
41
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
42
+
43
+ const outDir = resolve(process.cwd(), outputDir);
44
+ const resolvedInput = resolve(process.cwd(), inputFile);
45
+
46
+ // Bundle the file with all its dependencies
47
+ const bundledCode = await bundle(resolvedInput);
48
+
49
+ // Add inline JSX runtime
50
+ const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
51
+
52
+ // Write transformed JS temporarily
53
+ const tempFile = join(outDir, `_temp_${Date.now()}.js`);
54
+ await mkdir(dirname(tempFile), { recursive: true });
55
+ await writeFile(tempFile, codeWithRuntime);
56
+
57
+ // Import and render
58
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
59
+ const module = await import(moduleUrl.href);
60
+
61
+ const App = module.default;
62
+ if (!App) {
63
+ throw new Error(`No default export found in ${inputFile}`);
64
+ }
65
+
66
+ let vnode = typeof App === "function" ? App({}) : App;
67
+ if (vnode instanceof Promise) {
68
+ vnode = await vnode;
69
+ }
70
+
71
+ const html = renderToString(vnode);
72
+
73
+ // Determine output path
74
+ const baseName = basename(inputFile, ".jsx");
75
+ const outputFileName = baseName === "index" ? "index.html" : `${baseName}.html`;
76
+ const outputPath = join(outDir, outputFileName);
77
+
78
+ await mkdir(dirname(outputPath), { recursive: true });
79
+ await writeFile(outputPath, html);
80
+
81
+ if (!silent) {
82
+ console.log(`✓ Built successfully: ${relative(process.cwd(), outputPath)}`);
83
+ }
84
+
85
+ // Clean up temp file
86
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
87
+
88
+ return { outputPath, html };
89
+ }
90
+
91
+ /**
92
+ * Build a dynamic route (e.g., [slug].jsx)
93
+ */
94
+ export async function buildDynamicRoute(inputFile, options = {}) {
95
+ const { outputDir = "dist", silent = false } = options;
96
+
97
+ const outDir = resolve(process.cwd(), outputDir);
98
+ const resolvedInput = resolve(process.cwd(), inputFile);
99
+
100
+ // Bundle the file with all its dependencies
101
+ const bundledCode = await bundle(resolvedInput);
102
+
103
+ // Add inline JSX runtime
104
+ const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
105
+
106
+ // Write transformed JS temporarily
107
+ const tempFile = join(outDir, `_temp_${Date.now()}.js`);
108
+ await mkdir(dirname(tempFile), { recursive: true });
109
+ await writeFile(tempFile, codeWithRuntime);
110
+
111
+ // Import module
112
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
113
+ const module = await import(moduleUrl.href);
114
+
115
+ if (!module.getStaticPaths) {
116
+ throw new Error(
117
+ `Dynamic route ${inputFile} must export a getStaticPaths function`
118
+ );
119
+ }
120
+
121
+ const pathsData = await module.getStaticPaths();
122
+ const paths = Array.isArray(pathsData) ? pathsData : pathsData.paths || [];
123
+
124
+ const App = module.default;
125
+ if (!App) {
126
+ throw new Error(`No default export found in ${inputFile}`);
127
+ }
128
+
129
+ const outputs = [];
130
+
131
+ for (const pathData of paths) {
132
+ const params = pathData.params || {};
133
+
134
+ let vnode = typeof App === "function" ? App({ params }) : App;
135
+ if (vnode instanceof Promise) {
136
+ vnode = await vnode;
137
+ }
138
+
139
+ const html = renderToString(vnode);
140
+
141
+ // Determine output path from params
142
+ const routeDir = dirname(relative(join(process.cwd(), "pages"), resolvedInput));
143
+ const fileName = basename(resolvedInput, ".jsx");
144
+
145
+ // Replace [param] with actual value
146
+ let outputPath = fileName;
147
+ for (const [key, value] of Object.entries(params)) {
148
+ outputPath = outputPath.replace(`[${key}]`, value);
149
+ }
150
+
151
+ const fullOutputPath = join(
152
+ outDir,
153
+ routeDir === "." ? "" : routeDir,
154
+ `${outputPath}.html`
155
+ );
156
+
157
+ await mkdir(dirname(fullOutputPath), { recursive: true });
158
+ await writeFile(fullOutputPath, html);
159
+
160
+ outputs.push({ outputPath: fullOutputPath, html, params });
161
+
162
+ if (!silent) {
163
+ console.log(` ✓ ${relative(process.cwd(), fullOutputPath)}`);
164
+ }
165
+ }
166
+
167
+ // Clean up temp file
168
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
169
+
170
+ return outputs;
171
+ }
172
+
173
+ /**
174
+ * Build multiple JSX files
175
+ */
176
+ export async function buildFiles(inputPattern, options = {}) {
177
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
178
+
179
+ const pagesDir = resolve(process.cwd(), inputPattern);
180
+ const files = await getAllJSXFiles(pagesDir);
181
+
182
+ if (!silent) {
183
+ console.log(`Found ${files.length} page(s) in ${inputPattern}/\n`);
184
+ }
185
+
186
+ const results = [];
187
+
188
+ for (const file of files) {
189
+ if (isDynamicRoute(file)) {
190
+ if (!silent) {
191
+ const relativePath = relative(process.cwd(), file);
192
+ const pathsData = await getDynamicRoutePaths(file);
193
+ const count = Array.isArray(pathsData) ? pathsData.length : pathsData.paths?.length || 0;
194
+ console.log(`Building dynamic route ${relativePath} (${count} pages)...`);
195
+ }
196
+ const outputs = await buildDynamicRoute(file, { outputDir, silent: true });
197
+ results.push(...outputs);
198
+ } else {
199
+ if (!silent) {
200
+ console.log(`Building ${relative(process.cwd(), file)}...`);
201
+ }
202
+ const result = await buildFile(file, { outputDir, unocssConfig, silent: true });
203
+ results.push(result);
204
+ }
205
+ }
206
+
207
+ return results;
208
+ }
209
+
210
+ /**
211
+ * Get all JSX files recursively
212
+ */
213
+ async function getAllJSXFiles(dir) {
214
+ const files = [];
215
+ const entries = await readdir(dir, { withFileTypes: true });
216
+
217
+ for (const entry of entries) {
218
+ const fullPath = join(dir, entry.name);
219
+
220
+ if (entry.isDirectory()) {
221
+ const subFiles = await getAllJSXFiles(fullPath);
222
+ files.push(...subFiles);
223
+ } else if (entry.isFile() && entry.name.endsWith(".jsx")) {
224
+ files.push(fullPath);
225
+ }
226
+ }
227
+
228
+ return files;
229
+ }
230
+
231
+ /**
232
+ * Helper to get paths from a dynamic route
233
+ */
234
+ /**
235
+ * Helper to get paths from a dynamic route
236
+ */
237
+ export async function getDynamicRoutePaths(file) {
238
+ const outDir = resolve(process.cwd(), "dist");
239
+
240
+ // Bundle the file with all its dependencies
241
+ const bundledCode = await bundle(file);
242
+
243
+ // Add inline JSX runtime
244
+ const codeWithRuntime = INLINE_JSX_RUNTIME + bundledCode;
245
+
246
+ const tempFile = join(outDir, `_temp_paths_${Date.now()}.js`);
247
+ await mkdir(dirname(tempFile), { recursive: true });
248
+ await writeFile(tempFile, codeWithRuntime);
249
+
250
+ const moduleUrl = new URL(`file://${tempFile}?t=${Date.now()}`);
251
+ const module = await import(moduleUrl.href);
252
+
253
+ const pathsData = module.getStaticPaths ? await module.getStaticPaths() : [];
254
+
255
+ await import("node:fs/promises").then((fs) => fs.unlink(tempFile).catch(() => {}));
256
+
257
+ return pathsData;
258
+ }
259
+
260
+ /**
261
+ * Generate UnoCSS file
262
+ */
263
+ export async function generateUnoCSS(options = {}) {
264
+ const { outputDir = "dist", unocssConfig, silent = false } = options;
265
+
266
+ const outDir = resolve(process.cwd(), outputDir);
267
+
268
+ // Scan all HTML files
269
+ const htmlFiles = await getAllHTMLFiles(outDir);
270
+
271
+ if (htmlFiles.length === 0) {
272
+ return null;
273
+ }
274
+
275
+ // Generate CSS from HTML files
276
+ const css = await generateCSSFromFiles(htmlFiles, unocssConfig);
277
+
278
+ if (!css) {
279
+ return null;
280
+ }
281
+
282
+ const cssPath = join(outDir, "uno.css");
283
+ await writeFile(cssPath, css);
284
+
285
+ if (!silent) {
286
+ console.log(`\n⚡ Generated UnoCSS: ${relative(process.cwd(), cssPath)}`);
287
+ }
288
+
289
+ return cssPath;
290
+ }
291
+
292
+ /**
293
+ * Get all HTML files recursively
294
+ */
295
+ async function getAllHTMLFiles(dir) {
296
+ const files = [];
297
+
298
+ try {
299
+ const entries = await readdir(dir, { withFileTypes: true });
300
+
301
+ for (const entry of entries) {
302
+ const fullPath = join(dir, entry.name);
303
+
304
+ if (entry.isDirectory()) {
305
+ const subFiles = await getAllHTMLFiles(fullPath);
306
+ files.push(...subFiles);
307
+ } else if (entry.isFile() && entry.name.endsWith(".html")) {
308
+ files.push(fullPath);
309
+ }
310
+ }
311
+ } catch (error) {
312
+ // Directory might not exist yet
313
+ }
314
+
315
+ return files;
316
+ }
package/src/bundler.js CHANGED
@@ -48,16 +48,24 @@ export async function bundle(entryFile) {
48
48
  }
49
49
 
50
50
  /**
51
- * Remove import statements from code
51
+ * Remove import statements from code (but keep package imports)
52
52
  * @param {string} code - JavaScript code
53
- * @returns {string} Code without imports
53
+ * @returns {string} Code with relative imports removed, package imports kept
54
54
  */
55
55
  function removeImports(code) {
56
- // Remove import statements
56
+ // Remove only relative import statements, keep package imports
57
57
  const lines = code.split("\n");
58
58
  const filteredLines = lines.filter(line => {
59
59
  const trimmed = line.trim();
60
- return !trimmed.startsWith("import ") && trimmed !== "import";
60
+ if (!trimmed.startsWith("import ")) return true;
61
+
62
+ // Keep package imports (not starting with . or /)
63
+ const importMatch = trimmed.match(/from\s+['"]([^'"]+)['"]/);
64
+ if (importMatch && !importMatch[1].startsWith('.') && !importMatch[1].startsWith('/')) {
65
+ return true; // Keep package import
66
+ }
67
+
68
+ return false; // Remove relative import
61
69
  });
62
70
 
63
71
  return filteredLines.join("\n");