@ionify/ionify 0.1.1 → 0.1.2

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.
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -24,29 +31,97 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
31
  ));
25
32
 
26
33
  // node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.4_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
27
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
28
- var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
29
-
30
- // src/cli/index.ts
31
- var import_commander = require("commander");
34
+ var getImportMetaUrl, importMetaUrl;
35
+ var init_cjs_shims = __esm({
36
+ "node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.4_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js"() {
37
+ "use strict";
38
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
39
+ importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
+ }
41
+ });
32
42
 
33
- // src/cli/utils/logger.ts
34
- var import_chalk = __toESM(require("chalk"), 1);
35
- function logInfo(message) {
36
- console.log(import_chalk.default.cyan(`[Ionify] ${message}`));
43
+ // src/core/version.ts
44
+ function normalizeTreeshake(treeshake) {
45
+ if (treeshake === false || treeshake === void 0 || treeshake === null) {
46
+ return null;
47
+ }
48
+ if (treeshake === true) {
49
+ return {
50
+ mode: "safe",
51
+ include: [],
52
+ exclude: []
53
+ };
54
+ }
55
+ if (typeof treeshake === "string") {
56
+ return {
57
+ mode: treeshake === "aggressive" ? "aggressive" : "safe",
58
+ include: [],
59
+ exclude: []
60
+ };
61
+ }
62
+ return {
63
+ mode: treeshake.mode === "aggressive" ? "aggressive" : "safe",
64
+ include: Array.isArray(treeshake.include) ? [...treeshake.include].sort() : [],
65
+ exclude: Array.isArray(treeshake.exclude) ? [...treeshake.exclude].sort() : []
66
+ };
37
67
  }
38
- function logError(message, err) {
39
- console.error(import_chalk.default.red(`[Ionify] ${message}`));
40
- if (err) console.error(err);
68
+ function normalizeScopeHoist(scopeHoist) {
69
+ if (scopeHoist === false || scopeHoist === void 0 || scopeHoist === null) {
70
+ return null;
71
+ }
72
+ if (scopeHoist === true) {
73
+ return {
74
+ inlineFunctions: true,
75
+ constantFolding: true,
76
+ combineVariables: true
77
+ };
78
+ }
79
+ return {
80
+ inlineFunctions: scopeHoist.inlineFunctions === true,
81
+ constantFolding: scopeHoist.constantFolding === true,
82
+ combineVariables: scopeHoist.combineVariables === true
83
+ };
41
84
  }
42
-
43
- // src/native/index.ts
44
- var import_fs = __toESM(require("fs"), 1);
45
- var import_path = __toESM(require("path"), 1);
46
- var import_module = require("module");
47
-
48
- // src/core/version.ts
49
- var import_node_crypto = require("crypto");
85
+ function computeCanonicalVersionInputs(config) {
86
+ const parserMode = config.parserMode || "hybrid";
87
+ const minifier = config.minifier || "auto";
88
+ const treeshake = normalizeTreeshake(config.treeshake);
89
+ const scopeHoist = normalizeScopeHoist(config.scopeHoist);
90
+ const plugins = Array.isArray(config.plugins) ? config.plugins.map((p) => typeof p === "string" ? p : p.name).filter((name) => typeof name === "string").sort() : [];
91
+ let entry = null;
92
+ if (config.entry) {
93
+ if (typeof config.entry === "string") {
94
+ entry = [config.entry];
95
+ } else if (Array.isArray(config.entry)) {
96
+ entry = [...config.entry].sort();
97
+ }
98
+ }
99
+ const cssOptions = config.cssOptions && Object.keys(config.cssOptions).length > 0 ? config.cssOptions : null;
100
+ const assetOptions = config.assetOptions && Object.keys(config.assetOptions).length > 0 ? config.assetOptions : null;
101
+ return {
102
+ parserMode,
103
+ minifier,
104
+ treeshake,
105
+ scopeHoist,
106
+ plugins,
107
+ entry,
108
+ cssOptions,
109
+ assetOptions
110
+ };
111
+ }
112
+ function computeVersionHash(inputs) {
113
+ const json = JSON.stringify(inputs, Object.keys(inputs).sort());
114
+ const hash = (0, import_node_crypto.createHash)("sha256").update(json).digest("hex");
115
+ return hash.slice(0, 16);
116
+ }
117
+ var import_node_crypto;
118
+ var init_version = __esm({
119
+ "src/core/version.ts"() {
120
+ "use strict";
121
+ init_cjs_shims();
122
+ import_node_crypto = require("crypto");
123
+ }
124
+ });
50
125
 
51
126
  // src/native/index.ts
52
127
  function resolveCandidates() {
@@ -77,31 +152,429 @@ function resolveCandidates() {
77
152
  }
78
153
  });
79
154
  }
80
- var nativeBinding = null;
81
- (() => {
82
- const require2 = (0, import_module.createRequire)(importMetaUrl);
83
- for (const candidate of resolveCandidates()) {
155
+ function tryNativeTransform(mode, code, options) {
156
+ if (!nativeBinding) return null;
157
+ const wantsOxc = mode === "oxc" || mode === "hybrid";
158
+ const wantsSwc = mode === "swc" || mode === "hybrid";
159
+ if (wantsOxc && nativeBinding.parseAndTransformOxc) {
160
+ try {
161
+ return nativeBinding.parseAndTransformOxc(code, options);
162
+ } catch (err) {
163
+ if (mode === "oxc") throw err;
164
+ }
165
+ }
166
+ if (wantsSwc && nativeBinding.parseAndTransformSwc) {
84
167
  try {
85
- const mod = require2(candidate);
86
- if (mod) {
87
- nativeBinding = mod;
88
- break;
168
+ return nativeBinding.parseAndTransformSwc(code, options);
169
+ } catch (err) {
170
+ if (mode === "swc") throw err;
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ function ensureNativeGraph(graphPath, version) {
176
+ if (!nativeBinding?.graphInit) return;
177
+ try {
178
+ nativeBinding.graphInit(graphPath, version);
179
+ } catch (err) {
180
+ console.error(`[Native] Failed to initialize graph: ${err}`);
181
+ }
182
+ }
183
+ function computeGraphVersion(inputs) {
184
+ const canonical = computeCanonicalVersionInputs(inputs);
185
+ return computeVersionHash(canonical);
186
+ }
187
+ function tryBundleNodeModule(filePath, code) {
188
+ if (!nativeBinding?.plannerPlanBuild || !nativeBinding?.buildChunks) {
189
+ return null;
190
+ }
191
+ try {
192
+ const plan = nativeBinding.plannerPlanBuild([filePath]);
193
+ if (!plan || !plan.chunks || plan.chunks.length === 0) {
194
+ return null;
195
+ }
196
+ const artifacts = nativeBinding.buildChunks(plan);
197
+ if (artifacts && artifacts.length > 0 && artifacts[0].code) {
198
+ return artifacts[0].code;
199
+ }
200
+ } catch (error) {
201
+ console.warn(`[Ionify] Native bundler failed for ${filePath}:`, error);
202
+ }
203
+ return null;
204
+ }
205
+ var import_fs, import_path, import_module, nativeBinding, native;
206
+ var init_native = __esm({
207
+ "src/native/index.ts"() {
208
+ "use strict";
209
+ init_cjs_shims();
210
+ import_fs = __toESM(require("fs"), 1);
211
+ import_path = __toESM(require("path"), 1);
212
+ import_module = require("module");
213
+ init_version();
214
+ nativeBinding = null;
215
+ (() => {
216
+ const require2 = (0, import_module.createRequire)(importMetaUrl);
217
+ for (const candidate of resolveCandidates()) {
218
+ try {
219
+ const mod = require2(candidate);
220
+ if (mod) {
221
+ nativeBinding = mod;
222
+ break;
223
+ }
224
+ } catch {
225
+ }
89
226
  }
227
+ })();
228
+ native = nativeBinding;
229
+ }
230
+ });
231
+
232
+ // src/core/cache.ts
233
+ var cache_exports = {};
234
+ __export(cache_exports, {
235
+ clearCache: () => clearCache,
236
+ getCacheKey: () => getCacheKey,
237
+ readCache: () => readCache,
238
+ writeCache: () => writeCache
239
+ });
240
+ function ensureCacheDir() {
241
+ if (!import_fs2.default.existsSync(CACHE_DIR)) {
242
+ import_fs2.default.mkdirSync(CACHE_DIR, { recursive: true });
243
+ }
244
+ }
245
+ function getCacheKey(content) {
246
+ if (native?.cacheHash) {
247
+ try {
248
+ return native.cacheHash(Buffer.from(content));
90
249
  } catch {
91
250
  }
92
251
  }
93
- })();
94
- var native = nativeBinding;
252
+ return import_crypto.default.createHash("sha256").update(content).digest("hex");
253
+ }
254
+ function writeCache(hash, data) {
255
+ ensureCacheDir();
256
+ const target = import_path2.default.join(CACHE_DIR, hash);
257
+ import_fs2.default.writeFileSync(target, data);
258
+ }
259
+ function readCache(hash) {
260
+ const target = import_path2.default.join(CACHE_DIR, hash);
261
+ return import_fs2.default.existsSync(target) ? import_fs2.default.readFileSync(target) : null;
262
+ }
263
+ function clearCache() {
264
+ if (import_fs2.default.existsSync(CACHE_DIR)) {
265
+ import_fs2.default.rmSync(CACHE_DIR, { recursive: true, force: true });
266
+ }
267
+ }
268
+ var import_fs2, import_path2, import_crypto, CACHE_DIR;
269
+ var init_cache = __esm({
270
+ "src/core/cache.ts"() {
271
+ "use strict";
272
+ init_cjs_shims();
273
+ import_fs2 = __toESM(require("fs"), 1);
274
+ import_path2 = __toESM(require("path"), 1);
275
+ import_crypto = __toESM(require("crypto"), 1);
276
+ init_native();
277
+ CACHE_DIR = import_path2.default.join(process.cwd(), ".ionify", "cache");
278
+ }
279
+ });
95
280
 
96
- // src/cli/utils/config.ts
97
- var import_fs2 = __toESM(require("fs"), 1);
281
+ // src/core/utils/cas.ts
282
+ var cas_exports = {};
283
+ __export(cas_exports, {
284
+ getCasArtifactPath: () => getCasArtifactPath
285
+ });
286
+ function getCasArtifactPath(casRoot, versionHash, moduleHash) {
287
+ return import_path7.default.join(casRoot, versionHash, moduleHash);
288
+ }
289
+ var import_path7;
290
+ var init_cas = __esm({
291
+ "src/core/utils/cas.ts"() {
292
+ "use strict";
293
+ init_cjs_shims();
294
+ import_path7 = __toESM(require("path"), 1);
295
+ }
296
+ });
297
+
298
+ // src/cli/index.ts
299
+ init_cjs_shims();
300
+ var import_commander = require("commander");
301
+
302
+ // src/cli/utils/logger.ts
303
+ init_cjs_shims();
304
+ var import_chalk = __toESM(require("chalk"), 1);
305
+ function logInfo(message) {
306
+ console.log(import_chalk.default.cyan(`[Ionify] ${message}`));
307
+ }
308
+ function logWarn(message) {
309
+ console.warn(import_chalk.default.yellow(`[Ionify] ${message}`));
310
+ }
311
+ function logError(message, err) {
312
+ console.error(import_chalk.default.red(`[Ionify] ${message}`));
313
+ if (err) console.error(err);
314
+ }
315
+
316
+ // src/cli/commands/dev.ts
317
+ init_cjs_shims();
318
+ var import_http = __toESM(require("http"), 1);
319
+ var import_url3 = __toESM(require("url"), 1);
320
+ var import_fs9 = __toESM(require("fs"), 1);
321
+ var import_path12 = __toESM(require("path"), 1);
322
+ var import_url4 = require("url");
323
+ var import_module3 = require("module");
324
+ init_cache();
325
+
326
+ // src/core/graph.ts
327
+ init_cjs_shims();
328
+ var import_fs3 = __toESM(require("fs"), 1);
98
329
  var import_path3 = __toESM(require("path"), 1);
99
- var import_url = require("url");
100
- var import_esbuild = require("esbuild");
330
+ init_native();
331
+ var IONIFY_DIR = import_path3.default.join(process.cwd(), ".ionify");
332
+ var GRAPH_FILE = import_path3.default.join(IONIFY_DIR, "graph.json");
333
+ var GRAPH_DB_FILE = import_path3.default.join(IONIFY_DIR, "graph.db");
334
+ function ensureIonifyDir() {
335
+ if (!import_fs3.default.existsSync(IONIFY_DIR)) import_fs3.default.mkdirSync(IONIFY_DIR, { recursive: true });
336
+ }
337
+ var Graph = class {
338
+ nodes = /* @__PURE__ */ new Map();
339
+ dirty = false;
340
+ saveTimer = null;
341
+ native = native ?? null;
342
+ nativeFlushTimer = null;
343
+ queueSave() {
344
+ if (this.native) return;
345
+ this.dirty = true;
346
+ if (this.saveTimer) return;
347
+ this.saveTimer = setTimeout(() => this.save(), 300);
348
+ }
349
+ constructor(versionInputs) {
350
+ ensureIonifyDir();
351
+ if (this.native) {
352
+ const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
353
+ ensureNativeGraph(GRAPH_DB_FILE, version);
354
+ }
355
+ this.load();
356
+ }
357
+ load() {
358
+ if (this.native) {
359
+ try {
360
+ const snapshot = this.native.graphLoad();
361
+ for (const node of snapshot) {
362
+ const stat = import_fs3.default.existsSync(node.id) ? import_fs3.default.statSync(node.id) : null;
363
+ this.nodes.set(node.id, {
364
+ id: node.id,
365
+ hash: node.hash,
366
+ deps: node.deps,
367
+ dynamicDeps: node.dynamicDeps,
368
+ kind: node.kind,
369
+ configHash: node.config_hash ?? node.configHash ?? null,
370
+ mtimeMs: stat ? stat.mtimeMs : null
371
+ });
372
+ }
373
+ } catch {
374
+ this.loadFromDisk();
375
+ }
376
+ return;
377
+ }
378
+ this.loadFromDisk();
379
+ }
380
+ loadFromDisk() {
381
+ if (!import_fs3.default.existsSync(GRAPH_FILE)) return;
382
+ try {
383
+ const raw = import_fs3.default.readFileSync(GRAPH_FILE, "utf8");
384
+ const snap = JSON.parse(raw);
385
+ if (snap.version === 1 && snap.nodes) {
386
+ for (const [id, node] of Object.entries(snap.nodes)) {
387
+ this.nodes.set(id, node);
388
+ }
389
+ }
390
+ } catch {
391
+ }
392
+ }
393
+ scheduleNativeFlush() {
394
+ if (!this.native?.graphFlush) return;
395
+ if (this.nativeFlushTimer) return;
396
+ this.nativeFlushTimer = setTimeout(() => {
397
+ this.nativeFlushTimer = null;
398
+ try {
399
+ this.native?.graphFlush?.();
400
+ } catch {
401
+ }
402
+ }, 250);
403
+ }
404
+ scheduleSave() {
405
+ if (this.native) return;
406
+ this.queueSave();
407
+ }
408
+ save() {
409
+ if (this.native) return;
410
+ try {
411
+ const snap = {
412
+ version: 1,
413
+ nodes: Object.fromEntries(this.nodes.entries())
414
+ };
415
+ import_fs3.default.writeFileSync(GRAPH_FILE, JSON.stringify(snap, null, 2), "utf8");
416
+ this.dirty = false;
417
+ } catch {
418
+ } finally {
419
+ if (this.saveTimer) {
420
+ clearTimeout(this.saveTimer);
421
+ this.saveTimer = null;
422
+ }
423
+ }
424
+ }
425
+ /** Upsert a node and its deps; returns true if hash changed */
426
+ recordFile(absPath, contentHash, depsAbs, dynamicDeps, kind) {
427
+ const stat = import_fs3.default.existsSync(absPath) ? import_fs3.default.statSync(absPath) : null;
428
+ const mtimeMs = stat ? stat.mtimeMs : null;
429
+ const configHash = process.env.IONIFY_CONFIG_HASH || null;
430
+ const prev = this.nodes.get(absPath);
431
+ let changed = !prev || prev.hash !== contentHash;
432
+ const node = {
433
+ id: absPath,
434
+ hash: contentHash,
435
+ deps: Array.from(new Set(depsAbs)),
436
+ dynamicDeps: dynamicDeps ? Array.from(new Set(dynamicDeps)) : void 0,
437
+ kind: kind || this.inferKind(absPath),
438
+ configHash,
439
+ mtimeMs
440
+ };
441
+ this.nodes.set(absPath, node);
442
+ if (this.native) {
443
+ try {
444
+ changed = this.native.graphRecord(
445
+ absPath,
446
+ contentHash,
447
+ node.deps,
448
+ node.dynamicDeps || [],
449
+ node.kind,
450
+ node.configHash ?? null
451
+ );
452
+ this.scheduleNativeFlush();
453
+ } catch (err) {
454
+ console.error(`[Graph] Failed to record ${absPath}:`, err);
455
+ }
456
+ }
457
+ this.scheduleSave();
458
+ return changed;
459
+ }
460
+ /** Infer module kind from file extension */
461
+ inferKind(absPath) {
462
+ const ext = import_path3.default.extname(absPath).toLowerCase();
463
+ if (/\.(module)\.css$/i.test(absPath)) return "css-module";
464
+ if ([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"].includes(ext)) return "js";
465
+ if (ext === ".css") return "css";
466
+ if ([".json"].includes(ext)) return "json";
467
+ return "asset";
468
+ }
469
+ getNode(absPath) {
470
+ return this.nodes.get(absPath);
471
+ }
472
+ getDeps(absPath) {
473
+ return this.nodes.get(absPath)?.deps ?? [];
474
+ }
475
+ /** Reverse edges: who depends on target? */
476
+ getDependents(targetAbs) {
477
+ const candidates = /* @__PURE__ */ new Set();
478
+ if (this.native?.graphDependents) {
479
+ try {
480
+ for (const dep of this.native.graphDependents(targetAbs) ?? []) {
481
+ candidates.add(dep);
482
+ }
483
+ } catch {
484
+ }
485
+ }
486
+ for (const [id, node] of this.nodes) {
487
+ if (node.deps.includes(targetAbs)) candidates.add(id);
488
+ }
489
+ return Array.from(candidates);
490
+ }
491
+ /** Collect dependents recursively (breadth-first) */
492
+ collectDependentsDeep(targetAbs) {
493
+ const result = /* @__PURE__ */ new Set();
494
+ const queue = [targetAbs];
495
+ while (queue.length) {
496
+ const current = queue.shift();
497
+ for (const dep of this.getDependents(current)) {
498
+ if (!result.has(dep)) {
499
+ result.add(dep);
500
+ queue.push(dep);
501
+ }
502
+ }
503
+ }
504
+ return Array.from(result);
505
+ }
506
+ /** Includes changed files and all dependents */
507
+ collectAffected(changed) {
508
+ const result = /* @__PURE__ */ new Set();
509
+ let usedNative = false;
510
+ if (this.native?.graphCollectAffected) {
511
+ try {
512
+ const nativeList = this.native.graphCollectAffected(changed);
513
+ for (const item of nativeList ?? []) {
514
+ result.add(item);
515
+ }
516
+ usedNative = true;
517
+ } catch {
518
+ }
519
+ }
520
+ for (const target of changed) {
521
+ result.add(target);
522
+ }
523
+ if (!usedNative || result.size === 0) {
524
+ for (const target of changed) {
525
+ result.add(target);
526
+ for (const dep of this.collectDependentsDeep(target)) {
527
+ result.add(dep);
528
+ }
529
+ }
530
+ }
531
+ return Array.from(result);
532
+ }
533
+ /** Remove file from graph and clean up dependents lists */
534
+ removeFile(absPath) {
535
+ const existed = this.nodes.delete(absPath);
536
+ if (existed) {
537
+ for (const node of this.nodes.values()) {
538
+ if (node.deps.includes(absPath)) {
539
+ node.deps = node.deps.filter((dep) => dep !== absPath);
540
+ }
541
+ }
542
+ if (this.native) {
543
+ try {
544
+ this.native.graphRemove(absPath);
545
+ this.scheduleNativeFlush();
546
+ } catch {
547
+ }
548
+ }
549
+ this.queueSave();
550
+ }
551
+ }
552
+ /** Persist immediately (e.g., on shutdown) */
553
+ flush() {
554
+ if (this.nativeFlushTimer) {
555
+ clearTimeout(this.nativeFlushTimer);
556
+ this.nativeFlushTimer = null;
557
+ }
558
+ if (this.native?.graphFlush) {
559
+ try {
560
+ this.native.graphFlush();
561
+ } catch {
562
+ }
563
+ }
564
+ if (this.dirty) this.save();
565
+ }
566
+ };
101
567
 
102
568
  // src/core/resolver.ts
103
- var import_path2 = __toESM(require("path"), 1);
569
+ init_cjs_shims();
570
+ var import_fs4 = __toESM(require("fs"), 1);
571
+ var import_path4 = __toESM(require("path"), 1);
104
572
  var import_module2 = require("module");
573
+ var import_url = require("url");
574
+ init_native();
575
+ var import_meta = {};
576
+ var SUPPORTED_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
577
+ var CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
105
578
  var swc = null;
106
579
  (() => {
107
580
  try {
@@ -111,7 +584,119 @@ var swc = null;
111
584
  swc = null;
112
585
  }
113
586
  })();
587
+ function extractImports(source, filename = "inline.ts") {
588
+ if (native?.parseModuleIr) {
589
+ try {
590
+ const result = native.parseModuleIr(filename, source);
591
+ return result.dependencies.map((dep) => dep.specifier);
592
+ } catch {
593
+ }
594
+ }
595
+ const deps = /* @__PURE__ */ new Set();
596
+ const fallbackRegex = () => {
597
+ const re = /(?:import\s+(?:[^'"]+\s+from\s+)??['"]([^'"]+)['"])|(?:export\s+[^'"]+\s+from\s+['"]([^'"]+)['"])|(?:import\s*?\(\s*?['"]([^'"]+)['"]\s*?\))/g;
598
+ let m;
599
+ while (m = re.exec(source)) {
600
+ const spec = m[1] || m[2] || m[3];
601
+ if (spec) deps.add(spec);
602
+ }
603
+ };
604
+ try {
605
+ const parseSync = swc?.parseSync;
606
+ if (parseSync) {
607
+ const ast = parseSync(source, {
608
+ filename,
609
+ isModule: true,
610
+ target: "es2022",
611
+ syntax: "typescript",
612
+ tsx: true,
613
+ decorators: true,
614
+ dynamicImport: true
615
+ });
616
+ const visit = (node) => {
617
+ if (!node || typeof node !== "object") return;
618
+ const anyNode = node;
619
+ const type = anyNode.type;
620
+ if (type === "ImportDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
621
+ deps.add(anyNode.source.value);
622
+ } else if (type === "ExportAllDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
623
+ deps.add(anyNode.source.value);
624
+ } else if (type === "ExportNamedDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
625
+ deps.add(anyNode.source.value);
626
+ } else if (type === "CallExpression") {
627
+ const callee = anyNode.callee ?? {};
628
+ if (callee.type === "Import") {
629
+ const args = anyNode.arguments ?? [];
630
+ const first = args[0];
631
+ if (first && typeof first === "object") {
632
+ const expr = first.expression;
633
+ if (expr && expr.type === "StringLiteral" && typeof expr.value === "string") {
634
+ deps.add(expr.value);
635
+ }
636
+ }
637
+ }
638
+ }
639
+ for (const value of Object.values(anyNode)) {
640
+ if (!value) continue;
641
+ if (Array.isArray(value)) {
642
+ for (const item of value) visit(item);
643
+ } else if (typeof value === "object") {
644
+ visit(value);
645
+ }
646
+ }
647
+ };
648
+ visit(ast);
649
+ } else {
650
+ fallbackRegex();
651
+ }
652
+ } catch {
653
+ fallbackRegex();
654
+ }
655
+ if (!deps.size) {
656
+ fallbackRegex();
657
+ }
658
+ return Array.from(deps);
659
+ }
660
+ function tryFile(p) {
661
+ if (import_fs4.default.existsSync(p) && import_fs4.default.statSync(p).isFile()) return p;
662
+ return null;
663
+ }
664
+ function tryWithExt(p) {
665
+ if (tryFile(p)) return p;
666
+ for (const ext of SUPPORTED_EXTS) {
667
+ const cand = p.endsWith(ext) ? p : p + ext;
668
+ const found = tryFile(cand);
669
+ if (found) return found;
670
+ }
671
+ if (import_fs4.default.existsSync(p) && import_fs4.default.statSync(p).isDirectory()) {
672
+ const pkgPath = import_path4.default.join(p, "package.json");
673
+ if (import_fs4.default.existsSync(pkgPath)) {
674
+ try {
675
+ const pkg = JSON.parse(import_fs4.default.readFileSync(pkgPath, "utf8"));
676
+ if (pkg.main) {
677
+ const mainPath = import_path4.default.join(p, pkg.main);
678
+ const mainResolved = tryFile(mainPath) || tryWithExt(mainPath);
679
+ if (mainResolved) return mainResolved;
680
+ }
681
+ if (pkg.module) {
682
+ const modulePath = import_path4.default.join(p, pkg.module);
683
+ const moduleResolved = tryFile(modulePath) || tryWithExt(modulePath);
684
+ if (moduleResolved) return moduleResolved;
685
+ }
686
+ } catch {
687
+ }
688
+ }
689
+ for (const ext of SUPPORTED_EXTS) {
690
+ const idx = import_path4.default.join(p, "index" + ext);
691
+ const found = tryFile(idx);
692
+ if (found) return found;
693
+ }
694
+ }
695
+ return null;
696
+ }
697
+ var cachedTsconfigAliases;
114
698
  var customAliasEntries = [];
699
+ var resolvePathCache = /* @__PURE__ */ new Map();
115
700
  function createAliasEntry(pattern, targets) {
116
701
  const hasWildcard = pattern.includes("*");
117
702
  if (hasWildcard) {
@@ -143,7 +728,7 @@ function createAliasEntry(pattern, targets) {
143
728
  }
144
729
  if (normalizedPattern && specifier.startsWith(normalizedPattern + "/")) {
145
730
  const remainder = specifier.slice(normalizedPattern.length + 1);
146
- return targets.map((target) => import_path2.default.join(target, remainder));
731
+ return targets.map((target) => import_path4.default.join(target, remainder));
147
732
  }
148
733
  return [];
149
734
  }
@@ -154,93 +739,1086 @@ function buildAliasEntries(aliases, baseDir) {
154
739
  for (const [pattern, value] of Object.entries(aliases)) {
155
740
  const replacements = Array.isArray(value) ? value : [value];
156
741
  const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map(
157
- (rep) => import_path2.default.isAbsolute(rep) ? rep : import_path2.default.resolve(baseDir, rep)
742
+ (rep) => import_path4.default.isAbsolute(rep) ? rep : import_path4.default.resolve(baseDir, rep)
158
743
  );
159
744
  if (!targets.length) continue;
160
745
  entries.push(createAliasEntry(pattern, targets));
161
746
  }
162
747
  return entries;
163
748
  }
164
- function configureResolverAliases(aliases, baseDir) {
165
- customAliasEntries = aliases ? buildAliasEntries(aliases, baseDir) : [];
166
- }
167
-
168
- // src/cli/utils/config.ts
169
- var CONFIG_BASENAMES = [
170
- "ionify.config.ts",
171
- "ionify.config.mts",
172
- "ionify.config.js",
173
- "ionify.config.mjs",
174
- "ionify.config.cjs"
175
- ];
176
- var cachedConfig = null;
177
- var configLoaded = false;
178
- async function bundleConfig(entry) {
179
- const absDir = import_path3.default.dirname(entry);
180
- const inlineIonifyPlugin = {
181
- name: "inline-ionify",
182
- setup(build2) {
183
- build2.onResolve({ filter: /^ionify$/ }, () => ({
184
- path: "ionify-virtual",
185
- namespace: "ionify-ns"
186
- }));
187
- build2.onLoad({ filter: /.*/, namespace: "ionify-ns" }, () => ({
188
- contents: `
189
- export function defineConfig(config) {
190
- return typeof config === 'function' ? config : () => config;
191
- }
192
- `,
193
- loader: "js"
194
- }));
749
+ function loadTsconfigAliases() {
750
+ if (cachedTsconfigAliases !== void 0) {
751
+ return cachedTsconfigAliases ?? [];
752
+ }
753
+ const rootDir = process.cwd();
754
+ for (const configName of CONFIG_FILES) {
755
+ const candidate = import_path4.default.resolve(rootDir, configName);
756
+ if (!import_fs4.default.existsSync(candidate) || !import_fs4.default.statSync(candidate).isFile()) {
757
+ continue;
758
+ }
759
+ try {
760
+ const raw = import_fs4.default.readFileSync(candidate, "utf8");
761
+ const parsed = JSON.parse(raw);
762
+ const compilerOptions = parsed?.compilerOptions ?? {};
763
+ const baseUrl = compilerOptions.baseUrl ? import_path4.default.resolve(import_path4.default.dirname(candidate), compilerOptions.baseUrl) : import_path4.default.dirname(candidate);
764
+ const paths = compilerOptions.paths ?? {};
765
+ cachedTsconfigAliases = buildAliasEntries(paths, baseUrl);
766
+ return cachedTsconfigAliases;
767
+ } catch {
195
768
  }
196
- };
197
- const result = await (0, import_esbuild.build)({
198
- entryPoints: [entry],
199
- bundle: true,
200
- platform: "node",
201
- format: "esm",
202
- sourcemap: "inline",
203
- write: false,
204
- target: "node18",
205
- logLevel: "silent",
206
- absWorkingDir: absDir,
207
- plugins: [inlineIonifyPlugin]
208
- });
209
- const output = result.outputFiles?.[0];
210
- if (!output) throw new Error("Failed to bundle ionify config");
211
- const dirnameLiteral = JSON.stringify(absDir);
212
- const filenameLiteral = JSON.stringify(entry);
213
- const importMetaLiteral = JSON.stringify((0, import_url.pathToFileURL)(entry).href);
214
- let contents = output.text;
215
- if (contents.includes("import.meta.url")) {
216
- contents = contents.replace(/import\.meta\.url/g, "__IONIFY_IMPORT_META_URL");
217
- contents = `const __IONIFY_IMPORT_META_URL = ${importMetaLiteral};
218
- ${contents}`;
219
769
  }
220
- const preamble = `const __dirname = ${dirnameLiteral};
221
- const __filename = ${filenameLiteral};
222
- `;
223
- return preamble + contents;
770
+ cachedTsconfigAliases = [];
771
+ return cachedTsconfigAliases;
224
772
  }
225
- function findConfigFile(cwd) {
226
- for (const name of CONFIG_BASENAMES) {
227
- const candidate = import_path3.default.resolve(cwd, name);
228
- if (import_fs2.default.existsSync(candidate) && import_fs2.default.statSync(candidate).isFile()) {
229
- return candidate;
773
+ function resolveFromEntries(entries, specifier) {
774
+ for (const entry of entries) {
775
+ const candidates = entry.resolveCandidates(specifier);
776
+ for (const candidate of candidates) {
777
+ const resolved = tryWithExt(candidate);
778
+ if (resolved) return resolved;
230
779
  }
231
780
  }
232
781
  return null;
233
782
  }
234
- async function loadIonifyConfig(cwd = process.cwd()) {
235
- if (configLoaded) return cachedConfig;
236
- configLoaded = true;
237
- const configPath = findConfigFile(cwd);
238
- if (!configPath) {
239
- cachedConfig = null;
240
- configureResolverAliases(void 0, cwd);
241
- return cachedConfig;
783
+ function resolveWithAliases(specifier) {
784
+ const custom = resolveFromEntries(customAliasEntries, specifier);
785
+ if (custom) return custom;
786
+ const tsconfigEntries = loadTsconfigAliases();
787
+ return resolveFromEntries(tsconfigEntries, specifier);
788
+ }
789
+ function configureResolverAliases(aliases, baseDir) {
790
+ customAliasEntries = aliases ? buildAliasEntries(aliases, baseDir) : [];
791
+ }
792
+ function resolveImport(specifier, importerAbs) {
793
+ const cacheKey = `${importerAbs}\0${specifier}`;
794
+ if (resolvePathCache.has(cacheKey)) {
795
+ return resolvePathCache.get(cacheKey) ?? null;
242
796
  }
243
- try {
797
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
798
+ const aliasResolved = resolveWithAliases(specifier);
799
+ if (aliasResolved) {
800
+ resolvePathCache.set(cacheKey, aliasResolved);
801
+ return aliasResolved;
802
+ }
803
+ try {
804
+ const require2 = (0, import_module2.createRequire)(importerAbs);
805
+ const resolved2 = require2.resolve(specifier);
806
+ resolvePathCache.set(cacheKey, resolved2);
807
+ return resolved2;
808
+ } catch {
809
+ try {
810
+ const importerUrl = (0, import_url.pathToFileURL)(importerAbs).href;
811
+ const resolvedUrl = import_meta.resolve(specifier, importerUrl);
812
+ if (resolvedUrl.startsWith("file://")) {
813
+ const resolved2 = (0, import_url.fileURLToPath)(resolvedUrl);
814
+ resolvePathCache.set(cacheKey, resolved2);
815
+ return resolved2;
816
+ }
817
+ resolvePathCache.set(cacheKey, resolvedUrl);
818
+ return resolvedUrl;
819
+ } catch {
820
+ const nodeModulesPath = import_path4.default.join(import_path4.default.dirname(importerAbs), "node_modules", specifier);
821
+ const resolvedNodeModules = tryWithExt(nodeModulesPath);
822
+ if (resolvedNodeModules) {
823
+ resolvePathCache.set(cacheKey, resolvedNodeModules);
824
+ return resolvedNodeModules;
825
+ }
826
+ const srcPath = import_path4.default.join(process.cwd(), "src", specifier);
827
+ const resolvedSrc = tryWithExt(srcPath);
828
+ if (resolvedSrc) {
829
+ resolvePathCache.set(cacheKey, resolvedSrc);
830
+ return resolvedSrc;
831
+ }
832
+ const rootPath = import_path4.default.join(process.cwd(), specifier);
833
+ const resolvedRoot = tryWithExt(rootPath);
834
+ if (resolvedRoot) {
835
+ resolvePathCache.set(cacheKey, resolvedRoot);
836
+ return resolvedRoot;
837
+ }
838
+ resolvePathCache.set(cacheKey, null);
839
+ return null;
840
+ }
841
+ }
842
+ }
843
+ const baseDir = import_path4.default.dirname(importerAbs);
844
+ const target = import_path4.default.resolve(baseDir, specifier);
845
+ const resolved = tryWithExt(target);
846
+ resolvePathCache.set(cacheKey, resolved);
847
+ return resolved;
848
+ }
849
+ function resolveImports(specs, importerAbs) {
850
+ const abs = specs.map((s) => resolveImport(s, importerAbs)).filter((x) => !!x);
851
+ return Array.from(new Set(abs));
852
+ }
853
+
854
+ // src/core/resolver/module-resolver.ts
855
+ init_cjs_shims();
856
+ var import_path5 = __toESM(require("path"), 1);
857
+ var import_fs5 = __toESM(require("fs"), 1);
858
+ var DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"];
859
+ var DEFAULT_CONDITIONS = ["import", "default"];
860
+ var DEFAULT_MAIN_FIELDS = ["module", "main"];
861
+ var ModuleResolver = class {
862
+ options;
863
+ rootDir;
864
+ constructor(rootDir, options = {}) {
865
+ this.rootDir = rootDir;
866
+ this.options = {
867
+ baseUrl: options.baseUrl || ".",
868
+ paths: options.paths || {},
869
+ extensions: options.extensions || DEFAULT_EXTENSIONS,
870
+ alias: options.alias || {},
871
+ conditions: options.conditions || DEFAULT_CONDITIONS,
872
+ mainFields: options.mainFields || DEFAULT_MAIN_FIELDS
873
+ };
874
+ }
875
+ resolve(importSpecifier, importer) {
876
+ if (import_path5.default.isAbsolute(importSpecifier)) {
877
+ return this.tryResolveFile(importSpecifier);
878
+ }
879
+ const aliasResolved = this.resolveAlias(importSpecifier);
880
+ if (aliasResolved) {
881
+ return this.tryResolveFile(aliasResolved);
882
+ }
883
+ if (importSpecifier.startsWith(".")) {
884
+ const resolvedPath = import_path5.default.resolve(import_path5.default.dirname(importer), importSpecifier);
885
+ return this.tryResolveFile(resolvedPath);
886
+ }
887
+ return this.resolveBareModule(importSpecifier, importer);
888
+ }
889
+ resolveAlias(specifier) {
890
+ for (const [alias, target] of Object.entries(this.options.alias)) {
891
+ if (specifier === alias || specifier.startsWith(`${alias}/`)) {
892
+ const relativePath = specifier.slice(alias.length);
893
+ const targets = Array.isArray(target) ? target : [target];
894
+ for (const t of targets) {
895
+ const resolved = import_path5.default.join(this.rootDir, t, relativePath);
896
+ if (import_fs5.default.existsSync(resolved)) {
897
+ return resolved;
898
+ }
899
+ }
900
+ }
901
+ }
902
+ for (const [pattern, targets] of Object.entries(this.options.paths)) {
903
+ const wildcardIndex = pattern.indexOf("*");
904
+ if (wildcardIndex === -1) {
905
+ if (specifier === pattern) {
906
+ return import_path5.default.join(this.rootDir, this.options.baseUrl, targets[0]);
907
+ }
908
+ } else {
909
+ const prefix = pattern.slice(0, wildcardIndex);
910
+ const suffix = pattern.slice(wildcardIndex + 1);
911
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) {
912
+ const matchedPortion = specifier.slice(prefix.length, -suffix.length || void 0);
913
+ for (const target of targets) {
914
+ const resolved = import_path5.default.join(
915
+ this.rootDir,
916
+ this.options.baseUrl,
917
+ target.replace("*", matchedPortion)
918
+ );
919
+ if (import_fs5.default.existsSync(resolved)) {
920
+ return resolved;
921
+ }
922
+ }
923
+ }
924
+ }
925
+ }
926
+ return null;
927
+ }
928
+ resolveBareModule(specifier, importer) {
929
+ const parts = specifier.split("/");
930
+ const packageName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
931
+ const subpath = parts.slice(packageName.startsWith("@") ? 2 : 1).join("/");
932
+ let dir = import_path5.default.dirname(importer);
933
+ while (dir !== "/") {
934
+ const nodeModulesPath = import_path5.default.join(dir, "node_modules", packageName);
935
+ if (import_fs5.default.existsSync(nodeModulesPath)) {
936
+ if (subpath) {
937
+ return this.tryResolveFile(import_path5.default.join(nodeModulesPath, subpath));
938
+ }
939
+ return this.resolvePackageMain(nodeModulesPath);
940
+ }
941
+ dir = import_path5.default.dirname(dir);
942
+ }
943
+ return null;
944
+ }
945
+ resolvePackageMain(packageDir) {
946
+ const pkgJsonPath = import_path5.default.join(packageDir, "package.json");
947
+ if (import_fs5.default.existsSync(pkgJsonPath)) {
948
+ try {
949
+ const pkg = JSON.parse(import_fs5.default.readFileSync(pkgJsonPath, "utf8"));
950
+ if (pkg.exports) {
951
+ const resolved = this.resolveExports(pkg.exports, packageDir);
952
+ if (resolved) return resolved;
953
+ }
954
+ for (const field of this.options.mainFields) {
955
+ if (pkg[field]) {
956
+ const resolved = this.tryResolveFile(import_path5.default.join(packageDir, pkg[field]));
957
+ if (resolved) return resolved;
958
+ }
959
+ }
960
+ } catch {
961
+ }
962
+ }
963
+ return this.tryResolveFile(import_path5.default.join(packageDir, "index"));
964
+ }
965
+ resolveExports(exports2, packageDir) {
966
+ if (typeof exports2 === "string") {
967
+ return this.tryResolveFile(import_path5.default.join(packageDir, exports2));
968
+ }
969
+ if (Array.isArray(exports2)) {
970
+ for (const exp of exports2) {
971
+ const resolved = this.resolveExports(exp, packageDir);
972
+ if (resolved) return resolved;
973
+ }
974
+ return null;
975
+ }
976
+ if (typeof exports2 === "object") {
977
+ for (const condition of this.options.conditions) {
978
+ if (condition in exports2) {
979
+ const resolved = this.resolveExports(exports2[condition], packageDir);
980
+ if (resolved) return resolved;
981
+ }
982
+ }
983
+ if ("default" in exports2) {
984
+ return this.resolveExports(exports2.default, packageDir);
985
+ }
986
+ }
987
+ return null;
988
+ }
989
+ tryResolveFile(filepath) {
990
+ if (import_fs5.default.existsSync(filepath) && import_fs5.default.statSync(filepath).isFile()) {
991
+ return filepath;
992
+ }
993
+ for (const ext of this.options.extensions) {
994
+ const withExt = `${filepath}${ext}`;
995
+ if (import_fs5.default.existsSync(withExt) && import_fs5.default.statSync(withExt).isFile()) {
996
+ return withExt;
997
+ }
998
+ }
999
+ if (import_fs5.default.existsSync(filepath) && import_fs5.default.statSync(filepath).isDirectory()) {
1000
+ for (const ext of this.options.extensions) {
1001
+ const indexFile = import_path5.default.join(filepath, `index${ext}`);
1002
+ if (import_fs5.default.existsSync(indexFile) && import_fs5.default.statSync(indexFile).isFile()) {
1003
+ return indexFile;
1004
+ }
1005
+ }
1006
+ }
1007
+ return null;
1008
+ }
1009
+ };
1010
+
1011
+ // src/core/watcher.ts
1012
+ init_cjs_shims();
1013
+ var import_fs6 = __toESM(require("fs"), 1);
1014
+ var import_path6 = __toESM(require("path"), 1);
1015
+ var import_events = require("events");
1016
+ var IonifyWatcher = class extends import_events.EventEmitter {
1017
+ constructor(rootDir) {
1018
+ super();
1019
+ this.rootDir = rootDir;
1020
+ }
1021
+ watchers = /* @__PURE__ */ new Map();
1022
+ debounce = /* @__PURE__ */ new Map();
1023
+ polled = /* @__PURE__ */ new Set();
1024
+ watchFile(filePath) {
1025
+ const abs = import_path6.default.resolve(filePath);
1026
+ if (this.watchers.has(abs)) return;
1027
+ if (/(node_modules|\.git|\.ionify|dist)/.test(abs)) return;
1028
+ if (!import_fs6.default.existsSync(abs)) return;
1029
+ try {
1030
+ const dir = import_path6.default.dirname(abs);
1031
+ const watcher = import_fs6.default.watch(dir, (event, filename) => {
1032
+ if (!filename) return;
1033
+ const full = import_path6.default.join(dir, filename.toString());
1034
+ if (full !== abs) return;
1035
+ const now = Date.now();
1036
+ const last = this.debounce.get(abs) || 0;
1037
+ if (now - last < 100) return;
1038
+ this.debounce.set(abs, now);
1039
+ const exists = import_fs6.default.existsSync(abs);
1040
+ this.emit("change", abs, exists ? "changed" : "deleted");
1041
+ });
1042
+ this.watchers.set(abs, watcher);
1043
+ this.polled.add(abs);
1044
+ import_fs6.default.watchFile(abs, { interval: 5e3 }, (curr, prev) => {
1045
+ if (curr.mtimeMs !== prev.mtimeMs) {
1046
+ this.emit("change", abs, "changed");
1047
+ }
1048
+ });
1049
+ } catch {
1050
+ this.polled.add(abs);
1051
+ import_fs6.default.watchFile(abs, { interval: 8e3 }, (curr, prev) => {
1052
+ if (curr.mtimeMs !== prev.mtimeMs) {
1053
+ this.emit("change", abs, "changed");
1054
+ }
1055
+ });
1056
+ }
1057
+ }
1058
+ unwatchFile(filePath) {
1059
+ const abs = import_path6.default.resolve(filePath);
1060
+ const watcher = this.watchers.get(abs);
1061
+ if (watcher) watcher.close();
1062
+ import_fs6.default.unwatchFile(abs);
1063
+ this.watchers.delete(abs);
1064
+ this.polled.delete(abs);
1065
+ }
1066
+ closeAll() {
1067
+ for (const [abs, w] of this.watchers) {
1068
+ w.close();
1069
+ import_fs6.default.unwatchFile(abs);
1070
+ }
1071
+ this.watchers.clear();
1072
+ for (const abs of this.polled) {
1073
+ import_fs6.default.unwatchFile(abs);
1074
+ }
1075
+ this.polled.clear();
1076
+ }
1077
+ };
1078
+
1079
+ // src/core/transform.ts
1080
+ init_cjs_shims();
1081
+ var TransformCache = class {
1082
+ store = /* @__PURE__ */ new Map();
1083
+ hits = 0;
1084
+ misses = 0;
1085
+ maxEntries;
1086
+ constructor(maxEntries) {
1087
+ const envMax = process.env.IONIFY_DEV_TRANSFORM_CACHE_MAX;
1088
+ const parsedEnv = envMax ? parseInt(envMax, 10) : NaN;
1089
+ this.maxEntries = Number.isFinite(parsedEnv) ? parsedEnv : maxEntries ?? 5e3;
1090
+ }
1091
+ setMaxEntries(maxEntries) {
1092
+ this.maxEntries = maxEntries;
1093
+ this.prune();
1094
+ }
1095
+ get(key) {
1096
+ const entry = this.store.get(key);
1097
+ if (entry) {
1098
+ this.hits += 1;
1099
+ entry.timestamp = Date.now();
1100
+ return entry;
1101
+ }
1102
+ this.misses += 1;
1103
+ return null;
1104
+ }
1105
+ set(key, entry) {
1106
+ this.store.set(key, { ...entry, timestamp: Date.now() });
1107
+ this.prune();
1108
+ }
1109
+ prune(maxEntries) {
1110
+ const limit = maxEntries ?? this.maxEntries;
1111
+ if (this.store.size <= limit) return;
1112
+ const sorted = Array.from(this.store.entries()).sort(
1113
+ (a, b) => a[1].timestamp - b[1].timestamp
1114
+ );
1115
+ const removeCount = this.store.size - limit;
1116
+ for (let i = 0; i < removeCount; i++) {
1117
+ this.store.delete(sorted[i][0]);
1118
+ }
1119
+ }
1120
+ metrics() {
1121
+ return {
1122
+ hits: this.hits,
1123
+ misses: this.misses,
1124
+ size: this.store.size,
1125
+ max: this.maxEntries
1126
+ };
1127
+ }
1128
+ };
1129
+ var transformCache = new TransformCache();
1130
+ var TransformEngine = class {
1131
+ loaders = [];
1132
+ cacheEnabled;
1133
+ cacheVersion = "v1";
1134
+ casRoot;
1135
+ versionHash;
1136
+ constructor(options) {
1137
+ this.cacheEnabled = options?.cache ?? true;
1138
+ this.casRoot = options?.casRoot;
1139
+ this.versionHash = options?.versionHash;
1140
+ }
1141
+ useLoader(loader) {
1142
+ this.loaders.push(loader);
1143
+ this.loaders.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
1144
+ }
1145
+ async run(ctx) {
1146
+ const { getCacheKey: getCacheKey2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
1147
+ const path16 = await import("path");
1148
+ const fs13 = await import("fs");
1149
+ const { getCasArtifactPath: getCasArtifactPath2 } = await Promise.resolve().then(() => (init_cas(), cas_exports));
1150
+ const moduleHash = ctx.moduleHash || getCacheKey2(ctx.code);
1151
+ const loaderSig = this.loaders.map((l) => l.name || "loader").join("|");
1152
+ const loaderHash = getCacheKey2(loaderSig);
1153
+ const memKey = `${moduleHash}-${loaderHash}`;
1154
+ const casDir = this.casRoot && this.versionHash ? getCasArtifactPath2(this.casRoot, this.versionHash, moduleHash) : null;
1155
+ const casFile = casDir ? path16.join(casDir, "transformed.js") : null;
1156
+ const casMapFile = casDir ? path16.join(casDir, "transformed.js.map") : null;
1157
+ const debug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
1158
+ if (this.cacheEnabled) {
1159
+ const memHit = transformCache.get(memKey);
1160
+ if (memHit) {
1161
+ if (debug) {
1162
+ console.log(`[Dev Cache] HIT mem key=${memKey} size=${transformCache.metrics().size}`);
1163
+ }
1164
+ return { code: memHit.transformed, map: memHit.map };
1165
+ }
1166
+ if (casFile && fs13.existsSync(casFile)) {
1167
+ try {
1168
+ const code = fs13.readFileSync(casFile, "utf8");
1169
+ const map = casMapFile && fs13.existsSync(casMapFile) ? fs13.readFileSync(casMapFile, "utf8") : void 0;
1170
+ const parsed = { code, map };
1171
+ transformCache.set(memKey, {
1172
+ hash: moduleHash,
1173
+ loaderHash,
1174
+ transformed: parsed.code,
1175
+ map: parsed.map,
1176
+ timestamp: Date.now()
1177
+ });
1178
+ if (debug) {
1179
+ console.log(`[Dev Cache] HIT cas key=${memKey} size=${transformCache.metrics().size}`);
1180
+ }
1181
+ return parsed;
1182
+ } catch {
1183
+ }
1184
+ }
1185
+ }
1186
+ let working = { ...ctx };
1187
+ let result = { code: ctx.code };
1188
+ for (const loader of this.loaders) {
1189
+ if (!loader.test(working)) continue;
1190
+ const output = await loader.transform({ ...working, code: result.code });
1191
+ if (output && output.code !== void 0) {
1192
+ result = { ...result, ...output };
1193
+ working = { ...working, code: result.code };
1194
+ }
1195
+ }
1196
+ if (this.cacheEnabled) {
1197
+ transformCache.set(memKey, {
1198
+ hash: moduleHash,
1199
+ loaderHash,
1200
+ transformed: result.code,
1201
+ map: result.map,
1202
+ timestamp: Date.now()
1203
+ });
1204
+ if (casFile) {
1205
+ try {
1206
+ fs13.mkdirSync(path16.dirname(casFile), { recursive: true });
1207
+ fs13.writeFileSync(casFile, result.code, "utf8");
1208
+ if (result.map && casMapFile) {
1209
+ fs13.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
1210
+ }
1211
+ } catch {
1212
+ }
1213
+ }
1214
+ if (debug) {
1215
+ const m = transformCache.metrics();
1216
+ console.log(`[Dev Cache] MISS stored key=${memKey} size=${m.size} hits=${m.hits} misses=${m.misses}`);
1217
+ }
1218
+ }
1219
+ return result;
1220
+ }
1221
+ };
1222
+
1223
+ // src/core/hmr.ts
1224
+ init_cjs_shims();
1225
+ var HMRServer = class {
1226
+ clients = /* @__PURE__ */ new Set();
1227
+ pending = /* @__PURE__ */ new Map();
1228
+ nextId = 1;
1229
+ closed = false;
1230
+ /** Handle an incoming SSE subscription request */
1231
+ handleSSE(req, res) {
1232
+ if (this.closed) {
1233
+ res.writeHead(503);
1234
+ res.end();
1235
+ return;
1236
+ }
1237
+ res.writeHead(200, {
1238
+ "Content-Type": "text/event-stream",
1239
+ "Cache-Control": "no-cache, no-transform",
1240
+ Connection: "keep-alive",
1241
+ "X-Accel-Buffering": "no"
1242
+ });
1243
+ res.write(`event: ready
1244
+ data: "ok"
1245
+
1246
+ `);
1247
+ this.clients.add(res);
1248
+ req.on("close", () => {
1249
+ this.clients.delete(res);
1250
+ try {
1251
+ res.end();
1252
+ } catch {
1253
+ }
1254
+ });
1255
+ }
1256
+ send(event, payload) {
1257
+ const data = (event ? `event: ${event}
1258
+ ` : "") + `data: ${JSON.stringify(payload)}
1259
+
1260
+ `;
1261
+ for (const client of this.clients) {
1262
+ try {
1263
+ client.write(data);
1264
+ } catch {
1265
+ }
1266
+ }
1267
+ }
1268
+ /** Broadcast a JSON event to all SSE clients */
1269
+ broadcast(payload) {
1270
+ this.send(null, payload);
1271
+ }
1272
+ broadcastEvent(event, payload) {
1273
+ this.send(event, payload);
1274
+ }
1275
+ queueUpdate(modules) {
1276
+ if (!modules.length) return null;
1277
+ const timestamp = Date.now();
1278
+ const id = `${timestamp}-${this.nextId++}`;
1279
+ const summary = {
1280
+ type: "update",
1281
+ id,
1282
+ timestamp,
1283
+ modules: modules.map(({ url: url2, hash, reason }) => ({ url: url2, hash, reason }))
1284
+ };
1285
+ this.pending.set(id, { summary, modules, createdAt: timestamp });
1286
+ this.broadcastEvent("update", summary);
1287
+ return summary;
1288
+ }
1289
+ consumeUpdate(id) {
1290
+ const pending = this.pending.get(id);
1291
+ if (pending) {
1292
+ this.pending.delete(id);
1293
+ }
1294
+ return pending;
1295
+ }
1296
+ broadcastError(payload) {
1297
+ this.broadcastEvent("error", { type: "error", ...payload });
1298
+ }
1299
+ close() {
1300
+ this.closed = true;
1301
+ for (const client of this.clients) {
1302
+ try {
1303
+ client.end();
1304
+ } catch {
1305
+ }
1306
+ }
1307
+ this.clients.clear();
1308
+ this.pending.clear();
1309
+ }
1310
+ };
1311
+ function injectHMRClient(html) {
1312
+ const tag = `<script type="module" src="/__ionify_hmr_client.js"></script>`;
1313
+ return html.includes("</body>") ? html.replace("</body>", `${tag}
1314
+ </body>`) : html + "\n" + tag;
1315
+ }
1316
+
1317
+ // src/core/loaders/css.ts
1318
+ init_cjs_shims();
1319
+ var import_path8 = __toESM(require("path"), 1);
1320
+ var import_crypto2 = __toESM(require("crypto"), 1);
1321
+ var import_postcss = __toESM(require("postcss"), 1);
1322
+ var import_postcss_load_config = __toESM(require("postcss-load-config"), 1);
1323
+ var import_postcss_modules = __toESM(require("postcss-modules"), 1);
1324
+ init_cache();
1325
+ var cachedConfig = null;
1326
+ var configFailed = false;
1327
+ async function getPostcssConfig(rootDir) {
1328
+ if (cachedConfig) return cachedConfig;
1329
+ if (configFailed) return { plugins: [], options: {} };
1330
+ try {
1331
+ const result = await (0, import_postcss_load_config.default)({}, rootDir);
1332
+ cachedConfig = {
1333
+ plugins: Array.isArray(result.plugins) ? result.plugins : [],
1334
+ options: result.options ?? {}
1335
+ };
1336
+ } catch {
1337
+ configFailed = true;
1338
+ cachedConfig = { plugins: [], options: {} };
1339
+ }
1340
+ return cachedConfig;
1341
+ }
1342
+ async function compileCss({
1343
+ code,
1344
+ filePath,
1345
+ rootDir,
1346
+ modules = false
1347
+ }) {
1348
+ const loaderHash = getCacheKey(JSON.stringify({ modules, filePath: filePath.replace(/\\+/g, "/") }));
1349
+ const contentHash = getCacheKey(code);
1350
+ const cacheKey = `${contentHash}-${loaderHash}`;
1351
+ const cached = transformCache.get(cacheKey);
1352
+ if (cached) {
1353
+ try {
1354
+ const parsed = JSON.parse(cached.transformed);
1355
+ return parsed;
1356
+ } catch {
1357
+ }
1358
+ }
1359
+ const { plugins, options } = await getPostcssConfig(rootDir);
1360
+ const pipeline = [...plugins];
1361
+ let tokens;
1362
+ if (modules) {
1363
+ const scopedName = (name, filename) => {
1364
+ const relative = import_path8.default.relative(rootDir, filename || filePath).replace(/\\+/g, "/");
1365
+ const seed = import_crypto2.default.createHash("sha1").update(relative).digest("hex").slice(0, 6);
1366
+ return `${name}___${seed}`;
1367
+ };
1368
+ pipeline.push(
1369
+ (0, import_postcss_modules.default)({
1370
+ generateScopedName: scopedName,
1371
+ getJSON(_filename, json) {
1372
+ tokens = json;
1373
+ }
1374
+ })
1375
+ );
1376
+ }
1377
+ const runner = (0, import_postcss.default)(pipeline);
1378
+ const result = await runner.process(code, {
1379
+ ...options,
1380
+ from: filePath,
1381
+ map: false
1382
+ });
1383
+ const compiled = {
1384
+ css: result.css,
1385
+ tokens
1386
+ };
1387
+ transformCache.set(cacheKey, {
1388
+ hash: contentHash,
1389
+ loaderHash,
1390
+ transformed: JSON.stringify(compiled),
1391
+ timestamp: Date.now()
1392
+ });
1393
+ return compiled;
1394
+ }
1395
+ function renderCssModule({
1396
+ css,
1397
+ filePath,
1398
+ tokens
1399
+ }) {
1400
+ const cssJson = JSON.stringify(css);
1401
+ const styleId = `ionify-css-${getCacheKey(filePath).slice(0, 8)}`;
1402
+ const tokensJson = tokens ? JSON.stringify(tokens) : "null";
1403
+ return `
1404
+ const cssText = ${cssJson};
1405
+ const styleId = ${JSON.stringify(styleId)};
1406
+ let style = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
1407
+ if (!style) {
1408
+ style = document.createElement("style");
1409
+ style.setAttribute("data-ionify-id", styleId);
1410
+ document.head.appendChild(style);
1411
+ }
1412
+ style.textContent = cssText;
1413
+ ${tokens ? `const tokens = ${tokensJson};` : ""}
1414
+ export const css = cssText;
1415
+ ${tokens ? `export const classes = tokens;
1416
+ export default tokens;` : `export default cssText;`}
1417
+ if (import.meta.hot) {
1418
+ import.meta.hot.accept();
1419
+ import.meta.hot.dispose(() => {
1420
+ const existing = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
1421
+ if (existing) existing.remove();
1422
+ });
1423
+ }
1424
+ `.trim();
1425
+ }
1426
+
1427
+ // src/core/loaders/asset.ts
1428
+ init_cjs_shims();
1429
+
1430
+ // src/core/utils/public-path.ts
1431
+ init_cjs_shims();
1432
+ var import_path9 = __toESM(require("path"), 1);
1433
+ var MODULE_PREFIX = "/__ionify__/modules/";
1434
+ function publicPathForFile(rootDir, absPath) {
1435
+ const normalizedRoot = import_path9.default.resolve(rootDir);
1436
+ const normalizedFile = import_path9.default.resolve(absPath);
1437
+ if (normalizedFile.startsWith(normalizedRoot + import_path9.default.sep) || normalizedFile === normalizedRoot) {
1438
+ const relative = import_path9.default.relative(normalizedRoot, normalizedFile).split(import_path9.default.sep).join("/");
1439
+ return "/" + (relative.length ? relative : "");
1440
+ }
1441
+ const encoded = Buffer.from(normalizedFile).toString("base64url");
1442
+ return MODULE_PREFIX + encoded;
1443
+ }
1444
+ function decodePublicPath(rootDir, urlPath) {
1445
+ if (urlPath.startsWith(MODULE_PREFIX)) {
1446
+ const encoded = urlPath.slice(MODULE_PREFIX.length);
1447
+ try {
1448
+ const decoded = Buffer.from(encoded, "base64url").toString("utf8");
1449
+ return import_path9.default.resolve(decoded);
1450
+ } catch {
1451
+ return null;
1452
+ }
1453
+ }
1454
+ const normalizedRoot = import_path9.default.resolve(rootDir);
1455
+ const joined = import_path9.default.resolve(normalizedRoot, "." + urlPath);
1456
+ if (!joined.startsWith(normalizedRoot + import_path9.default.sep) && joined !== normalizedRoot) {
1457
+ return null;
1458
+ }
1459
+ return joined;
1460
+ }
1461
+
1462
+ // src/core/loaders/asset.ts
1463
+ function assetAsModule(urlPath) {
1464
+ const safe = urlPath.replace(/"/g, "%22");
1465
+ return `export default "${safe}";`;
1466
+ }
1467
+ function isAssetExt(ext) {
1468
+ return [
1469
+ ".png",
1470
+ ".jpg",
1471
+ ".jpeg",
1472
+ ".gif",
1473
+ ".svg",
1474
+ ".ico",
1475
+ ".webp",
1476
+ ".avif",
1477
+ ".woff",
1478
+ ".woff2",
1479
+ ".ttf",
1480
+ ".otf",
1481
+ ".eot"
1482
+ ].includes(ext);
1483
+ }
1484
+ function contentTypeForAsset(ext) {
1485
+ switch (ext) {
1486
+ case ".png":
1487
+ return "image/png";
1488
+ case ".jpg":
1489
+ case ".jpeg":
1490
+ return "image/jpeg";
1491
+ case ".gif":
1492
+ return "image/gif";
1493
+ case ".svg":
1494
+ return "image/svg+xml";
1495
+ case ".ico":
1496
+ return "image/x-icon";
1497
+ case ".webp":
1498
+ return "image/webp";
1499
+ case ".avif":
1500
+ return "image/avif";
1501
+ case ".woff":
1502
+ return "font/woff";
1503
+ case ".woff2":
1504
+ return "font/woff2";
1505
+ case ".ttf":
1506
+ return "font/ttf";
1507
+ case ".otf":
1508
+ return "font/otf";
1509
+ case ".eot":
1510
+ return "application/vnd.ms-fontobject";
1511
+ default:
1512
+ return "application/octet-stream";
1513
+ }
1514
+ }
1515
+ function normalizeUrlFromFs(rootDir, fsPath) {
1516
+ return publicPathForFile(rootDir, fsPath);
1517
+ }
1518
+
1519
+ // src/core/loaders/registry.ts
1520
+ init_cjs_shims();
1521
+
1522
+ // src/core/loaders/js.ts
1523
+ init_cjs_shims();
1524
+ var import_core = require("@swc/core");
1525
+ var import_es_module_lexer = require("es-module-lexer");
1526
+ init_native();
1527
+ var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
1528
+ function needsReactRefresh(ext) {
1529
+ if (ext === ".jsx" || ext === ".tsx") return true;
1530
+ if (!ext.endsWith("x")) return false;
1531
+ return false;
1532
+ }
1533
+ function shouldTransform(ext, filePath) {
1534
+ if (!JS_EXTENSIONS.has(ext)) return false;
1535
+ if (filePath.endsWith(".d.ts")) return false;
1536
+ return true;
1537
+ }
1538
+ async function swcTranspile(code, filePath, ext, reactRefresh) {
1539
+ const isTypeScript = ext === ".ts" || ext === ".tsx";
1540
+ const isTsx = ext === ".tsx";
1541
+ const isJsx = ext === ".jsx";
1542
+ const swcParser = isTypeScript ? {
1543
+ syntax: "typescript",
1544
+ tsx: isTsx,
1545
+ decorators: true,
1546
+ dynamicImport: true
1547
+ } : {
1548
+ syntax: "ecmascript",
1549
+ jsx: isJsx,
1550
+ decorators: true,
1551
+ dynamicImport: true
1552
+ };
1553
+ const result = await (0, import_core.transform)(code, {
1554
+ filename: filePath,
1555
+ jsc: {
1556
+ parser: swcParser,
1557
+ target: "es2022",
1558
+ transform: reactRefresh ? {
1559
+ react: {
1560
+ development: true,
1561
+ refresh: true,
1562
+ runtime: "automatic"
1563
+ }
1564
+ } : void 0
1565
+ },
1566
+ sourceMaps: false,
1567
+ module: {
1568
+ type: "es6"
1569
+ }
1570
+ });
1571
+ return result.code ?? code;
1572
+ }
1573
+ function currentMode() {
1574
+ const mode = (process.env.IONIFY_PARSER || "hybrid").toLowerCase();
1575
+ if (mode === "swc") return "swc";
1576
+ if (mode === "oxc") return "oxc";
1577
+ return "hybrid";
1578
+ }
1579
+ var jsLoader = {
1580
+ name: "js",
1581
+ order: 0,
1582
+ test: ({ ext, path: filePath }) => shouldTransform(ext, filePath),
1583
+ transform: async ({ path: filePath, code, ext }) => {
1584
+ const isNodeModules = filePath.includes("node_modules");
1585
+ let output = code;
1586
+ if (isNodeModules) {
1587
+ const bundled = tryBundleNodeModule(filePath, code);
1588
+ if (bundled) {
1589
+ output = bundled;
1590
+ } else {
1591
+ output = code;
1592
+ }
1593
+ } else {
1594
+ const reactRefresh = needsReactRefresh(ext);
1595
+ const mode = currentMode();
1596
+ const nativeResult = tryNativeTransform(mode, code, {
1597
+ filename: filePath,
1598
+ jsx: ext === ".jsx" || ext === ".tsx",
1599
+ typescript: ext === ".ts" || ext === ".tsx",
1600
+ react_refresh: reactRefresh
1601
+ });
1602
+ if (nativeResult) {
1603
+ output = nativeResult.code ?? code;
1604
+ } else {
1605
+ output = await swcTranspile(code, filePath, ext, reactRefresh);
1606
+ }
1607
+ if (reactRefresh) {
1608
+ const prologue = `import { setupReactRefresh } from "/__ionify_react_refresh.js";
1609
+ const __ionifyRefresh__ = setupReactRefresh(import.meta.hot, import.meta.url);
1610
+ `;
1611
+ const epilogue = `
1612
+ __ionifyRefresh__?.finalize?.();
1613
+
1614
+ if (import.meta.hot) {
1615
+ import.meta.hot.accept((newModule) => {
1616
+ __ionifyRefresh__?.refresh?.(newModule);
1617
+ });
1618
+ import.meta.hot.dispose(() => {
1619
+ __ionifyRefresh__?.dispose?.();
1620
+ });
1621
+ }
1622
+ `;
1623
+ output = prologue + output + epilogue;
1624
+ } else {
1625
+ output += `
1626
+ if (import.meta.hot) {
1627
+ import.meta.hot.accept();
1628
+ }
1629
+ `;
1630
+ }
1631
+ }
1632
+ await import_es_module_lexer.init;
1633
+ const [imports] = (0, import_es_module_lexer.parse)(output);
1634
+ if (imports.length) {
1635
+ const rootDir = process.cwd();
1636
+ let rewritten = "";
1637
+ let lastIndex = 0;
1638
+ let mutated = false;
1639
+ for (const record of imports) {
1640
+ if (!record.n) continue;
1641
+ const spec = record.n;
1642
+ if (spec.startsWith("http://") || spec.startsWith("https://") || spec.startsWith(MODULE_PREFIX)) {
1643
+ continue;
1644
+ }
1645
+ let pathPart = spec;
1646
+ let suffix = "";
1647
+ const queryIndex = spec.indexOf("?");
1648
+ const hashIndex = spec.indexOf("#");
1649
+ const splitIndex = queryIndex === -1 ? hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
1650
+ if (splitIndex !== -1) {
1651
+ pathPart = spec.slice(0, splitIndex);
1652
+ suffix = spec.slice(splitIndex);
1653
+ }
1654
+ const resolved = resolveImport(pathPart, filePath);
1655
+ if (!resolved) continue;
1656
+ const resolvedExt = resolved.slice(resolved.lastIndexOf("."));
1657
+ let augmentedSuffix = suffix;
1658
+ if (resolvedExt === ".css" && !suffix) {
1659
+ augmentedSuffix = "?inline";
1660
+ }
1661
+ const assetExts = [
1662
+ ".png",
1663
+ ".jpg",
1664
+ ".jpeg",
1665
+ ".gif",
1666
+ ".svg",
1667
+ ".ico",
1668
+ ".webp",
1669
+ ".avif",
1670
+ ".woff",
1671
+ ".woff2",
1672
+ ".ttf",
1673
+ ".otf",
1674
+ ".eot"
1675
+ ];
1676
+ if (assetExts.includes(resolvedExt) && !suffix) {
1677
+ augmentedSuffix = "?import";
1678
+ }
1679
+ const replacementPath = publicPathForFile(rootDir, resolved);
1680
+ const replacement = replacementPath + augmentedSuffix;
1681
+ if (replacement === spec) continue;
1682
+ if (!mutated) {
1683
+ mutated = true;
1684
+ }
1685
+ if (record.t === 2) {
1686
+ rewritten += output.slice(lastIndex, record.s + 1);
1687
+ rewritten += replacement;
1688
+ rewritten += output[record.e - 1];
1689
+ lastIndex = record.e;
1690
+ } else {
1691
+ rewritten += output.slice(lastIndex, record.s);
1692
+ rewritten += replacement;
1693
+ lastIndex = record.e;
1694
+ }
1695
+ }
1696
+ if (mutated) {
1697
+ rewritten += output.slice(lastIndex);
1698
+ output = rewritten;
1699
+ }
1700
+ }
1701
+ return { code: output };
1702
+ }
1703
+ };
1704
+
1705
+ // src/core/loaders/registry.ts
1706
+ var registry = [];
1707
+ function registerLoader(registration) {
1708
+ registry.push(registration);
1709
+ }
1710
+ async function applyRegisteredLoaders(engine, config) {
1711
+ for (const registration of registry) {
1712
+ await registration(engine, config ?? null);
1713
+ }
1714
+ if (config?.plugins) {
1715
+ for (const plugin of config.plugins) {
1716
+ if (plugin.loaders) {
1717
+ for (const loader of plugin.loaders) {
1718
+ engine.useLoader(loader);
1719
+ }
1720
+ }
1721
+ if (plugin.setup) {
1722
+ const context = {
1723
+ registerLoader: (loader) => {
1724
+ engine.useLoader(loader);
1725
+ }
1726
+ };
1727
+ await plugin.setup(context);
1728
+ }
1729
+ }
1730
+ }
1731
+ if (config?.loaders) {
1732
+ for (const loader of config.loaders) {
1733
+ engine.useLoader(loader);
1734
+ }
1735
+ }
1736
+ }
1737
+ registerLoader((engine) => {
1738
+ engine.useLoader(jsLoader);
1739
+ });
1740
+
1741
+ // src/cli/utils/config.ts
1742
+ init_cjs_shims();
1743
+ var import_fs7 = __toESM(require("fs"), 1);
1744
+ var import_path10 = __toESM(require("path"), 1);
1745
+ var import_url2 = require("url");
1746
+ var import_esbuild = require("esbuild");
1747
+ var CONFIG_BASENAMES = [
1748
+ "ionify.config.ts",
1749
+ "ionify.config.mts",
1750
+ "ionify.config.js",
1751
+ "ionify.config.mjs",
1752
+ "ionify.config.cjs"
1753
+ ];
1754
+ var cachedConfig2 = null;
1755
+ var configLoaded = false;
1756
+ async function bundleConfig(entry) {
1757
+ const absDir = import_path10.default.dirname(entry);
1758
+ const inlineIonifyPlugin = {
1759
+ name: "inline-ionify",
1760
+ setup(build2) {
1761
+ build2.onResolve({ filter: /^ionify$/ }, () => ({
1762
+ path: "ionify-virtual",
1763
+ namespace: "ionify-ns"
1764
+ }));
1765
+ build2.onLoad({ filter: /.*/, namespace: "ionify-ns" }, () => ({
1766
+ contents: `
1767
+ export function defineConfig(config) {
1768
+ return typeof config === 'function' ? config : () => config;
1769
+ }
1770
+ `,
1771
+ loader: "js"
1772
+ }));
1773
+ }
1774
+ };
1775
+ const result = await (0, import_esbuild.build)({
1776
+ entryPoints: [entry],
1777
+ bundle: true,
1778
+ platform: "node",
1779
+ format: "esm",
1780
+ sourcemap: "inline",
1781
+ write: false,
1782
+ target: "node18",
1783
+ logLevel: "silent",
1784
+ absWorkingDir: absDir,
1785
+ plugins: [inlineIonifyPlugin]
1786
+ });
1787
+ const output = result.outputFiles?.[0];
1788
+ if (!output) throw new Error("Failed to bundle ionify config");
1789
+ const dirnameLiteral = JSON.stringify(absDir);
1790
+ const filenameLiteral = JSON.stringify(entry);
1791
+ const importMetaLiteral = JSON.stringify((0, import_url2.pathToFileURL)(entry).href);
1792
+ let contents = output.text;
1793
+ if (contents.includes("import.meta.url")) {
1794
+ contents = contents.replace(/import\.meta\.url/g, "__IONIFY_IMPORT_META_URL");
1795
+ contents = `const __IONIFY_IMPORT_META_URL = ${importMetaLiteral};
1796
+ ${contents}`;
1797
+ }
1798
+ const preamble = `const __dirname = ${dirnameLiteral};
1799
+ const __filename = ${filenameLiteral};
1800
+ `;
1801
+ return preamble + contents;
1802
+ }
1803
+ function findConfigFile(cwd) {
1804
+ for (const name of CONFIG_BASENAMES) {
1805
+ const candidate = import_path10.default.resolve(cwd, name);
1806
+ if (import_fs7.default.existsSync(candidate) && import_fs7.default.statSync(candidate).isFile()) {
1807
+ return candidate;
1808
+ }
1809
+ }
1810
+ return null;
1811
+ }
1812
+ async function loadIonifyConfig(cwd = process.cwd()) {
1813
+ if (configLoaded) return cachedConfig2;
1814
+ configLoaded = true;
1815
+ const configPath = findConfigFile(cwd);
1816
+ if (!configPath) {
1817
+ cachedConfig2 = null;
1818
+ configureResolverAliases(void 0, cwd);
1819
+ return cachedConfig2;
1820
+ }
1821
+ try {
244
1822
  const bundled = await bundleConfig(configPath);
245
1823
  const dataUrl = `data:text/javascript;base64,${Buffer.from(bundled).toString("base64")}`;
246
1824
  const imported = await import(dataUrl);
@@ -248,78 +1826,935 @@ async function loadIonifyConfig(cwd = process.cwd()) {
248
1826
  if (resolved && typeof resolved === "function") {
249
1827
  resolved = resolved({ mode: process.env.NODE_ENV || "development" });
250
1828
  }
251
- if (resolved && typeof resolved?.then === "function") {
252
- resolved = await resolved;
1829
+ if (resolved && typeof resolved?.then === "function") {
1830
+ resolved = await resolved;
1831
+ }
1832
+ if (resolved && typeof resolved === "object") {
1833
+ cachedConfig2 = resolved;
1834
+ const baseDir = import_path10.default.dirname(configPath);
1835
+ const aliases = resolved?.resolve?.alias;
1836
+ if (aliases && typeof aliases === "object") {
1837
+ configureResolverAliases(aliases, baseDir);
1838
+ } else {
1839
+ configureResolverAliases(void 0, baseDir);
1840
+ }
1841
+ logInfo(`Loaded ionify config from ${import_path10.default.relative(cwd, configPath)}`);
1842
+ } else {
1843
+ throw new Error("Config did not export an object");
1844
+ }
1845
+ } catch (err) {
1846
+ logError("Failed to load ionify.config", err);
1847
+ cachedConfig2 = null;
1848
+ configureResolverAliases(void 0, cwd);
1849
+ }
1850
+ return cachedConfig2;
1851
+ }
1852
+
1853
+ // src/cli/utils/minifier.ts
1854
+ init_cjs_shims();
1855
+ function normalize(value) {
1856
+ if (value === "oxc" || value === "swc" || value === "auto") return value;
1857
+ if (typeof value === "string") {
1858
+ const v = value.toLowerCase();
1859
+ if (v === "oxc" || v === "swc" || v === "auto") return v;
1860
+ }
1861
+ return null;
1862
+ }
1863
+ function resolveMinifier(config, opts = {}) {
1864
+ const fromCli = normalize(opts.cliFlag);
1865
+ if (fromCli) return fromCli;
1866
+ const fromEnv = normalize(opts.envVar);
1867
+ if (fromEnv) return fromEnv;
1868
+ const fromConfig = normalize(config?.minifier);
1869
+ if (fromConfig) return fromConfig;
1870
+ return "auto";
1871
+ }
1872
+ function applyMinifierEnv(choice) {
1873
+ process.env.IONIFY_MINIFIER = choice;
1874
+ }
1875
+
1876
+ // src/cli/utils/env.ts
1877
+ init_cjs_shims();
1878
+ var import_fs8 = __toESM(require("fs"), 1);
1879
+ var import_path11 = __toESM(require("path"), 1);
1880
+ function parseValue(raw) {
1881
+ let value = raw.trim();
1882
+ if (!value) return "";
1883
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1884
+ value = value.slice(1, -1);
1885
+ }
1886
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
1887
+ return value;
1888
+ }
1889
+ function parseEnvFile(source) {
1890
+ const env = {};
1891
+ const lines = source.split(/\r?\n/);
1892
+ for (const line of lines) {
1893
+ const trimmed = line.trim();
1894
+ if (!trimmed || trimmed.startsWith("#")) continue;
1895
+ const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_\.]*)\s*=\s*(.*)$/);
1896
+ if (!match) continue;
1897
+ const [, key, rest] = match;
1898
+ env[key] = parseValue(rest);
1899
+ }
1900
+ return env;
1901
+ }
1902
+ function loadEnv(mode = "development", rootDir = process.cwd()) {
1903
+ const candidates = [
1904
+ ".env",
1905
+ ".env.local",
1906
+ `.env.${mode}`,
1907
+ `.env.${mode}.local`
1908
+ ];
1909
+ const merged = {};
1910
+ for (const name of candidates) {
1911
+ const filePath = import_path11.default.resolve(rootDir, name);
1912
+ if (!import_fs8.default.existsSync(filePath) || !import_fs8.default.statSync(filePath).isFile()) {
1913
+ continue;
1914
+ }
1915
+ const contents = import_fs8.default.readFileSync(filePath, "utf8");
1916
+ const parsed = parseEnvFile(contents);
1917
+ Object.assign(merged, parsed);
1918
+ }
1919
+ for (const [key, value] of Object.entries(merged)) {
1920
+ process.env[key] = value;
1921
+ }
1922
+ return {
1923
+ ...merged
1924
+ };
1925
+ }
1926
+
1927
+ // src/cli/utils/treeshake.ts
1928
+ init_cjs_shims();
1929
+ var DEFAULT_RESOLUTION = {
1930
+ mode: "safe",
1931
+ include: [],
1932
+ exclude: []
1933
+ };
1934
+ function parseMode(value) {
1935
+ if (!value) return null;
1936
+ switch (value.toLowerCase()) {
1937
+ case "off":
1938
+ case "false":
1939
+ return "off";
1940
+ case "aggressive":
1941
+ return "aggressive";
1942
+ case "safe":
1943
+ case "true":
1944
+ return "safe";
1945
+ default:
1946
+ return null;
1947
+ }
1948
+ }
1949
+ function normalizeList(value) {
1950
+ if (!value) return [];
1951
+ if (Array.isArray(value)) {
1952
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
1953
+ }
1954
+ return [];
1955
+ }
1956
+ function parseEnvList(raw) {
1957
+ if (!raw || !raw.trim()) return null;
1958
+ try {
1959
+ const parsed = JSON.parse(raw);
1960
+ return normalizeList(parsed);
1961
+ } catch {
1962
+ return null;
1963
+ }
1964
+ }
1965
+ function extractConfigObject(value) {
1966
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1967
+ return value;
1968
+ }
1969
+ return null;
1970
+ }
1971
+ function resolveTreeshake(input, options = {}) {
1972
+ let resolved = { ...DEFAULT_RESOLUTION };
1973
+ const objectValue = extractConfigObject(input);
1974
+ if (objectValue) {
1975
+ resolved.include = normalizeList(objectValue.include);
1976
+ resolved.exclude = normalizeList(objectValue.exclude);
1977
+ if (objectValue.mode) {
1978
+ const objectMode = parseMode(objectValue.mode);
1979
+ if (objectMode) {
1980
+ resolved.mode = objectMode;
1981
+ }
1982
+ }
1983
+ } else if (typeof input === "boolean") {
1984
+ resolved.mode = input ? "safe" : "off";
1985
+ } else if (typeof input === "string") {
1986
+ resolved.mode = parseMode(input) ?? DEFAULT_RESOLUTION.mode;
1987
+ }
1988
+ const envMode = parseMode(options.envMode);
1989
+ if (envMode) {
1990
+ resolved.mode = envMode;
1991
+ }
1992
+ const includeOverride = parseEnvList(options.includeEnv);
1993
+ if (includeOverride) {
1994
+ resolved.include = includeOverride;
1995
+ }
1996
+ const excludeOverride = parseEnvList(options.excludeEnv);
1997
+ if (excludeOverride) {
1998
+ resolved.exclude = excludeOverride;
1999
+ }
2000
+ return resolved;
2001
+ }
2002
+ function applyTreeshakeEnv(resolved) {
2003
+ process.env.IONIFY_TREESHAKE = resolved.mode;
2004
+ process.env.IONIFY_TREESHAKE_INCLUDE = JSON.stringify(resolved.include);
2005
+ process.env.IONIFY_TREESHAKE_EXCLUDE = JSON.stringify(resolved.exclude);
2006
+ }
2007
+
2008
+ // src/cli/utils/scope-hoist.ts
2009
+ init_cjs_shims();
2010
+ var DEFAULT_SCOPE_HOIST = {
2011
+ enable: true,
2012
+ inlineFunctions: true,
2013
+ constantFolding: true,
2014
+ combineVariables: true
2015
+ };
2016
+ function parseBool(value) {
2017
+ if (value === true || value === false) return value;
2018
+ if (typeof value === "string") {
2019
+ const normalized = value.toLowerCase();
2020
+ if (["true", "1", "yes", "on", "enable"].includes(normalized)) return true;
2021
+ if (["false", "0", "no", "off", "disable"].includes(normalized)) return false;
2022
+ }
2023
+ return null;
2024
+ }
2025
+ function parseEnvFlag(value) {
2026
+ if (!value) return null;
2027
+ return parseBool(value);
2028
+ }
2029
+ function resolveScopeHoist(configValue, options = {}) {
2030
+ let resolved = { ...DEFAULT_SCOPE_HOIST };
2031
+ const scopeConfig = configValue;
2032
+ if (typeof scopeConfig === "boolean") {
2033
+ resolved.enable = scopeConfig;
2034
+ } else if (scopeConfig && typeof scopeConfig === "object") {
2035
+ resolved.enable = true;
2036
+ if (scopeConfig.inlineFunctions !== void 0) {
2037
+ resolved.inlineFunctions = !!scopeConfig.inlineFunctions;
2038
+ }
2039
+ if (scopeConfig.constantFolding !== void 0) {
2040
+ resolved.constantFolding = !!scopeConfig.constantFolding;
2041
+ }
2042
+ if (scopeConfig.combineVariables !== void 0) {
2043
+ resolved.combineVariables = !!scopeConfig.combineVariables;
2044
+ }
2045
+ }
2046
+ const envMode = parseEnvFlag(options.envMode);
2047
+ if (envMode !== null) {
2048
+ resolved.enable = envMode;
2049
+ }
2050
+ const inlineEnv = parseEnvFlag(options.inlineEnv);
2051
+ if (inlineEnv !== null) {
2052
+ resolved.inlineFunctions = inlineEnv;
2053
+ } else if (!resolved.enable) {
2054
+ resolved.inlineFunctions = false;
2055
+ }
2056
+ const constantEnv = parseEnvFlag(options.constantEnv);
2057
+ if (constantEnv !== null) {
2058
+ resolved.constantFolding = constantEnv;
2059
+ } else if (!resolved.enable) {
2060
+ resolved.constantFolding = false;
2061
+ }
2062
+ const combineEnv = parseEnvFlag(options.combineEnv);
2063
+ if (combineEnv !== null) {
2064
+ resolved.combineVariables = combineEnv;
2065
+ } else if (!resolved.enable) {
2066
+ resolved.combineVariables = false;
2067
+ }
2068
+ return resolved;
2069
+ }
2070
+ function applyScopeHoistEnv(result) {
2071
+ process.env.IONIFY_SCOPE_HOIST = result.enable ? "true" : "false";
2072
+ process.env.IONIFY_SCOPE_HOIST_INLINE = result.inlineFunctions ? "true" : "false";
2073
+ process.env.IONIFY_SCOPE_HOIST_CONST = result.constantFolding ? "true" : "false";
2074
+ process.env.IONIFY_SCOPE_HOIST_COMBINE = result.combineVariables ? "true" : "false";
2075
+ }
2076
+
2077
+ // src/cli/utils/parser.ts
2078
+ init_cjs_shims();
2079
+ function normalize2(mode) {
2080
+ if (typeof mode !== "string") return null;
2081
+ const lower = mode.toLowerCase();
2082
+ if (lower === "swc") return "swc";
2083
+ if (lower === "hybrid") return "hybrid";
2084
+ if (lower === "oxc") return "oxc";
2085
+ return null;
2086
+ }
2087
+ function resolveParser(config, opts) {
2088
+ const envRaw = opts?.envMode ?? process.env.IONIFY_PARSER;
2089
+ const env = normalize2(envRaw);
2090
+ if (env) return env;
2091
+ const fromConfig = normalize2(config?.parser);
2092
+ return fromConfig ?? "hybrid";
2093
+ }
2094
+ function applyParserEnv(mode) {
2095
+ process.env.IONIFY_PARSER = mode;
2096
+ }
2097
+
2098
+ // src/cli/commands/dev.ts
2099
+ init_cas();
2100
+ init_native();
2101
+ var import_crypto3 = __toESM(require("crypto"), 1);
2102
+ var __filename2 = (0, import_url4.fileURLToPath)(importMetaUrl);
2103
+ var __dirname = import_path12.default.dirname(__filename2);
2104
+ var CLIENT_DIR = import_path12.default.resolve(__dirname, "../client");
2105
+ var CLIENT_FALLBACK_DIR = import_path12.default.resolve(process.cwd(), "src/client");
2106
+ function readClientAssetFile(fileName) {
2107
+ const primary = import_path12.default.join(CLIENT_DIR, fileName);
2108
+ if (import_fs9.default.existsSync(primary)) {
2109
+ return { filePath: primary, code: import_fs9.default.readFileSync(primary, "utf8") };
2110
+ }
2111
+ const fallback = import_path12.default.join(CLIENT_FALLBACK_DIR, fileName);
2112
+ if (import_fs9.default.existsSync(fallback)) {
2113
+ return { filePath: fallback, code: import_fs9.default.readFileSync(fallback, "utf8") };
2114
+ }
2115
+ throw new Error(`Missing Ionify client asset: ${fileName}`);
2116
+ }
2117
+ function readClientAsset(fileName) {
2118
+ return readClientAssetFile(fileName).code;
2119
+ }
2120
+ function guessContentType(filePath) {
2121
+ const ext = import_path12.default.extname(filePath);
2122
+ if (ext === ".html") return "text/html; charset=utf-8";
2123
+ if (ext === ".css") return "text/css; charset=utf-8";
2124
+ if (ext === ".json") return "application/json; charset=utf-8";
2125
+ if ([".mjs", ".js", ".ts", ".tsx", ".jsx", ".cjs", ".mts", ".cts"].includes(ext))
2126
+ return "application/javascript; charset=utf-8";
2127
+ if ([".wasm"].includes(ext))
2128
+ return "application/wasm";
2129
+ if ([".map"].includes(ext))
2130
+ return "application/json; charset=utf-8";
2131
+ return "text/plain; charset=utf-8";
2132
+ }
2133
+ async function startDevServer({
2134
+ port = 5173,
2135
+ enableSignalHandlers = true
2136
+ } = {}) {
2137
+ const rootDir = process.cwd();
2138
+ const watcher = new IonifyWatcher(rootDir);
2139
+ const cacheDebug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
2140
+ const userConfig = await loadIonifyConfig();
2141
+ const minifier = resolveMinifier(userConfig, { envVar: process.env.IONIFY_MINIFIER });
2142
+ applyMinifierEnv(minifier);
2143
+ const parserMode = resolveParser(userConfig, { envMode: process.env.IONIFY_PARSER });
2144
+ applyParserEnv(parserMode);
2145
+ const treeshake = resolveTreeshake(userConfig?.treeshake, {
2146
+ envMode: process.env.IONIFY_TREESHAKE,
2147
+ includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
2148
+ excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
2149
+ });
2150
+ applyTreeshakeEnv(treeshake);
2151
+ const scopeHoist = resolveScopeHoist(userConfig?.scopeHoist, {
2152
+ envMode: process.env.IONIFY_SCOPE_HOIST,
2153
+ inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
2154
+ constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
2155
+ combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
2156
+ });
2157
+ applyScopeHoistEnv(scopeHoist);
2158
+ const resolvedEntry = userConfig?.entry ? userConfig.entry.startsWith("/") ? import_path12.default.join(rootDir, userConfig.entry) : import_path12.default.resolve(rootDir, userConfig.entry) : void 0;
2159
+ const pluginNames = Array.isArray(userConfig?.plugins) ? userConfig.plugins.map((p) => typeof p === "string" ? p : p?.name).filter((name) => typeof name === "string" && name.length > 0) : void 0;
2160
+ const rawVersionInputs = {
2161
+ parserMode,
2162
+ minifier,
2163
+ treeshake,
2164
+ scopeHoist,
2165
+ plugins: pluginNames,
2166
+ entry: resolvedEntry ? [resolvedEntry] : null,
2167
+ cssOptions: userConfig?.css,
2168
+ assetOptions: userConfig?.assets ?? userConfig?.asset
2169
+ };
2170
+ const configHash = computeGraphVersion(rawVersionInputs);
2171
+ logInfo(`[Dev] Version hash: ${configHash}`);
2172
+ process.env.IONIFY_CONFIG_HASH = configHash;
2173
+ const casRoot = import_path12.default.join(rootDir, ".ionify", "cas");
2174
+ const transformer = new TransformEngine({ casRoot, versionHash: configHash });
2175
+ const graph = new Graph(rawVersionInputs);
2176
+ if (native?.initAstCache) {
2177
+ const versionHash = JSON.stringify(rawVersionInputs);
2178
+ native.initAstCache(versionHash);
2179
+ logInfo(`AST cache initialized with version hash`);
2180
+ if (native?.astCacheWarmup) {
2181
+ try {
2182
+ native.astCacheWarmup();
2183
+ } catch (err) {
2184
+ logWarn(`AST cache warmup skipped: ${err}`);
2185
+ }
2186
+ }
2187
+ if (native?.astCacheStats) {
2188
+ try {
2189
+ const stats = native.astCacheStats();
2190
+ const entries = stats.total_entries ?? stats.totalEntries ?? 0;
2191
+ const sizeBytes = stats.total_size_bytes ?? stats.totalSizeBytes ?? 0;
2192
+ const hits = stats.total_hits ?? stats.totalHits ?? 0;
2193
+ const hitRate = stats.hit_rate ?? stats.hitRate ?? 0;
2194
+ logInfo(`[AST Cache] entries=${entries}, size=${sizeBytes} bytes, hits=${hits}, hitRate=${hitRate}`);
2195
+ } catch {
2196
+ }
2197
+ }
2198
+ }
2199
+ const moduleResolver = new ModuleResolver(rootDir, {
2200
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"],
2201
+ conditions: ["import", "default"],
2202
+ mainFields: ["module", "main"],
2203
+ ...userConfig?.resolve || {}
2204
+ });
2205
+ await applyRegisteredLoaders(transformer, userConfig);
2206
+ const hmr = new HMRServer();
2207
+ const envFromFiles = loadEnv("development", rootDir);
2208
+ process.env.NODE_ENV = process.env.NODE_ENV ?? "development";
2209
+ process.env.MODE = process.env.MODE ?? "development";
2210
+ const envValues = {
2211
+ ...envFromFiles,
2212
+ NODE_ENV: process.env.NODE_ENV,
2213
+ MODE: process.env.MODE
2214
+ };
2215
+ const envPlaceholderPattern = /%([A-Z0-9_]+)%/g;
2216
+ const envEnabledExts = /* @__PURE__ */ new Set([
2217
+ ".html",
2218
+ ".js",
2219
+ ".mjs",
2220
+ ".cjs",
2221
+ ".ts",
2222
+ ".tsx",
2223
+ ".jsx"
2224
+ ]);
2225
+ const applyEnvPlaceholders = (input, extname) => {
2226
+ if (!envEnabledExts.has(extname)) return input;
2227
+ return input.replace(envPlaceholderPattern, (match, key) => {
2228
+ if (key === "NODE_ENV" || key === "MODE" || key.startsWith("VITE_") || key.startsWith("IONIFY_")) {
2229
+ const replacement = envValues[key];
2230
+ return replacement !== void 0 ? replacement : match;
2231
+ }
2232
+ return match;
2233
+ });
2234
+ };
2235
+ const parseJsonBody = async (req) => {
2236
+ const chunks = [];
2237
+ await new Promise((resolve, reject) => {
2238
+ req.on("data", (chunk) => chunks.push(chunk));
2239
+ req.on("end", () => resolve());
2240
+ req.on("error", (err) => reject(err));
2241
+ });
2242
+ if (!chunks.length) return null;
2243
+ const raw = Buffer.concat(chunks).toString("utf8");
2244
+ if (!raw.trim()) return null;
2245
+ return JSON.parse(raw);
2246
+ };
2247
+ const sendJson = (res, status, payload) => {
2248
+ const body = JSON.stringify(payload);
2249
+ res.writeHead(status, {
2250
+ "Content-Type": "application/json; charset=utf-8",
2251
+ "Cache-Control": "no-store"
2252
+ });
2253
+ res.end(body);
2254
+ };
2255
+ const buildUpdatePayload = async (modules) => {
2256
+ const updates = [];
2257
+ for (const mod of modules) {
2258
+ const exists = import_fs9.default.existsSync(mod.absPath);
2259
+ if (mod.reason === "deleted" || !exists) {
2260
+ graph.removeFile(mod.absPath);
2261
+ watcher.unwatchFile(mod.absPath);
2262
+ updates.push({
2263
+ url: mod.url,
2264
+ hash: null,
2265
+ deps: [],
2266
+ reason: mod.reason,
2267
+ status: "deleted"
2268
+ });
2269
+ continue;
2270
+ }
2271
+ watcher.watchFile(mod.absPath);
2272
+ let code;
2273
+ try {
2274
+ code = import_fs9.default.readFileSync(mod.absPath, "utf8");
2275
+ } catch (err) {
2276
+ logError("Failed to read module during HMR apply", err);
2277
+ throw err;
2278
+ }
2279
+ let hash;
2280
+ let specs;
2281
+ if (native?.parseModuleIr) {
2282
+ try {
2283
+ const ir = native.parseModuleIr(mod.absPath, code);
2284
+ hash = ir.hash;
2285
+ specs = ir.dependencies.map((dep) => dep.specifier);
2286
+ } catch {
2287
+ hash = getCacheKey(code);
2288
+ specs = extractImports(code, mod.absPath);
2289
+ }
2290
+ } else {
2291
+ hash = getCacheKey(code);
2292
+ specs = extractImports(code, mod.absPath);
2293
+ }
2294
+ const depsAbs = resolveImports(specs, mod.absPath);
2295
+ graph.recordFile(mod.absPath, hash, depsAbs);
2296
+ for (const dep of depsAbs) {
2297
+ watcher.watchFile(dep);
2298
+ }
2299
+ const result = await transformer.run({
2300
+ path: mod.absPath,
2301
+ code,
2302
+ ext: import_path12.default.extname(mod.absPath),
2303
+ moduleHash: hash
2304
+ });
2305
+ const transformed = result.code;
2306
+ const envApplied = applyEnvPlaceholders(
2307
+ transformed,
2308
+ import_path12.default.extname(mod.absPath)
2309
+ );
2310
+ updates.push({
2311
+ url: mod.url,
2312
+ hash,
2313
+ deps: depsAbs.map((dep) => normalizeUrlFromFs(rootDir, dep)),
2314
+ reason: mod.reason,
2315
+ status: "updated",
2316
+ code: envApplied
2317
+ });
2318
+ }
2319
+ return updates;
2320
+ };
2321
+ const server = import_http.default.createServer(async (req, res) => {
2322
+ try {
2323
+ const parsed = import_url3.default.parse(req.url || "/", true);
2324
+ let reqPath = parsed.pathname || "/";
2325
+ try {
2326
+ reqPath = decodeURIComponent(reqPath);
2327
+ } catch {
2328
+ }
2329
+ const q = parsed.query || {};
2330
+ if (reqPath === "/__ionify_hmr") {
2331
+ hmr.handleSSE(req, res);
2332
+ return;
2333
+ }
2334
+ if (reqPath === "/__ionify_hmr_client.js") {
2335
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
2336
+ res.end(readClientAsset("hmr.js"));
2337
+ return;
2338
+ }
2339
+ if (reqPath === "/__ionify_overlay.js") {
2340
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
2341
+ res.end(readClientAsset("overlay.js"));
2342
+ return;
2343
+ }
2344
+ if (reqPath === "/__ionify_react_refresh.js") {
2345
+ try {
2346
+ const asset = readClientAssetFile("react-refresh-runtime.js");
2347
+ let reactRefreshPath;
2348
+ try {
2349
+ const projectRequire = (0, import_module3.createRequire)(import_path12.default.join(rootDir, "package.json"));
2350
+ reactRefreshPath = projectRequire.resolve("react-refresh/runtime");
2351
+ } catch (err) {
2352
+ logError("Failed to resolve react-refresh/runtime", err);
2353
+ res.statusCode = 500;
2354
+ res.end("Failed to resolve react-refresh/runtime. Make sure react-refresh is installed.");
2355
+ return;
2356
+ }
2357
+ const reactRefreshUrl = normalizeUrlFromFs(rootDir, reactRefreshPath);
2358
+ let code2 = asset.code.replace(
2359
+ 'import RefreshRuntime from "react-refresh/runtime"',
2360
+ `import RefreshRuntime from "${reactRefreshUrl}"`
2361
+ );
2362
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
2363
+ res.end(code2);
2364
+ } catch (err) {
2365
+ logError("Failed to serve react refresh runtime", err);
2366
+ res.statusCode = 500;
2367
+ res.end("Internal Server Error");
2368
+ }
2369
+ return;
2370
+ }
2371
+ if (reqPath === "/__ionify_hmr/apply") {
2372
+ if (req.method !== "POST") {
2373
+ res.writeHead(405, { Allow: "POST" });
2374
+ res.end("Method Not Allowed");
2375
+ return;
2376
+ }
2377
+ let body;
2378
+ try {
2379
+ body = await parseJsonBody(req);
2380
+ } catch (err) {
2381
+ logError("Invalid JSON body for HMR apply", err);
2382
+ sendJson(res, 400, { error: "Invalid JSON body" });
2383
+ return;
2384
+ }
2385
+ const id = typeof body?.id === "string" ? body.id : null;
2386
+ if (!id) {
2387
+ sendJson(res, 400, { error: "Missing update id" });
2388
+ return;
2389
+ }
2390
+ const pending = hmr.consumeUpdate(id);
2391
+ if (!pending) {
2392
+ sendJson(res, 404, { error: "Update not found", id });
2393
+ return;
2394
+ }
2395
+ try {
2396
+ const modules = await buildUpdatePayload(pending.modules);
2397
+ sendJson(res, 200, {
2398
+ type: "update",
2399
+ id: pending.summary.id,
2400
+ timestamp: Date.now(),
2401
+ modules
2402
+ });
2403
+ } catch (err) {
2404
+ logError("Failed to build HMR update payload", err);
2405
+ hmr.broadcastError({
2406
+ id,
2407
+ message: "Failed to compile update; falling back to full reload"
2408
+ });
2409
+ sendJson(res, 500, { error: "Failed to compile update", id });
2410
+ }
2411
+ return;
2412
+ }
2413
+ if (reqPath === "/__ionify_hmr/error") {
2414
+ if (req.method !== "POST") {
2415
+ res.writeHead(405, { Allow: "POST" });
2416
+ res.end("Method Not Allowed");
2417
+ return;
2418
+ }
2419
+ let body;
2420
+ try {
2421
+ body = await parseJsonBody(req);
2422
+ } catch {
2423
+ body = null;
2424
+ }
2425
+ const id = typeof body?.id === "string" ? body.id : void 0;
2426
+ const message = typeof body?.message === "string" ? body.message : "Unknown HMR error";
2427
+ logError(`[HMR] client reported error${id ? ` ${id}` : ""}: ${message}`);
2428
+ hmr.broadcastError({ id, message });
2429
+ sendJson(res, 200, { ok: true });
2430
+ return;
2431
+ }
2432
+ const fsPath = decodePublicPath(rootDir, reqPath);
2433
+ if (!fsPath) {
2434
+ res.statusCode = 404;
2435
+ res.end("Not found");
2436
+ return;
2437
+ }
2438
+ let effectiveFsPath = fsPath;
2439
+ let effectiveUrlPath = reqPath;
2440
+ if (import_fs9.default.existsSync(effectiveFsPath) && import_fs9.default.statSync(effectiveFsPath).isDirectory()) {
2441
+ const indexExtensions = [".html", ".js", ".ts", ".tsx", ".jsx"];
2442
+ let found = false;
2443
+ for (const ext2 of indexExtensions) {
2444
+ const indexFile = import_path12.default.join(effectiveFsPath, `index${ext2}`);
2445
+ if (import_fs9.default.existsSync(indexFile)) {
2446
+ effectiveFsPath = indexFile;
2447
+ effectiveUrlPath = effectiveUrlPath.endsWith("/") ? `${effectiveUrlPath}index${ext2}` : `${effectiveUrlPath}/index${ext2}`;
2448
+ found = true;
2449
+ break;
2450
+ }
2451
+ }
2452
+ if (!found) {
2453
+ const packageJson = import_path12.default.join(effectiveFsPath, "package.json");
2454
+ if (import_fs9.default.existsSync(packageJson)) {
2455
+ try {
2456
+ const pkg = JSON.parse(import_fs9.default.readFileSync(packageJson, "utf8"));
2457
+ if (pkg.main) {
2458
+ const mainFile = import_path12.default.join(effectiveFsPath, pkg.main);
2459
+ if (import_fs9.default.existsSync(mainFile)) {
2460
+ effectiveFsPath = mainFile;
2461
+ found = true;
2462
+ }
2463
+ }
2464
+ } catch (e) {
2465
+ }
2466
+ }
2467
+ }
2468
+ if (!found) {
2469
+ for (const ext2 of indexExtensions) {
2470
+ const moduleFile = import_path12.default.join(effectiveFsPath, `module${ext2}`);
2471
+ if (import_fs9.default.existsSync(moduleFile)) {
2472
+ effectiveFsPath = moduleFile;
2473
+ found = true;
2474
+ break;
2475
+ }
2476
+ }
2477
+ }
2478
+ if (!found) {
2479
+ res.statusCode = 404;
2480
+ res.end("Module not found");
2481
+ return;
2482
+ }
2483
+ }
2484
+ if (!import_fs9.default.existsSync(effectiveFsPath)) {
2485
+ res.statusCode = 404;
2486
+ res.end("Not found");
2487
+ return;
2488
+ }
2489
+ const ext = import_path12.default.extname(effectiveFsPath);
2490
+ if (isAssetExt(ext)) {
2491
+ try {
2492
+ const data = import_fs9.default.readFileSync(effectiveFsPath);
2493
+ const assetHash = import_crypto3.default.createHash("sha256").update(data).digest("hex");
2494
+ const kind = "asset";
2495
+ const changed2 = graph.recordFile(effectiveFsPath, assetHash, [], [], kind);
2496
+ watcher.watchFile(effectiveFsPath);
2497
+ if (changed2) {
2498
+ logInfo(`[Graph] Asset updated: ${effectiveFsPath}`);
2499
+ }
2500
+ } catch {
2501
+ }
2502
+ if ("import" in q) {
2503
+ const js = assetAsModule(normalizeUrlFromFs(rootDir, effectiveFsPath));
2504
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
2505
+ res.end(js);
2506
+ return;
2507
+ } else {
2508
+ res.writeHead(200, { "Content-Type": contentTypeForAsset(ext) });
2509
+ import_fs9.default.createReadStream(effectiveFsPath).pipe(res);
2510
+ return;
2511
+ }
2512
+ }
2513
+ if (ext === ".css") {
2514
+ try {
2515
+ const cssSource = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
2516
+ const isModule = "module" in q || /\.module\.css$/i.test(effectiveFsPath);
2517
+ const isInline = "inline" in q;
2518
+ const mode = isModule ? "css:module" : isInline ? "css:inline" : "css:raw";
2519
+ const contentHash = getCacheKey(cssSource);
2520
+ watcher.watchFile(effectiveFsPath);
2521
+ const kind = isModule ? "css-module" : "css";
2522
+ const changed2 = graph.recordFile(effectiveFsPath, contentHash, [], [], kind);
2523
+ const casDir = getCasArtifactPath(casRoot, configHash, contentHash);
2524
+ const casFile = import_path12.default.join(casDir, "transformed.js");
2525
+ let finalBuffer = null;
2526
+ if (import_fs9.default.existsSync(casFile)) {
2527
+ try {
2528
+ finalBuffer = import_fs9.default.readFileSync(casFile);
2529
+ res.setHeader("X-Ionify-Cache", "HIT");
2530
+ } catch {
2531
+ finalBuffer = null;
2532
+ }
2533
+ }
2534
+ if (!finalBuffer) {
2535
+ const { css: compiledCss, tokens } = await compileCss({
2536
+ code: cssSource,
2537
+ filePath: effectiveFsPath,
2538
+ rootDir,
2539
+ modules: isModule
2540
+ });
2541
+ const body = isModule || isInline ? renderCssModule({
2542
+ css: compiledCss,
2543
+ filePath: effectiveFsPath,
2544
+ tokens: isModule ? tokens ?? {} : void 0
2545
+ }) : compiledCss;
2546
+ finalBuffer = Buffer.from(body, "utf8");
2547
+ res.setHeader("X-Ionify-Cache", "MISS");
2548
+ try {
2549
+ import_fs9.default.mkdirSync(casDir, { recursive: true });
2550
+ import_fs9.default.writeFileSync(casFile, finalBuffer);
2551
+ } catch {
2552
+ }
2553
+ }
2554
+ if (isModule || isInline) {
2555
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
2556
+ } else {
2557
+ res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
2558
+ }
2559
+ res.end(finalBuffer);
2560
+ logInfo(`Served: ${effectiveUrlPath} deps:0 ${changed2 ? "(updated)" : "(cached)"}`);
2561
+ return;
2562
+ } catch (err) {
2563
+ logError("Failed to process CSS", err);
2564
+ res.statusCode = 500;
2565
+ res.end("Failed to process CSS");
2566
+ return;
2567
+ }
2568
+ }
2569
+ const code = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
2570
+ let hash;
2571
+ let specs;
2572
+ if (native?.parseModuleIr) {
2573
+ try {
2574
+ const ir = native.parseModuleIr(effectiveFsPath, code);
2575
+ hash = ir.hash;
2576
+ specs = ir.dependencies.map((dep) => dep.specifier);
2577
+ } catch {
2578
+ hash = getCacheKey(code);
2579
+ specs = extractImports(code, effectiveFsPath);
2580
+ }
2581
+ } else {
2582
+ hash = getCacheKey(code);
2583
+ specs = extractImports(code, effectiveFsPath);
2584
+ }
2585
+ const depsAbs = resolveImports(specs, effectiveFsPath);
2586
+ const changed = graph.recordFile(effectiveFsPath, hash, depsAbs);
2587
+ watcher.watchFile(effectiveFsPath);
2588
+ for (const dep of depsAbs) {
2589
+ watcher.watchFile(dep);
2590
+ }
2591
+ const result = await transformer.run({
2592
+ path: effectiveFsPath,
2593
+ code,
2594
+ ext,
2595
+ moduleHash: hash
2596
+ });
2597
+ const transformedCode = result.code;
2598
+ res.setHeader("X-Ionify-Cache", changed ? "MISS" : "HIT");
2599
+ const envApplied = applyEnvPlaceholders(transformedCode, ext);
2600
+ if (import_path12.default.extname(effectiveFsPath) === ".html") {
2601
+ const injected = injectHMRClient(envApplied);
2602
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
2603
+ res.end(injected);
2604
+ } else {
2605
+ const finalBuffer = Buffer.from(envApplied);
2606
+ res.setHeader("Content-Type", guessContentType(effectiveFsPath));
2607
+ res.end(finalBuffer);
2608
+ }
2609
+ logInfo(`Served: ${effectiveUrlPath} deps:${depsAbs.length} ${changed ? "(updated)" : "(cached)"}`);
2610
+ if (cacheDebug) {
2611
+ const m = transformCache.metrics();
2612
+ logInfo(`[Ionify][Dev Cache] hits:${m.hits} misses:${m.misses} size:${m.size}`);
2613
+ }
2614
+ } catch (err) {
2615
+ logError("Error serving request:", err);
2616
+ res.statusCode = 500;
2617
+ res.end("Internal Server Error");
2618
+ }
2619
+ });
2620
+ watcher.on("change", (file, status) => {
2621
+ logInfo(`[Watcher] ${status}: ${file}`);
2622
+ const affected = graph.collectAffected([file]);
2623
+ if (!affected.includes(file)) {
2624
+ affected.unshift(file);
253
2625
  }
254
- if (resolved && typeof resolved === "object") {
255
- cachedConfig = resolved;
256
- const baseDir = import_path3.default.dirname(configPath);
257
- const aliases = resolved?.resolve?.alias;
258
- if (aliases && typeof aliases === "object") {
259
- configureResolverAliases(aliases, baseDir);
260
- } else {
261
- configureResolverAliases(void 0, baseDir);
2626
+ const modules = [];
2627
+ for (const absPath of affected) {
2628
+ const reason = absPath === file ? status === "deleted" ? "deleted" : "changed" : "dependent";
2629
+ let hash = null;
2630
+ if (reason !== "deleted") {
2631
+ if (absPath === file) {
2632
+ try {
2633
+ const code = import_fs9.default.readFileSync(absPath, "utf8");
2634
+ hash = getCacheKey(code);
2635
+ } catch {
2636
+ hash = graph.getNode(absPath)?.hash ?? null;
2637
+ }
2638
+ } else {
2639
+ hash = graph.getNode(absPath)?.hash ?? null;
2640
+ }
262
2641
  }
263
- logInfo(`Loaded ionify config from ${import_path3.default.relative(cwd, configPath)}`);
264
- } else {
265
- throw new Error("Config did not export an object");
2642
+ modules.push({
2643
+ absPath,
2644
+ url: normalizeUrlFromFs(rootDir, absPath),
2645
+ hash,
2646
+ reason
2647
+ });
266
2648
  }
267
- } catch (err) {
268
- logError("Failed to load ionify.config", err);
269
- cachedConfig = null;
270
- configureResolverAliases(void 0, cwd);
271
- }
272
- return cachedConfig;
273
- }
274
-
275
- // src/cli/commands/dev.ts
276
- async function startDevServer(options = {}) {
277
- try {
278
- const config = await loadIonifyConfig();
279
- const port = parseInt(options.port || config?.server?.port || "5173");
280
- logInfo(`Starting Ionify dev server on port ${port}...`);
281
- const result = native.startDevServer({
282
- port,
283
- root: process.cwd(),
284
- config: config || {}
285
- });
286
- logInfo(`Dev server running at http://localhost:${port}`);
287
- return result;
288
- } catch (err) {
289
- logError(`Dev server failed: ${err.message}`);
290
- throw err;
291
- }
292
- }
293
-
294
- // src/cli/commands/build.ts
295
- async function runBuildCommand(options = {}) {
296
- try {
297
- const config = await loadIonifyConfig();
298
- const outDir = options.outDir || config?.build?.outDir || "dist";
299
- const level = options.level ?? config?.optimizationLevel ?? 3;
300
- logInfo(`Building for production (optimization level: ${level})...`);
301
- const result = native.build({
302
- root: process.cwd(),
303
- outDir,
304
- optimizationLevel: level,
305
- config: config || {}
306
- });
307
- logInfo(`\u2705 Build complete! Output: ${outDir}/`);
308
- return result;
309
- } catch (err) {
310
- logError(`Build failed: ${err.message}`);
311
- throw err;
2649
+ const summary = hmr.queueUpdate(modules);
2650
+ if (summary) {
2651
+ logInfo(
2652
+ `[HMR] update ${summary.id} -> ${summary.modules.length} module(s) queued`
2653
+ );
2654
+ }
2655
+ if (status === "deleted") {
2656
+ graph.removeFile(file);
2657
+ watcher.unwatchFile(file);
2658
+ }
2659
+ });
2660
+ let closingPromise = null;
2661
+ let cleanedUp = false;
2662
+ const signalHandlers = [];
2663
+ const cleanup = (force = false) => {
2664
+ if (cleanedUp) return;
2665
+ cleanedUp = true;
2666
+ if (force) {
2667
+ server.getConnections((err, count) => {
2668
+ if (!err && count > 0) {
2669
+ server.closeAllConnections();
2670
+ }
2671
+ });
2672
+ }
2673
+ try {
2674
+ watcher.closeAll();
2675
+ } catch (err) {
2676
+ logError("Error closing watcher:", err);
2677
+ }
2678
+ try {
2679
+ hmr.close();
2680
+ } catch (err) {
2681
+ logError("Error closing HMR:", err);
2682
+ }
2683
+ graph.flush();
2684
+ for (const { event, handler } of signalHandlers) {
2685
+ process.off(event, handler);
2686
+ }
2687
+ };
2688
+ server.on("close", () => cleanup(false));
2689
+ const shutdown = async (exitProcess) => {
2690
+ if (!closingPromise) {
2691
+ closingPromise = new Promise((resolve, reject) => {
2692
+ const timeoutId = setTimeout(() => {
2693
+ logInfo("Server shutdown taking too long, forcing cleanup...");
2694
+ cleanup(true);
2695
+ resolve();
2696
+ }, 3e3);
2697
+ server.close((err) => {
2698
+ clearTimeout(timeoutId);
2699
+ if (err) {
2700
+ logError("Error during server shutdown:", err);
2701
+ reject(err);
2702
+ } else {
2703
+ resolve();
2704
+ }
2705
+ });
2706
+ });
2707
+ }
2708
+ try {
2709
+ await Promise.race([
2710
+ closingPromise,
2711
+ new Promise(
2712
+ (_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), 5e3)
2713
+ )
2714
+ ]);
2715
+ } catch (err) {
2716
+ logError("Shutdown error:", err);
2717
+ cleanup(true);
2718
+ }
2719
+ if (exitProcess) {
2720
+ setTimeout(() => process.exit(0), 100);
2721
+ }
2722
+ };
2723
+ if (enableSignalHandlers) {
2724
+ const onSignal = () => {
2725
+ void shutdown(true);
2726
+ };
2727
+ process.on("SIGINT", onSignal);
2728
+ process.on("SIGTERM", onSignal);
2729
+ signalHandlers.push({ event: "SIGINT", handler: onSignal });
2730
+ signalHandlers.push({ event: "SIGTERM", handler: onSignal });
312
2731
  }
2732
+ await new Promise((resolve) => {
2733
+ server.listen(port, () => resolve());
2734
+ });
2735
+ const address = server.address();
2736
+ const actualPort = address && typeof address === "object" && address?.port ? address.port : port;
2737
+ logInfo(`Ionify Dev Server (Phase 2) at http://localhost:${actualPort}`);
2738
+ logInfo(`HMR listening at /__ionify_hmr (SSE)`);
2739
+ return {
2740
+ server,
2741
+ port: actualPort,
2742
+ close: async () => {
2743
+ await shutdown(false);
2744
+ }
2745
+ };
313
2746
  }
314
2747
 
315
2748
  // src/cli/commands/analyze.ts
316
- var import_fs3 = __toESM(require("fs"), 1);
317
- var import_path4 = __toESM(require("path"), 1);
2749
+ init_cjs_shims();
2750
+ var import_fs10 = __toESM(require("fs"), 1);
2751
+ var import_path13 = __toESM(require("path"), 1);
2752
+ init_native();
318
2753
  function readGraphFromDisk(root) {
319
- const file = import_path4.default.join(root, ".ionify", "graph.json");
320
- if (!import_fs3.default.existsSync(file)) return null;
2754
+ const file = import_path13.default.join(root, ".ionify", "graph.json");
2755
+ if (!import_fs10.default.existsSync(file)) return null;
321
2756
  try {
322
- const raw = import_fs3.default.readFileSync(file, "utf8");
2757
+ const raw = import_fs10.default.readFileSync(file, "utf8");
323
2758
  const snapshot = JSON.parse(raw);
324
2759
  if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
325
2760
  return Object.entries(snapshot.nodes).map(([id, node]) => ({
@@ -414,31 +2849,980 @@ async function runAnalyzeCommand(options = {}) {
414
2849
  }
415
2850
  }
416
2851
 
2852
+ // src/cli/commands/build.ts
2853
+ init_cjs_shims();
2854
+ var import_fs12 = __toESM(require("fs"), 1);
2855
+ var import_path15 = __toESM(require("path"), 1);
2856
+ init_native();
2857
+ init_cas();
2858
+
2859
+ // src/cli/utils/optimization-level.ts
2860
+ init_cjs_shims();
2861
+ function getOptimizationPreset(level) {
2862
+ switch (level) {
2863
+ case 0:
2864
+ return {
2865
+ minifier: "swc",
2866
+ treeshake: {
2867
+ mode: "off",
2868
+ include: [],
2869
+ exclude: []
2870
+ },
2871
+ scopeHoist: {
2872
+ enable: false,
2873
+ inlineFunctions: false,
2874
+ constantFolding: false,
2875
+ combineVariables: false
2876
+ }
2877
+ };
2878
+ case 1:
2879
+ return {
2880
+ minifier: "oxc",
2881
+ treeshake: {
2882
+ mode: "safe",
2883
+ include: [],
2884
+ exclude: []
2885
+ },
2886
+ scopeHoist: {
2887
+ enable: true,
2888
+ inlineFunctions: true,
2889
+ constantFolding: false,
2890
+ combineVariables: false
2891
+ }
2892
+ };
2893
+ case 2:
2894
+ return {
2895
+ minifier: "oxc",
2896
+ treeshake: {
2897
+ mode: "safe",
2898
+ include: [],
2899
+ exclude: []
2900
+ },
2901
+ scopeHoist: {
2902
+ enable: true,
2903
+ inlineFunctions: true,
2904
+ constantFolding: true,
2905
+ combineVariables: true
2906
+ }
2907
+ };
2908
+ case 3:
2909
+ return {
2910
+ minifier: "oxc",
2911
+ treeshake: {
2912
+ mode: "aggressive",
2913
+ include: [],
2914
+ exclude: []
2915
+ },
2916
+ scopeHoist: {
2917
+ enable: true,
2918
+ inlineFunctions: true,
2919
+ constantFolding: true,
2920
+ combineVariables: true
2921
+ }
2922
+ };
2923
+ default:
2924
+ return getOptimizationPreset(2);
2925
+ }
2926
+ }
2927
+ function resolveOptimizationLevel(configLevel, options = {}) {
2928
+ if (options.cliLevel !== void 0) {
2929
+ const parsed = typeof options.cliLevel === "number" ? options.cliLevel : parseInt(options.cliLevel, 10);
2930
+ if ([0, 1, 2, 3].includes(parsed)) {
2931
+ return parsed;
2932
+ }
2933
+ }
2934
+ if (options.envLevel) {
2935
+ const parsed = parseInt(options.envLevel, 10);
2936
+ if ([0, 1, 2, 3].includes(parsed)) {
2937
+ return parsed;
2938
+ }
2939
+ }
2940
+ if (configLevel !== void 0 && [0, 1, 2, 3].includes(configLevel)) {
2941
+ return configLevel;
2942
+ }
2943
+ return null;
2944
+ }
2945
+
2946
+ // src/core/bundler.ts
2947
+ init_cjs_shims();
2948
+ var import_fs11 = __toESM(require("fs"), 1);
2949
+ var import_path14 = __toESM(require("path"), 1);
2950
+ var import_crypto4 = __toESM(require("crypto"), 1);
2951
+ init_native();
2952
+ init_cache();
2953
+ function readGraphSnapshot() {
2954
+ if (native?.graphLoadMap) {
2955
+ try {
2956
+ const nativeMap = native.graphLoadMap();
2957
+ if (nativeMap && Object.keys(nativeMap).length > 0) {
2958
+ return Object.values(nativeMap).map((node) => ({
2959
+ id: node.id,
2960
+ hash: node.hash,
2961
+ deps: node.deps || [],
2962
+ dynamicDeps: node.dynamicDeps || [],
2963
+ kind: node.kind
2964
+ }));
2965
+ }
2966
+ } catch (err) {
2967
+ logWarn(`Failed to load native graph: ${String(err)}`);
2968
+ }
2969
+ }
2970
+ const file = import_path14.default.join(process.cwd(), ".ionify", "graph.json");
2971
+ if (!import_fs11.default.existsSync(file)) return [];
2972
+ try {
2973
+ const raw = import_fs11.default.readFileSync(file, "utf8");
2974
+ const snapshot = JSON.parse(raw);
2975
+ if (snapshot?.version !== 1 || !snapshot?.nodes) return [];
2976
+ return Object.entries(snapshot.nodes).map(([id, node]) => ({
2977
+ id,
2978
+ hash: typeof node.hash === "string" ? node.hash : null,
2979
+ deps: Array.isArray(node.deps) ? node.deps : []
2980
+ }));
2981
+ } catch (err) {
2982
+ logWarn(`Failed to read graph snapshot: ${String(err)}`);
2983
+ return [];
2984
+ }
2985
+ }
2986
+ var JS_EXTENSIONS2 = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
2987
+ var CSS_EXTENSIONS = /* @__PURE__ */ new Set([".css"]);
2988
+ function classifyModuleKind(id) {
2989
+ const ext = import_path14.default.extname(id).toLowerCase();
2990
+ if (CSS_EXTENSIONS.has(ext)) return "css";
2991
+ if (JS_EXTENSIONS2.has(ext)) return "js";
2992
+ return "asset";
2993
+ }
2994
+ var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
2995
+ var toPosix = (p) => p.split(import_path14.default.sep).join("/");
2996
+ function minifyCss(input) {
2997
+ return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,])\s*/g, "$1").trim();
2998
+ }
2999
+ function orderCssModules(chunk) {
3000
+ const cssModules = chunk.modules.filter((m) => m.kind === "css");
3001
+ const cssSet = new Set(cssModules.map((m) => m.id));
3002
+ const adj = /* @__PURE__ */ new Map();
3003
+ for (const mod of cssModules) {
3004
+ const deps = [...mod.deps || [], ...mod.dynamicDeps || []].filter((d) => cssSet.has(d));
3005
+ deps.sort();
3006
+ adj.set(mod.id, deps);
3007
+ }
3008
+ const visited = /* @__PURE__ */ new Set();
3009
+ const temp = /* @__PURE__ */ new Set();
3010
+ const ordered = [];
3011
+ const dfs = (id) => {
3012
+ if (visited.has(id) || temp.has(id)) return;
3013
+ temp.add(id);
3014
+ const edges = adj.get(id) || [];
3015
+ for (const dep of edges) dfs(dep);
3016
+ temp.delete(id);
3017
+ visited.add(id);
3018
+ ordered.push(id);
3019
+ };
3020
+ const sorted = [...cssModules.map((m) => m.id)].sort();
3021
+ for (const id of sorted) {
3022
+ dfs(id);
3023
+ }
3024
+ return ordered;
3025
+ }
3026
+ function normalizeModules(rawModules) {
3027
+ const modules = [];
3028
+ for (const raw of rawModules) {
3029
+ if (typeof raw === "string") {
3030
+ modules.push({
3031
+ id: raw,
3032
+ hash: null,
3033
+ kind: classifyModuleKind(raw),
3034
+ deps: [],
3035
+ dynamicDeps: []
3036
+ });
3037
+ continue;
3038
+ }
3039
+ if (!raw || typeof raw !== "object") continue;
3040
+ const id = typeof raw.id === "string" ? raw.id : null;
3041
+ if (!id) continue;
3042
+ const rawKind = typeof raw.kind === "string" ? raw.kind : classifyModuleKind(id);
3043
+ const kind = rawKind === "css" || rawKind === "asset" ? rawKind : "js";
3044
+ const deps = Array.isArray(raw.deps) ? raw.deps.filter(isNonEmptyString) : [];
3045
+ const dynamicSource = Array.isArray(raw.dynamicDeps) ? raw.dynamicDeps : Array.isArray(raw.dynamic_deps) ? raw.dynamic_deps : [];
3046
+ const dynamicDeps = dynamicSource.filter(isNonEmptyString);
3047
+ const hash = typeof raw.hash === "string" && raw.hash.length ? raw.hash : null;
3048
+ modules.push({
3049
+ id,
3050
+ hash,
3051
+ kind,
3052
+ deps,
3053
+ dynamicDeps
3054
+ });
3055
+ }
3056
+ return modules;
3057
+ }
3058
+ function normalizePlan(plan) {
3059
+ const entries = Array.isArray(plan?.entries) ? Array.from(new Set(plan.entries.filter(isNonEmptyString))) : [];
3060
+ const rawChunks = Array.isArray(plan?.chunks) ? plan.chunks : [];
3061
+ const normalizedChunks = rawChunks.map((chunk, index) => {
3062
+ const id = typeof chunk?.id === "string" && chunk.id.length ? chunk.id : `chunk-${index}`;
3063
+ const modules = normalizeModules(Array.isArray(chunk?.modules) ? chunk.modules : []);
3064
+ const consumersRaw = Array.isArray(chunk?.consumers) ? chunk.consumers.filter(isNonEmptyString) : null;
3065
+ const cssRaw = Array.isArray(chunk?.css) ? chunk.css.filter(isNonEmptyString) : null;
3066
+ const assetsRaw = Array.isArray(chunk?.assets) ? chunk.assets.filter(isNonEmptyString) : null;
3067
+ const consumers = consumersRaw && consumersRaw.length ? Array.from(new Set(consumersRaw)) : [...entries];
3068
+ const inferredCss = cssRaw && cssRaw.length ? cssRaw : modules.filter((m) => m.kind === "css").map((m) => m.id);
3069
+ const inferredAssets = assetsRaw && assetsRaw.length ? assetsRaw : modules.filter((m) => m.kind === "asset").map((m) => m.id);
3070
+ return {
3071
+ id,
3072
+ modules,
3073
+ entry: chunk?.entry === true,
3074
+ shared: chunk?.shared === true,
3075
+ consumers,
3076
+ css: inferredCss,
3077
+ assets: inferredAssets
3078
+ };
3079
+ });
3080
+ return {
3081
+ entries,
3082
+ chunks: normalizedChunks
3083
+ };
3084
+ }
3085
+ function fallbackPlan(entries) {
3086
+ const nodes = readGraphSnapshot();
3087
+ logInfo(`[Fallback] modules: ${nodes.length}, entries: ${entries?.length ?? 0}`);
3088
+ logInfo(`[Fallback] module IDs: ${nodes.map((n) => n.id).join(", ")}`);
3089
+ logInfo(`[Fallback] entry IDs: ${entries?.join(", ") ?? "none"}`);
3090
+ const modules = nodes.map((n) => n.id);
3091
+ const deps = /* @__PURE__ */ new Set();
3092
+ for (const node of nodes) {
3093
+ for (const dep of node.deps) deps.add(dep);
3094
+ }
3095
+ let finalEntries = entries && entries.length ? [...entries] : modules.filter((m) => !deps.has(m));
3096
+ if (!finalEntries.length && modules.length) {
3097
+ finalEntries = [modules[0]];
3098
+ }
3099
+ const nodeMap = new Map(nodes.map((node) => [node.id, node]));
3100
+ const planModules = modules.map((id) => {
3101
+ const node = nodeMap.get(id);
3102
+ return {
3103
+ id,
3104
+ hash: node?.hash ?? null,
3105
+ kind: node?.kind ?? classifyModuleKind(id),
3106
+ deps: node?.deps ?? [],
3107
+ dynamicDeps: node?.dynamicDeps ?? []
3108
+ };
3109
+ });
3110
+ const css = planModules.filter((m) => m.kind === "css").map((m) => m.id);
3111
+ const assets = planModules.filter((m) => m.kind === "asset").map((m) => m.id);
3112
+ return normalizePlan({
3113
+ entries: finalEntries,
3114
+ chunks: [
3115
+ {
3116
+ id: "chunk-main",
3117
+ modules: planModules,
3118
+ entry: true,
3119
+ shared: false,
3120
+ consumers: finalEntries,
3121
+ css,
3122
+ assets
3123
+ }
3124
+ ]
3125
+ });
3126
+ }
3127
+ async function generateBuildPlan(entries, versionInputs) {
3128
+ const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
3129
+ logInfo(`Graph version: ${version || "default"}`);
3130
+ const graphDbPath = import_path14.default.join(process.cwd(), ".ionify", "graph.db");
3131
+ ensureNativeGraph(graphDbPath, version);
3132
+ let moduleCount = 0;
3133
+ if (native?.graphLoadMap) {
3134
+ try {
3135
+ const persistedGraph = native.graphLoadMap();
3136
+ const graphSize = persistedGraph ? Object.keys(persistedGraph).length : 0;
3137
+ moduleCount = graphSize;
3138
+ logInfo(`Native graph loaded: ${graphSize} modules`);
3139
+ if (persistedGraph && graphSize > 0) {
3140
+ logInfo(`Loaded persisted graph with ${graphSize} modules`);
3141
+ }
3142
+ } catch (err) {
3143
+ logWarn(`Failed to load persisted graph: ${String(err)}`);
3144
+ }
3145
+ } else {
3146
+ logWarn(`graphLoadMap not available, native binding: ${!!native}`);
3147
+ }
3148
+ if (moduleCount === 0 && entries?.length && native) {
3149
+ logWarn(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
3150
+ const queue = [...entries];
3151
+ const seen = new Set(queue);
3152
+ while (queue.length) {
3153
+ const file = queue.shift();
3154
+ if (!import_fs11.default.existsSync(file)) continue;
3155
+ const code = import_fs11.default.readFileSync(file, "utf8");
3156
+ let hash = getCacheKey(code);
3157
+ let specs = [];
3158
+ if (native.parseModuleIr) {
3159
+ try {
3160
+ const ir = native.parseModuleIr(file, code);
3161
+ hash = ir.hash;
3162
+ specs = ir.dependencies.map((d) => d.specifier);
3163
+ } catch {
3164
+ specs = extractImports(code, file);
3165
+ }
3166
+ } else {
3167
+ specs = extractImports(code, file);
3168
+ }
3169
+ const depsAbs = resolveImports(specs, file);
3170
+ if (typeof native.graphRecord === "function") {
3171
+ native.graphRecord(file, hash, depsAbs, [], "module");
3172
+ } else if (typeof native.recordFile === "function") {
3173
+ native.recordFile(file, hash, depsAbs, [], "module");
3174
+ }
3175
+ for (const dep of depsAbs) {
3176
+ if (!seen.has(dep)) {
3177
+ seen.add(dep);
3178
+ queue.push(dep);
3179
+ }
3180
+ }
3181
+ }
3182
+ try {
3183
+ if (typeof native.loadModulesCount === "function") {
3184
+ moduleCount = native.loadModulesCount() ?? moduleCount;
3185
+ } else if (native.graphLoadMap) {
3186
+ const persistedGraph = native.graphLoadMap();
3187
+ moduleCount = persistedGraph ? Object.keys(persistedGraph).length : moduleCount;
3188
+ }
3189
+ } catch {
3190
+ }
3191
+ logInfo(`[Build] Dependency graph rebuilt: ${moduleCount} modules`);
3192
+ }
3193
+ if (native?.plannerPlanBuild) {
3194
+ try {
3195
+ const start = Date.now();
3196
+ logInfo(`[Planner] Calling native plannerPlanBuild with ${entries?.length ?? 0} entries`);
3197
+ const plan = native.plannerPlanBuild(entries ?? []);
3198
+ logInfo(`[Planner] Native plan returned: ${plan.entries.length} entries, ${plan.chunks.length} chunks in ${Date.now() - start}ms`);
3199
+ return normalizePlan(plan);
3200
+ } catch (err) {
3201
+ logWarn(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
3202
+ }
3203
+ }
3204
+ return fallbackPlan(entries);
3205
+ }
3206
+ async function writeBuildManifest(outputDir, plan, artifacts) {
3207
+ const filesByChunk = /* @__PURE__ */ new Map();
3208
+ for (const artifact of artifacts) {
3209
+ filesByChunk.set(artifact.id, artifact.files);
3210
+ }
3211
+ const manifest = {
3212
+ entries: plan.entries,
3213
+ chunks: plan.chunks.map((chunk) => ({
3214
+ id: chunk.id,
3215
+ entry: chunk.entry,
3216
+ shared: chunk.shared,
3217
+ consumers: chunk.consumers,
3218
+ modules: chunk.modules.map((mod) => ({
3219
+ id: mod.id,
3220
+ kind: mod.kind,
3221
+ deps: mod.deps,
3222
+ dynamicDeps: mod.dynamicDeps
3223
+ })),
3224
+ files: filesByChunk.get(chunk.id) ?? { js: [], css: [], assets: [] }
3225
+ }))
3226
+ };
3227
+ const dir = import_path14.default.resolve(outputDir);
3228
+ await import_fs11.default.promises.mkdir(dir, { recursive: true });
3229
+ const file = import_path14.default.join(dir, "manifest.json");
3230
+ await import_fs11.default.promises.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
3231
+ }
3232
+ async function emitChunks(outputDir, plan, moduleOutputs, opts) {
3233
+ if (!native?.buildChunks) {
3234
+ logWarn("Native buildChunks binding is not available; using JS fallback emitter.");
3235
+ const rawArtifacts2 = buildJsFallbackArtifacts(plan, moduleOutputs);
3236
+ return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts2);
3237
+ }
3238
+ const start = Date.now();
3239
+ const rawArtifacts = native.buildChunks(plan, opts?.casRoot, opts?.versionHash) ?? [];
3240
+ logInfo(`[Bundler] buildChunks completed in ${Date.now() - start}ms (native)`);
3241
+ return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts);
3242
+ }
3243
+ function buildJsFallbackArtifacts(plan, moduleOutputs) {
3244
+ const artifacts = [];
3245
+ for (const chunk of plan.chunks) {
3246
+ const jsParts = [];
3247
+ const assets = [];
3248
+ for (const mod of chunk.modules) {
3249
+ const output = moduleOutputs.get(mod.id);
3250
+ if (output?.type === "js") {
3251
+ jsParts.push(`// ${mod.id}
3252
+ ${output.code}`);
3253
+ }
3254
+ }
3255
+ for (const assetPath of chunk.assets) {
3256
+ try {
3257
+ const data = import_fs11.default.readFileSync(assetPath);
3258
+ if (data.length < 4096) {
3259
+ const mime = "application/octet-stream";
3260
+ const inline = `data:${mime};base64,${data.toString("base64")}`;
3261
+ jsParts.push(`// ${assetPath}
3262
+ export const __ionify_asset = "${inline}";`);
3263
+ continue;
3264
+ }
3265
+ const hash = import_crypto4.default.createHash("sha256").update(data).digest("hex").slice(0, 16);
3266
+ const ext = import_path14.default.extname(assetPath) || ".bin";
3267
+ const fileName = `assets/${hash}${ext}`;
3268
+ assets.push({
3269
+ source: assetPath,
3270
+ file_name: fileName
3271
+ });
3272
+ } catch {
3273
+ const fileName = import_path14.default.basename(assetPath) || "asset";
3274
+ assets.push({
3275
+ source: assetPath,
3276
+ file_name: fileName
3277
+ });
3278
+ }
3279
+ }
3280
+ const code = jsParts.length ? jsParts.join("\n\n") : `// Ionify JS fallback for ${chunk.id}
3281
+ export default {};`;
3282
+ artifacts.push({
3283
+ id: chunk.id,
3284
+ file_name: `${chunk.id}.fallback.js`,
3285
+ code,
3286
+ map: null,
3287
+ assets,
3288
+ code_bytes: Buffer.byteLength(code, "utf8"),
3289
+ map_bytes: 0
3290
+ });
3291
+ }
3292
+ return artifacts;
3293
+ }
3294
+ function normalizeNativeArtifact(raw) {
3295
+ const id = raw.id;
3296
+ if (!id) {
3297
+ throw new Error("Native artifact missing id");
3298
+ }
3299
+ const file_name = raw.file_name ?? `${id.replace(/::/g, ".")}.native.js`;
3300
+ const code = raw.code ?? "";
3301
+ const map = raw.map ?? null;
3302
+ const code_bytes = typeof raw.code_bytes === "number" ? raw.code_bytes : Buffer.byteLength(code, "utf8");
3303
+ const map_bytes = typeof raw.map_bytes === "number" ? raw.map_bytes : map ? Buffer.byteLength(map, "utf8") : 0;
3304
+ const assets = Array.isArray(raw.assets) ? raw.assets.map((asset) => ({
3305
+ source: asset.source,
3306
+ file_name: asset.file_name ?? asset.fileName ?? import_path14.default.basename(asset.source ?? "asset")
3307
+ })) : [];
3308
+ return { id, file_name, code, map, assets, code_bytes, map_bytes };
3309
+ }
3310
+ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts) {
3311
+ const chunkDir = import_path14.default.join(outputDir, "chunks");
3312
+ await import_fs11.default.promises.mkdir(chunkDir, { recursive: true });
3313
+ const assetsDir = import_path14.default.join(outputDir, "assets");
3314
+ await import_fs11.default.promises.mkdir(assetsDir, { recursive: true });
3315
+ const enableSourceMaps = process.env.IONIFY_SOURCEMAPS === "true";
3316
+ const grouped = /* @__PURE__ */ new Map();
3317
+ for (const raw of rawArtifacts) {
3318
+ const artifact = normalizeNativeArtifact(raw);
3319
+ const baseId = artifact.id.split("::")[0] ?? artifact.id;
3320
+ const bucket = grouped.get(baseId);
3321
+ if (bucket) bucket.push(artifact);
3322
+ else grouped.set(baseId, [artifact]);
3323
+ }
3324
+ const buildStats = {};
3325
+ const results = [];
3326
+ for (const chunk of plan.chunks) {
3327
+ const artifacts = grouped.get(chunk.id);
3328
+ if (!artifacts || !artifacts.length) {
3329
+ throw new Error(`Native bundler did not emit artifacts for ${chunk.id}`);
3330
+ }
3331
+ const chunkOutDir = import_path14.default.join(chunkDir, chunk.id);
3332
+ await import_fs11.default.promises.mkdir(chunkOutDir, { recursive: true });
3333
+ artifacts.sort((a, b) => {
3334
+ if (a.id === chunk.id) return -1;
3335
+ if (b.id === chunk.id) return 1;
3336
+ return a.id.localeCompare(b.id);
3337
+ });
3338
+ const jsFiles = [];
3339
+ const cssFiles = [];
3340
+ const assetFiles = [];
3341
+ const assetWritten = /* @__PURE__ */ new Set();
3342
+ const copyAssets = async (assets) => {
3343
+ for (const asset of assets) {
3344
+ if (!asset?.source) continue;
3345
+ const relName = asset.file_name ?? import_path14.default.basename(asset.source);
3346
+ const assetFile = import_path14.default.join(outputDir, relName);
3347
+ if (assetWritten.has(assetFile)) continue;
3348
+ try {
3349
+ const data = await import_fs11.default.promises.readFile(asset.source);
3350
+ await import_fs11.default.promises.mkdir(import_path14.default.dirname(assetFile), { recursive: true });
3351
+ await import_fs11.default.promises.writeFile(assetFile, data);
3352
+ const rel = toPosix(import_path14.default.relative(outputDir, assetFile));
3353
+ buildStats[rel] = {
3354
+ bytes: data.length,
3355
+ emitter: "native",
3356
+ type: "asset"
3357
+ };
3358
+ assetFiles.push(rel);
3359
+ assetWritten.add(assetFile);
3360
+ } catch (err) {
3361
+ logWarn(`Failed to emit asset ${asset.source}: ${String(err)}`);
3362
+ }
3363
+ }
3364
+ };
3365
+ const cssOrder = orderCssModules(chunk);
3366
+ let cssFileRel = null;
3367
+ if (cssOrder.length) {
3368
+ const seenCss = /* @__PURE__ */ new Set();
3369
+ const cssPieces = [];
3370
+ for (const cssPath of cssOrder) {
3371
+ let cssSource = moduleOutputs.get(cssPath)?.code;
3372
+ if (!cssSource && import_fs11.default.existsSync(cssPath)) {
3373
+ try {
3374
+ cssSource = await import_fs11.default.promises.readFile(cssPath, "utf8");
3375
+ } catch (err) {
3376
+ logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3377
+ }
3378
+ }
3379
+ if (!cssSource) continue;
3380
+ const minified = minifyCss(cssSource);
3381
+ if (!minified.length) continue;
3382
+ const key = getCacheKey(minified);
3383
+ if (seenCss.has(key)) continue;
3384
+ seenCss.add(key);
3385
+ cssPieces.push(minified);
3386
+ }
3387
+ if (cssPieces.length) {
3388
+ const combinedCss = cssPieces.join("\n");
3389
+ const cssHash = getCacheKey(combinedCss).slice(0, 8);
3390
+ const cssFileName = `assets/${chunk.id}.${cssHash}.css`;
3391
+ const cssFilePath = import_path14.default.join(outputDir, cssFileName);
3392
+ await import_fs11.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
3393
+ cssFileRel = toPosix(import_path14.default.relative(outputDir, cssFilePath));
3394
+ buildStats[cssFileRel] = {
3395
+ bytes: Buffer.byteLength(combinedCss),
3396
+ emitter: "native",
3397
+ type: "css"
3398
+ };
3399
+ cssFiles.push(cssFileRel);
3400
+ }
3401
+ }
3402
+ for (const artifact of artifacts) {
3403
+ const nativeFile = import_path14.default.join(chunkOutDir, artifact.file_name);
3404
+ let nativeCode = artifact.code;
3405
+ if (cssFileRel) {
3406
+ const absCss = import_path14.default.join(outputDir, cssFileRel);
3407
+ const relCss = toPosix(import_path14.default.relative(import_path14.default.dirname(nativeFile), absCss));
3408
+ const inject = `(()=>{const url=new URL(${JSON.stringify(
3409
+ relCss
3410
+ )},import.meta.url).toString();if(typeof document!=="undefined"&&!document.querySelector('link[data-ionify-css="'+url+'"]')){const l=document.createElement("link");l.rel="stylesheet";l.href=url;l.setAttribute("data-ionify-css",url);document.head.appendChild(l);}})();`;
3411
+ nativeCode = `${inject}
3412
+ ${nativeCode}`;
3413
+ }
3414
+ if (enableSourceMaps && artifact.map) {
3415
+ const mapFile = `${nativeFile}.map`;
3416
+ await import_fs11.default.promises.writeFile(mapFile, artifact.map, "utf8");
3417
+ nativeCode = `${nativeCode}
3418
+ //# sourceMappingURL=${import_path14.default.basename(mapFile)}`;
3419
+ const relMap = toPosix(import_path14.default.relative(outputDir, mapFile));
3420
+ buildStats[relMap] = {
3421
+ bytes: artifact.map_bytes,
3422
+ emitter: "native",
3423
+ type: "map"
3424
+ };
3425
+ jsFiles.push(relMap);
3426
+ }
3427
+ await import_fs11.default.promises.writeFile(nativeFile, nativeCode, "utf8");
3428
+ const relNative = toPosix(import_path14.default.relative(outputDir, nativeFile));
3429
+ buildStats[relNative] = {
3430
+ bytes: artifact.code_bytes,
3431
+ emitter: "native",
3432
+ type: "js"
3433
+ };
3434
+ jsFiles.push(relNative);
3435
+ await copyAssets(artifact.assets);
3436
+ }
3437
+ if (chunk.css.length) {
3438
+ const seenCss = /* @__PURE__ */ new Set();
3439
+ const cssSources = [];
3440
+ for (const cssPath of chunk.css) {
3441
+ if (!seenCss.add(cssPath)) continue;
3442
+ const output = moduleOutputs.get(cssPath);
3443
+ if (output?.type === "css") {
3444
+ cssSources.push(output.code);
3445
+ } else if (import_fs11.default.existsSync(cssPath)) {
3446
+ try {
3447
+ cssSources.push(await import_fs11.default.promises.readFile(cssPath, "utf8"));
3448
+ } catch (err) {
3449
+ logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3450
+ }
3451
+ }
3452
+ }
3453
+ if (cssSources.length) {
3454
+ const combinedCss = cssSources.join("\n\n");
3455
+ const cssHash = import_crypto4.default.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
3456
+ const cssFileName = `${chunk.id}.${cssHash}.native.css`;
3457
+ const cssFilePath = import_path14.default.join(chunkOutDir, cssFileName);
3458
+ await import_fs11.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
3459
+ const relCss = import_path14.default.relative(outputDir, cssFilePath);
3460
+ buildStats[relCss] = {
3461
+ bytes: Buffer.byteLength(combinedCss),
3462
+ emitter: "native",
3463
+ type: "css"
3464
+ };
3465
+ cssFiles.push(relCss);
3466
+ }
3467
+ }
3468
+ results.push({
3469
+ id: chunk.id,
3470
+ files: {
3471
+ js: jsFiles,
3472
+ css: cssFiles,
3473
+ assets: assetFiles
3474
+ }
3475
+ });
3476
+ }
3477
+ return { artifacts: results, stats: buildStats };
3478
+ }
3479
+ async function writeAssetsManifest(outputDir, artifacts) {
3480
+ const dir = import_path14.default.resolve(outputDir);
3481
+ await import_fs11.default.promises.mkdir(dir, { recursive: true });
3482
+ const file = import_path14.default.join(dir, "manifest.assets.json");
3483
+ const payload = {
3484
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3485
+ chunks: artifacts
3486
+ };
3487
+ await import_fs11.default.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
3488
+ }
3489
+
3490
+ // src/core/worker/pool.ts
3491
+ init_cjs_shims();
3492
+ var import_worker_threads = require("worker_threads");
3493
+ var import_os = __toESM(require("os"), 1);
3494
+ var import_url5 = require("url");
3495
+ var workerPath = (0, import_url5.fileURLToPath)(new URL("./worker.cjs", importMetaUrl));
3496
+ var TransformWorkerPool = class {
3497
+ workers = [];
3498
+ queue = [];
3499
+ active = /* @__PURE__ */ new Map();
3500
+ callbacks = /* @__PURE__ */ new Map();
3501
+ waiters = [];
3502
+ pendingBytes = 0;
3503
+ closed = false;
3504
+ size;
3505
+ maxQueueBytes;
3506
+ constructor(options = {}) {
3507
+ const cpuDefault = Math.max(1, import_os.default.cpus().length - 1);
3508
+ this.size = Math.max(1, options.size ?? cpuDefault);
3509
+ this.maxQueueBytes = options.maxQueueBytes;
3510
+ for (let i = 0; i < this.size; i++) {
3511
+ this.spawnWorker();
3512
+ }
3513
+ }
3514
+ spawnWorker() {
3515
+ const worker = new import_worker_threads.Worker(workerPath, { env: process.env });
3516
+ const id = worker.threadId;
3517
+ worker.on("message", (message) => {
3518
+ const item = this.active.get(id);
3519
+ if (item) {
3520
+ this.active.delete(id);
3521
+ this.pendingBytes -= item.size;
3522
+ this.resolveWaiters();
3523
+ }
3524
+ const cb = message ? this.callbacks.get(message.id) : void 0;
3525
+ if (message && cb) cb(message);
3526
+ if (message) this.callbacks.delete(message.id);
3527
+ this.dequeue(worker);
3528
+ });
3529
+ worker.on("error", (err) => {
3530
+ logWarn(`Transform worker error: ${String(err)}`);
3531
+ const item = this.active.get(id);
3532
+ if (item) {
3533
+ this.active.delete(id);
3534
+ this.queue.unshift(item);
3535
+ }
3536
+ this.spawnWorker();
3537
+ });
3538
+ worker.on("exit", (code) => {
3539
+ const item = this.active.get(id);
3540
+ if (item) {
3541
+ this.active.delete(id);
3542
+ this.queue.unshift(item);
3543
+ }
3544
+ if (!this.closed && code !== 0) {
3545
+ logWarn(`Transform worker exited unexpectedly (${code}), respawning`);
3546
+ this.spawnWorker();
3547
+ }
3548
+ });
3549
+ this.workers.push(worker);
3550
+ }
3551
+ dequeue(worker) {
3552
+ if (this.queue.length === 0) return;
3553
+ const item = this.queue.shift();
3554
+ this.active.set(worker.threadId, item);
3555
+ worker.postMessage(item.job);
3556
+ }
3557
+ resolveWaiters() {
3558
+ if (!this.maxQueueBytes) return;
3559
+ while (this.waiters.length && this.pendingBytes < this.maxQueueBytes) {
3560
+ const resolve = this.waiters.shift();
3561
+ resolve && resolve();
3562
+ }
3563
+ }
3564
+ async run(job) {
3565
+ if (this.closed) {
3566
+ throw new Error("Worker pool already closed");
3567
+ }
3568
+ const size = Buffer.byteLength(job.code, "utf8");
3569
+ if (this.maxQueueBytes) {
3570
+ while (this.pendingBytes + size > this.maxQueueBytes) {
3571
+ await new Promise((resolve) => this.waiters.push(resolve));
3572
+ await new Promise((r) => setTimeout(r, 50 + Math.random() * 100));
3573
+ }
3574
+ }
3575
+ this.pendingBytes += size;
3576
+ return new Promise((resolve) => {
3577
+ this.callbacks.set(job.id, resolve);
3578
+ const idleWorker = this.workers.find((w) => !this.active.has(w.threadId));
3579
+ const item = { job, size };
3580
+ if (idleWorker) {
3581
+ this.active.set(idleWorker.threadId, item);
3582
+ idleWorker.postMessage(job);
3583
+ } else {
3584
+ this.queue.push(item);
3585
+ }
3586
+ });
3587
+ }
3588
+ async runMany(jobs) {
3589
+ const resultMap = /* @__PURE__ */ new Map();
3590
+ await Promise.all(
3591
+ jobs.map(async (job) => {
3592
+ const res = await this.run(job);
3593
+ resultMap.set(job.id, res);
3594
+ })
3595
+ );
3596
+ return jobs.map((job) => resultMap.get(job.id));
3597
+ }
3598
+ async close() {
3599
+ this.closed = true;
3600
+ await Promise.all(this.workers.map((worker) => worker.terminate()));
3601
+ this.workers = [];
3602
+ this.queue = [];
3603
+ this.active.clear();
3604
+ this.callbacks.clear();
3605
+ this.waiters.forEach((resolve) => resolve());
3606
+ this.waiters = [];
3607
+ this.pendingBytes = 0;
3608
+ }
3609
+ async drain() {
3610
+ while (!this.closed && (this.queue.length || this.active.size)) {
3611
+ await new Promise((r) => setTimeout(r, 100));
3612
+ }
3613
+ }
3614
+ };
3615
+
3616
+ // src/cli/commands/build.ts
3617
+ init_cache();
3618
+ async function runBuildCommand(options = {}) {
3619
+ try {
3620
+ const config = await loadIonifyConfig();
3621
+ const optLevel = resolveOptimizationLevel(config?.optimizationLevel, {
3622
+ cliLevel: options.level,
3623
+ envLevel: process.env.IONIFY_OPTIMIZATION_LEVEL
3624
+ });
3625
+ let minifier;
3626
+ const parserMode = resolveParser(config, { envMode: process.env.IONIFY_PARSER });
3627
+ let treeshake;
3628
+ let scopeHoist;
3629
+ if (optLevel !== null) {
3630
+ const preset = getOptimizationPreset(optLevel);
3631
+ minifier = preset.minifier;
3632
+ treeshake = preset.treeshake;
3633
+ scopeHoist = preset.scopeHoist;
3634
+ logInfo(`Using optimization level ${optLevel} (preset)`);
3635
+ } else {
3636
+ minifier = resolveMinifier(config, { envVar: process.env.IONIFY_MINIFIER });
3637
+ treeshake = resolveTreeshake(config?.treeshake, {
3638
+ envMode: process.env.IONIFY_TREESHAKE,
3639
+ includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
3640
+ excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
3641
+ });
3642
+ scopeHoist = resolveScopeHoist(config?.scopeHoist, {
3643
+ envMode: process.env.IONIFY_SCOPE_HOIST,
3644
+ inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
3645
+ constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
3646
+ combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
3647
+ });
3648
+ }
3649
+ applyMinifierEnv(minifier);
3650
+ applyParserEnv(parserMode);
3651
+ applyTreeshakeEnv(treeshake);
3652
+ applyScopeHoistEnv(scopeHoist);
3653
+ const entries = config?.entry ? [config.entry.startsWith("/") ? import_path15.default.join(process.cwd(), config.entry) : import_path15.default.resolve(process.cwd(), config.entry)] : void 0;
3654
+ if (entries) {
3655
+ logInfo(`Build entries: ${entries.join(", ")}`);
3656
+ } else {
3657
+ logInfo(`No entries in config, planner will infer from graph`);
3658
+ }
3659
+ const pluginNames = Array.isArray(config?.plugins) ? config.plugins.map((p) => typeof p === "string" ? p : p?.name).filter((name) => typeof name === "string" && name.length > 0) : void 0;
3660
+ const rawVersionInputs = {
3661
+ parserMode,
3662
+ minifier,
3663
+ treeshake,
3664
+ scopeHoist,
3665
+ plugins: pluginNames,
3666
+ entry: entries ?? null,
3667
+ cssOptions: config?.css,
3668
+ assetOptions: config?.assets ?? config?.asset
3669
+ };
3670
+ const configHash = computeGraphVersion(rawVersionInputs);
3671
+ logInfo(`[Build] Version hash: ${configHash}`);
3672
+ process.env.IONIFY_CONFIG_HASH = configHash;
3673
+ if (native?.initAstCache) {
3674
+ const versionHash = JSON.stringify(rawVersionInputs);
3675
+ native.initAstCache(versionHash);
3676
+ logInfo(`AST cache initialized with version hash`);
3677
+ }
3678
+ const plan = await generateBuildPlan(entries, rawVersionInputs);
3679
+ const outDir = options.outDir || "dist";
3680
+ const moduleHashes = /* @__PURE__ */ new Map();
3681
+ for (const chunk of plan.chunks) {
3682
+ for (const mod of chunk.modules) {
3683
+ if (mod.hash) {
3684
+ moduleHashes.set(mod.id, mod.hash);
3685
+ }
3686
+ }
3687
+ }
3688
+ const uniqueModules = /* @__PURE__ */ new Set();
3689
+ for (const chunk of plan.chunks) {
3690
+ for (const mod of chunk.modules) uniqueModules.add(mod.id);
3691
+ }
3692
+ const moduleOutputs = /* @__PURE__ */ new Map();
3693
+ const pool = new TransformWorkerPool();
3694
+ try {
3695
+ const jobs = Array.from(uniqueModules).filter((filePath) => import_fs12.default.existsSync(filePath)).map((filePath) => {
3696
+ const code = import_fs12.default.readFileSync(filePath, "utf8");
3697
+ const sourceHash = getCacheKey(code);
3698
+ const moduleHash = moduleHashes.get(filePath) ?? sourceHash;
3699
+ const cacheKey = getCacheKey(`build-worker:v1:${import_path15.default.extname(filePath)}:${moduleHash}:${filePath}`);
3700
+ const cached = readCache(cacheKey);
3701
+ if (cached) {
3702
+ try {
3703
+ const parsed = JSON.parse(cached.toString("utf8"));
3704
+ if (parsed?.code) {
3705
+ const transformedHash = getCacheKey(parsed.code);
3706
+ moduleHashes.set(filePath, transformedHash);
3707
+ const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
3708
+ const cacheDir = getCasArtifactPath(casRoot2, configHash, transformedHash);
3709
+ if (!import_fs12.default.existsSync(import_path15.default.join(cacheDir, "transformed.js"))) {
3710
+ import_fs12.default.mkdirSync(cacheDir, { recursive: true });
3711
+ import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js"), parsed.code, "utf8");
3712
+ if (parsed.map) {
3713
+ import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
3714
+ }
3715
+ }
3716
+ moduleOutputs.set(filePath, { code: parsed.code, type: parsed.type ?? "js" });
3717
+ return null;
3718
+ }
3719
+ } catch {
3720
+ }
3721
+ }
3722
+ return {
3723
+ id: filePath,
3724
+ filePath,
3725
+ ext: import_path15.default.extname(filePath),
3726
+ code,
3727
+ cacheKey
3728
+ };
3729
+ }).filter((job) => !!job);
3730
+ const results = await pool.runMany(
3731
+ jobs.map((job) => ({
3732
+ id: job.id,
3733
+ filePath: job.filePath,
3734
+ ext: job.ext,
3735
+ code: job.code
3736
+ }))
3737
+ );
3738
+ for (let i = 0; i < results.length; i++) {
3739
+ const result = results[i];
3740
+ const job = jobs[i];
3741
+ if (result.error) {
3742
+ throw new Error(`Transform failed for ${result.filePath}: ${result.error}`);
3743
+ }
3744
+ const payload = JSON.stringify({ code: result.code, map: result.map, type: result.type });
3745
+ writeCache(job.cacheKey, Buffer.from(payload));
3746
+ const transformedHash = getCacheKey(result.code);
3747
+ const moduleHash = transformedHash;
3748
+ const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
3749
+ const versionHash = configHash;
3750
+ const cacheDir = getCasArtifactPath(casRoot2, versionHash, moduleHash);
3751
+ import_fs12.default.mkdirSync(cacheDir, { recursive: true });
3752
+ import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js"), result.code, "utf8");
3753
+ if (result.map) {
3754
+ import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), result.map, "utf8");
3755
+ }
3756
+ moduleHashes.set(job.filePath, moduleHash);
3757
+ for (const chunk of plan.chunks) {
3758
+ for (const mod of chunk.modules) {
3759
+ if (mod.id === job.filePath) {
3760
+ mod.hash = moduleHash;
3761
+ }
3762
+ }
3763
+ }
3764
+ moduleOutputs.set(result.filePath, { code: result.code, type: result.type });
3765
+ }
3766
+ } finally {
3767
+ await pool.close();
3768
+ }
3769
+ for (const chunk of plan.chunks) {
3770
+ for (const mod of chunk.modules) {
3771
+ const updatedHash = moduleHashes.get(mod.id);
3772
+ if (updatedHash) {
3773
+ mod.hash = updatedHash;
3774
+ }
3775
+ }
3776
+ }
3777
+ const absOutDir = import_path15.default.resolve(outDir);
3778
+ const casRoot = import_path15.default.join(process.cwd(), ".ionify", "cas");
3779
+ const { artifacts, stats } = await emitChunks(absOutDir, plan, moduleOutputs, {
3780
+ casRoot,
3781
+ versionHash: configHash
3782
+ });
3783
+ await writeBuildManifest(absOutDir, plan, artifacts);
3784
+ await writeAssetsManifest(absOutDir, artifacts);
3785
+ await import_fs12.default.promises.writeFile(
3786
+ import_path15.default.join(absOutDir, "build.stats.json"),
3787
+ JSON.stringify(stats, null, 2),
3788
+ "utf8"
3789
+ );
3790
+ logInfo(`Build plan generated \u2192 ${import_path15.default.join(absOutDir, "manifest.json")}`);
3791
+ logInfo(`Entries: ${plan.entries.length}, Chunks: ${plan.chunks.length}`);
3792
+ logInfo(`Modules transformed: ${moduleOutputs.size}`);
3793
+ } catch (err) {
3794
+ logError("ionify build failed", err);
3795
+ throw err;
3796
+ }
3797
+ }
3798
+
417
3799
  // src/cli/index.ts
418
3800
  var program = new import_commander.Command();
419
- program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.1.0");
3801
+ program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.0.1");
420
3802
  program.command("dev").description("Start Ionify development server").option("-p, --port <port>", "Port to run the server on", "5173").action(async (options) => {
421
3803
  try {
422
- await startDevServer(options);
3804
+ const port = parseInt(options.port, 10);
3805
+ await startDevServer({ port });
423
3806
  } catch (err) {
424
- logError(`Dev server failed: ${err.message}`);
3807
+ logError("Failed to start dev server", err);
425
3808
  process.exit(1);
426
3809
  }
427
3810
  });
428
- program.command("build").description("Build for production").option("-o, --outDir <dir>", "Output directory", "dist").option("-l, --level <level>", "Optimization level (0-4)", "3").action(async (options) => {
3811
+ program.command("build").description("Create production build using Ionify bundler").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
429
3812
  try {
430
- await runBuildCommand(options);
431
- } catch (err) {
432
- logError(`Build failed: ${err.message}`);
3813
+ await runBuildCommand({ outDir: options.outDir });
3814
+ } catch {
433
3815
  process.exit(1);
434
3816
  }
435
3817
  });
436
- program.command("analyze").description("Analyze bundle and performance").option("-f, --format <format>", "Output format (json|text)", "text").action(async (options) => {
3818
+ program.command("migrate").description("Migrate from Vite/Rollup config (not implemented yet)").action(() => logInfo("Migrate command coming soon..."));
3819
+ program.command("analyze").description("Inspect cached dependency graph stats").option("--json", "Output summary as JSON").option("-l, --limit <count>", "Limit list outputs", "10").action(async (options) => {
437
3820
  try {
438
- await runAnalyzeCommand(options);
3821
+ const limit = parseInt(options.limit ?? "10", 10);
3822
+ await runAnalyzeCommand({ json: !!options.json, limit: Number.isFinite(limit) ? limit : 10 });
439
3823
  } catch (err) {
440
- logError(`Analyzer failed: ${err.message}`);
3824
+ logError("Analyzer failed", err);
441
3825
  process.exit(1);
442
3826
  }
443
3827
  });
444
- program.parse();
3828
+ program.parse(process.argv);