@ionify/ionify 0.1.2 → 0.1.4

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.
@@ -30,12 +30,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
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
33
+ // node_modules/.pnpm/tsup@8.5.0_@swc+core@1.13.5_jiti@1.21.7_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js
34
34
  var getImportMetaUrl, importMetaUrl;
35
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"() {
36
+ "node_modules/.pnpm/tsup@8.5.0_@swc+core@1.13.5_jiti@1.21.7_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js"() {
37
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;
38
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
39
39
  importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
40
  }
41
41
  });
@@ -170,6 +170,9 @@ function tryNativeTransform(mode, code, options) {
170
170
  if (mode === "swc") throw err;
171
171
  }
172
172
  }
173
+ if (options.filename?.includes("Counter.jsx")) {
174
+ console.log("[TASK1 DEBUG] \u26A0\uFE0F No native transform available, returning null");
175
+ }
173
176
  return null;
174
177
  }
175
178
  function ensureNativeGraph(graphPath, version) {
@@ -305,7 +308,7 @@ var import_chalk = __toESM(require("chalk"), 1);
305
308
  function logInfo(message) {
306
309
  console.log(import_chalk.default.cyan(`[Ionify] ${message}`));
307
310
  }
308
- function logWarn(message) {
311
+ function logWarn2(message) {
309
312
  console.warn(import_chalk.default.yellow(`[Ionify] ${message}`));
310
313
  }
311
314
  function logError(message, err) {
@@ -317,8 +320,8 @@ function logError(message, err) {
317
320
  init_cjs_shims();
318
321
  var import_http = __toESM(require("http"), 1);
319
322
  var import_url3 = __toESM(require("url"), 1);
320
- var import_fs9 = __toESM(require("fs"), 1);
321
- var import_path12 = __toESM(require("path"), 1);
323
+ var import_fs11 = __toESM(require("fs"), 1);
324
+ var import_path15 = __toESM(require("path"), 1);
322
325
  var import_url4 = require("url");
323
326
  var import_module3 = require("module");
324
327
  init_cache();
@@ -738,9 +741,12 @@ function buildAliasEntries(aliases, baseDir) {
738
741
  const entries = [];
739
742
  for (const [pattern, value] of Object.entries(aliases)) {
740
743
  const replacements = Array.isArray(value) ? value : [value];
741
- const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map(
742
- (rep) => import_path4.default.isAbsolute(rep) ? rep : import_path4.default.resolve(baseDir, rep)
743
- );
744
+ const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map((rep) => {
745
+ if (rep.startsWith("/")) {
746
+ return import_path4.default.resolve(baseDir, rep.slice(1));
747
+ }
748
+ return import_path4.default.isAbsolute(rep) ? rep : import_path4.default.resolve(baseDir, rep);
749
+ });
744
750
  if (!targets.length) continue;
745
751
  entries.push(createAliasEntry(pattern, targets));
746
752
  }
@@ -771,20 +777,41 @@ function loadTsconfigAliases() {
771
777
  return cachedTsconfigAliases;
772
778
  }
773
779
  function resolveFromEntries(entries, specifier) {
780
+ const debug = process.env.IONIFY_RESOLVE_DEBUG === "1";
774
781
  for (const entry of entries) {
775
782
  const candidates = entry.resolveCandidates(specifier);
783
+ if (debug && candidates.length > 0) {
784
+ console.log(`[RESOLVE] Candidates for ${specifier}:`, candidates);
785
+ }
776
786
  for (const candidate of candidates) {
777
787
  const resolved = tryWithExt(candidate);
778
- if (resolved) return resolved;
788
+ if (resolved) {
789
+ if (debug) console.log(`[RESOLVE] Found: ${resolved}`);
790
+ return resolved;
791
+ }
779
792
  }
780
793
  }
781
794
  return null;
782
795
  }
783
796
  function resolveWithAliases(specifier) {
797
+ const debug = process.env.IONIFY_RESOLVE_DEBUG === "1";
798
+ if (debug) {
799
+ console.log(`[RESOLVE] Trying to resolve: ${specifier}`);
800
+ console.log(`[RESOLVE] Custom aliases count: ${customAliasEntries.length}`);
801
+ }
784
802
  const custom = resolveFromEntries(customAliasEntries, specifier);
785
- if (custom) return custom;
803
+ if (custom) {
804
+ if (debug) console.log(`[RESOLVE] \u2705 Resolved via custom alias: ${custom}`);
805
+ return custom;
806
+ }
786
807
  const tsconfigEntries = loadTsconfigAliases();
787
- return resolveFromEntries(tsconfigEntries, specifier);
808
+ if (debug) console.log(`[RESOLVE] Tsconfig aliases count: ${tsconfigEntries.length}`);
809
+ const result = resolveFromEntries(tsconfigEntries, specifier);
810
+ if (debug) {
811
+ if (result) console.log(`[RESOLVE] \u2705 Resolved via tsconfig: ${result}`);
812
+ else console.log(`[RESOLVE] \u274C Not resolved`);
813
+ }
814
+ return result;
788
815
  }
789
816
  function configureResolverAliases(aliases, baseDir) {
790
817
  customAliasEntries = aliases ? buildAliasEntries(aliases, baseDir) : [];
@@ -855,12 +882,14 @@ function resolveImports(specs, importerAbs) {
855
882
  init_cjs_shims();
856
883
  var import_path5 = __toESM(require("path"), 1);
857
884
  var import_fs5 = __toESM(require("fs"), 1);
885
+ init_native();
858
886
  var DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"];
859
887
  var DEFAULT_CONDITIONS = ["import", "default"];
860
888
  var DEFAULT_MAIN_FIELDS = ["module", "main"];
861
889
  var ModuleResolver = class {
862
890
  options;
863
891
  rootDir;
892
+ metadataByPath = /* @__PURE__ */ new Map();
864
893
  constructor(rootDir, options = {}) {
865
894
  this.rootDir = rootDir;
866
895
  this.options = {
@@ -886,6 +915,9 @@ var ModuleResolver = class {
886
915
  }
887
916
  return this.resolveBareModule(importSpecifier, importer);
888
917
  }
918
+ getMetadata(resolvedPath) {
919
+ return this.metadataByPath.get(resolvedPath);
920
+ }
889
921
  resolveAlias(specifier) {
890
922
  for (const [alias, target] of Object.entries(this.options.alias)) {
891
923
  if (specifier === alias || specifier.startsWith(`${alias}/`)) {
@@ -926,6 +958,33 @@ var ModuleResolver = class {
926
958
  return null;
927
959
  }
928
960
  resolveBareModule(specifier, importer) {
961
+ const nativeResolved = native?.resolveModule?.(specifier, importer);
962
+ if (nativeResolved?.kind) {
963
+ const fsPath = nativeResolved.fsPath ?? nativeResolved.fs_path ?? null;
964
+ const kind = normalizeResolveKind(nativeResolved.kind);
965
+ if (kind === "pkg_cjs") {
966
+ if (fsPath) {
967
+ this.metadataByPath.set(fsPath, {
968
+ format: "cjs",
969
+ needsInterop: true
970
+ });
971
+ }
972
+ if (process.env.IONIFY_DEBUG) {
973
+ const name = nativeResolved.pkg?.name ?? specifier;
974
+ console.log(`[resolver] CJS package detected: ${name} (conversion deferred)`);
975
+ }
976
+ }
977
+ if (kind === "pkg_esm" && fsPath) {
978
+ return fsPath;
979
+ }
980
+ if (kind === "pkg_cjs" && fsPath) {
981
+ return fsPath;
982
+ }
983
+ if (kind === "local" && fsPath) {
984
+ return fsPath;
985
+ }
986
+ return null;
987
+ }
929
988
  const parts = specifier.split("/");
930
989
  const packageName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
931
990
  const subpath = parts.slice(packageName.startsWith("@") ? 2 : 1).join("/");
@@ -1007,6 +1066,19 @@ var ModuleResolver = class {
1007
1066
  return null;
1008
1067
  }
1009
1068
  };
1069
+ function normalizeResolveKind(kind) {
1070
+ const mapping = {
1071
+ PkgEsm: "pkg_esm",
1072
+ PkgCjs: "pkg_cjs",
1073
+ Builtin: "builtin",
1074
+ Virtual: "virtual",
1075
+ Local: "local"
1076
+ };
1077
+ if (kind in mapping) {
1078
+ return mapping[kind];
1079
+ }
1080
+ return kind.toLowerCase();
1081
+ }
1010
1082
 
1011
1083
  // src/core/watcher.ts
1012
1084
  init_cjs_shims();
@@ -1144,16 +1216,16 @@ var TransformEngine = class {
1144
1216
  }
1145
1217
  async run(ctx) {
1146
1218
  const { getCacheKey: getCacheKey2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
1147
- const path16 = await import("path");
1148
- const fs13 = await import("fs");
1219
+ const path19 = await import("path");
1220
+ const fs15 = await import("fs");
1149
1221
  const { getCasArtifactPath: getCasArtifactPath2 } = await Promise.resolve().then(() => (init_cas(), cas_exports));
1150
1222
  const moduleHash = ctx.moduleHash || getCacheKey2(ctx.code);
1151
1223
  const loaderSig = this.loaders.map((l) => l.name || "loader").join("|");
1152
1224
  const loaderHash = getCacheKey2(loaderSig);
1153
1225
  const memKey = `${moduleHash}-${loaderHash}`;
1154
1226
  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;
1227
+ const casFile = casDir ? path19.join(casDir, "transformed.js") : null;
1228
+ const casMapFile = casDir ? path19.join(casDir, "transformed.js.map") : null;
1157
1229
  const debug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
1158
1230
  if (this.cacheEnabled) {
1159
1231
  const memHit = transformCache.get(memKey);
@@ -1163,10 +1235,10 @@ var TransformEngine = class {
1163
1235
  }
1164
1236
  return { code: memHit.transformed, map: memHit.map };
1165
1237
  }
1166
- if (casFile && fs13.existsSync(casFile)) {
1238
+ if (casFile && fs15.existsSync(casFile)) {
1167
1239
  try {
1168
- const code = fs13.readFileSync(casFile, "utf8");
1169
- const map = casMapFile && fs13.existsSync(casMapFile) ? fs13.readFileSync(casMapFile, "utf8") : void 0;
1240
+ const code = fs15.readFileSync(casFile, "utf8");
1241
+ const map = casMapFile && fs15.existsSync(casMapFile) ? fs15.readFileSync(casMapFile, "utf8") : void 0;
1170
1242
  const parsed = { code, map };
1171
1243
  transformCache.set(memKey, {
1172
1244
  hash: moduleHash,
@@ -1203,10 +1275,10 @@ var TransformEngine = class {
1203
1275
  });
1204
1276
  if (casFile) {
1205
1277
  try {
1206
- fs13.mkdirSync(path16.dirname(casFile), { recursive: true });
1207
- fs13.writeFileSync(casFile, result.code, "utf8");
1278
+ fs15.mkdirSync(path19.dirname(casFile), { recursive: true });
1279
+ fs15.writeFileSync(casFile, result.code, "utf8");
1208
1280
  if (result.map && casMapFile) {
1209
- fs13.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
1281
+ fs15.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
1210
1282
  }
1211
1283
  } catch {
1212
1284
  }
@@ -1345,17 +1417,6 @@ async function compileCss({
1345
1417
  rootDir,
1346
1418
  modules = false
1347
1419
  }) {
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
1420
  const { plugins, options } = await getPostcssConfig(rootDir);
1360
1421
  const pipeline = [...plugins];
1361
1422
  let tokens;
@@ -1380,16 +1441,34 @@ async function compileCss({
1380
1441
  from: filePath,
1381
1442
  map: false
1382
1443
  });
1444
+ const deps = [];
1445
+ const seen = /* @__PURE__ */ new Set();
1446
+ const addDep = (depPath) => {
1447
+ const normalized = depPath.replace(/\\+/g, "/");
1448
+ if (seen.has(normalized)) return;
1449
+ seen.add(normalized);
1450
+ deps.push({ filePath: depPath, kind: "dependency" });
1451
+ };
1452
+ for (const message of result.messages || []) {
1453
+ const anyMsg = message;
1454
+ if (anyMsg?.type === "dependency" && typeof anyMsg.file === "string") {
1455
+ addDep(anyMsg.file);
1456
+ }
1457
+ }
1458
+ const importRe = /@import\s+(?:url\(\s*)?(?:'([^']+)'|"([^"]+)"|([^'"\s)]+))\s*\)?[^;]*;/gi;
1459
+ let match;
1460
+ while (match = importRe.exec(code)) {
1461
+ const spec = (match[1] || match[2] || match[3] || "").trim();
1462
+ if (!spec) continue;
1463
+ if (/^(data:|https?:|\/\/)/i.test(spec)) continue;
1464
+ const resolved = spec.startsWith("/") ? import_path8.default.resolve(rootDir, "." + spec) : import_path8.default.resolve(import_path8.default.dirname(filePath), spec);
1465
+ addDep(resolved);
1466
+ }
1383
1467
  const compiled = {
1384
1468
  css: result.css,
1385
- tokens
1469
+ tokens,
1470
+ deps
1386
1471
  };
1387
- transformCache.set(cacheKey, {
1388
- hash: contentHash,
1389
- loaderHash,
1390
- transformed: JSON.stringify(compiled),
1391
- timestamp: Date.now()
1392
- });
1393
1472
  return compiled;
1394
1473
  }
1395
1474
  function renderCssModule({
@@ -1401,6 +1480,7 @@ function renderCssModule({
1401
1480
  const styleId = `ionify-css-${getCacheKey(filePath).slice(0, 8)}`;
1402
1481
  const tokensJson = tokens ? JSON.stringify(tokens) : "null";
1403
1482
  return `
1483
+ // ionify:css
1404
1484
  const cssText = ${cssJson};
1405
1485
  const styleId = ${JSON.stringify(styleId)};
1406
1486
  let style = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
@@ -1423,6 +1503,22 @@ if (import.meta.hot) {
1423
1503
  }
1424
1504
  `.trim();
1425
1505
  }
1506
+ function renderCssRawStringModule(cssText) {
1507
+ return `
1508
+ // ionify:css
1509
+ const css = ${JSON.stringify(cssText)};
1510
+ export { css };
1511
+ export default css;
1512
+ `.trim();
1513
+ }
1514
+ function renderCssUrlModule(url2) {
1515
+ return `
1516
+ // ionify:css
1517
+ const url = ${JSON.stringify(url2)};
1518
+ export { url };
1519
+ export default url;
1520
+ `.trim();
1521
+ }
1426
1522
 
1427
1523
  // src/core/loaders/asset.ts
1428
1524
  init_cjs_shims();
@@ -1524,17 +1620,354 @@ init_cjs_shims();
1524
1620
  var import_core = require("@swc/core");
1525
1621
  var import_es_module_lexer = require("es-module-lexer");
1526
1622
  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;
1623
+
1624
+ // src/core/deps/registry.ts
1625
+ init_cjs_shims();
1626
+ var import_crypto3 = __toESM(require("crypto"), 1);
1627
+ var import_fs7 = __toESM(require("fs"), 1);
1628
+ var import_path10 = __toESM(require("path"), 1);
1629
+ var registry = /* @__PURE__ */ new Map();
1630
+ function computeStableDepFileName(options) {
1631
+ const pkgName = sanitizePackageName(options.packageName);
1632
+ const pkgVersion = options.packageVersion || "0.0.0";
1633
+ const subpath = normalizeSubpath(options.subpath);
1634
+ let canonicalPath = options.entryPath;
1635
+ try {
1636
+ canonicalPath = import_fs7.default.realpathSync(options.entryPath);
1637
+ } catch {
1638
+ }
1639
+ const hash = import_crypto3.default.createHash("sha256").update(canonicalPath).digest("hex").slice(0, 6);
1640
+ const subpathSuffix = subpath ? `__${subpath}` : "";
1641
+ return `${pkgName}@${pkgVersion}${subpathSuffix}_${hash}.js`;
1642
+ }
1643
+ function registerDepEntry(entry) {
1644
+ const fileName = computeStableDepFileName({
1645
+ entryPath: entry.entryPath,
1646
+ packageName: entry.packageName,
1647
+ packageVersion: entry.packageVersion,
1648
+ subpath: entry.subpath
1649
+ });
1650
+ const existing = registry.get(fileName);
1651
+ if (existing) {
1652
+ return existing;
1653
+ }
1654
+ const record = { ...entry, fileName };
1655
+ registry.set(fileName, record);
1656
+ return record;
1657
+ }
1658
+ function getDepEntry(fileName) {
1659
+ return registry.get(fileName);
1660
+ }
1661
+ function computeSubpathFromEntryPath(entryPath) {
1662
+ const packageRoot = findPackageRoot(entryPath);
1663
+ if (!packageRoot) {
1664
+ if (process.env.DEBUG_DEPS) {
1665
+ console.log(`[computeSubpathFromEntryPath] No package root for: ${entryPath}`);
1666
+ }
1667
+ return "";
1668
+ }
1669
+ let rel = import_path10.default.relative(packageRoot, entryPath).replace(/\\/g, "/");
1670
+ const extIndex = rel.lastIndexOf(".");
1671
+ if (extIndex !== -1) {
1672
+ rel = rel.substring(0, extIndex);
1673
+ }
1674
+ if (rel.endsWith("/index")) {
1675
+ rel = rel.substring(0, rel.length - "/index".length);
1676
+ }
1677
+ const pkgName = import_path10.default.basename(packageRoot);
1678
+ if (process.env.DEBUG_DEPS) {
1679
+ console.log(`[subpath] entry: ${import_path10.default.basename(entryPath)}, root: ${pkgName}, rel: "${rel}", isMain: ${rel === pkgName}`);
1680
+ }
1681
+ if (rel === pkgName || rel === "index" || rel === "" || rel === ".") {
1682
+ return "";
1683
+ }
1684
+ return rel || "";
1685
+ }
1686
+ function findPackageRoot(entryPath) {
1687
+ let currentDir = import_path10.default.dirname(entryPath);
1688
+ let previousDir = entryPath;
1689
+ while (currentDir && currentDir !== previousDir) {
1690
+ const parent = import_path10.default.dirname(currentDir);
1691
+ const grandparent = import_path10.default.dirname(parent);
1692
+ if (import_path10.default.basename(parent) === "node_modules") {
1693
+ const pkgJsonPath = import_path10.default.join(currentDir, "package.json");
1694
+ if (import_fs7.default.existsSync(pkgJsonPath)) {
1695
+ return currentDir;
1696
+ }
1697
+ }
1698
+ if (import_path10.default.basename(grandparent) === "node_modules" && import_path10.default.basename(parent).startsWith("@")) {
1699
+ const pkgJsonPath = import_path10.default.join(currentDir, "package.json");
1700
+ if (import_fs7.default.existsSync(pkgJsonPath)) {
1701
+ return currentDir;
1702
+ }
1703
+ }
1704
+ previousDir = currentDir;
1705
+ currentDir = parent;
1706
+ }
1707
+ return null;
1708
+ }
1709
+ function sanitizePackageName(name) {
1710
+ return name.replace(/^@/, "").replace(/\//g, "__");
1711
+ }
1712
+ function normalizeSubpath(subpath) {
1713
+ if (!subpath) return "";
1714
+ const cleaned = subpath.replace(/^\.\//, "").replace(/^\//, "");
1715
+ if (!cleaned || cleaned === "." || cleaned === "index") return "";
1716
+ return cleaned.replace(/\//g, "__");
1717
+ }
1718
+
1719
+ // src/core/refresh/reactRefreshInstrumentation.ts
1720
+ init_cjs_shims();
1721
+ function isPascalCaseIdentifier(name) {
1722
+ return /^[A-Z][A-Za-z0-9_$]*$/.test(name);
1723
+ }
1724
+ function hasRefreshRegistrationsAlready(code) {
1725
+ return /\$RefreshReg\$/.test(code);
1726
+ }
1727
+ function dedupeByExportName(items) {
1728
+ const seen = /* @__PURE__ */ new Set();
1729
+ const out = [];
1730
+ for (const it of items) {
1731
+ const key = `${it.exportName}::${it.localName}`;
1732
+ if (seen.has(key)) continue;
1733
+ seen.add(key);
1734
+ out.push(it);
1735
+ }
1736
+ return out;
1737
+ }
1738
+ function detectRefreshBoundaryExports(code) {
1739
+ const out = [];
1740
+ function isValidIdentifier(name) {
1741
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
1742
+ }
1743
+ {
1744
+ const re = /export\s+(?:async\s+)?function\s+([A-Z][A-Za-z0-9_$]*)\s*\(/g;
1745
+ let m;
1746
+ while (m = re.exec(code)) {
1747
+ const name = m[1];
1748
+ out.push({ exportName: name, localName: name });
1749
+ }
1750
+ }
1751
+ {
1752
+ const re = /export\s+(?:const|let)\s+([A-Z][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][A-Za-z0-9_$]*\s*=>)/g;
1753
+ let m;
1754
+ while (m = re.exec(code)) {
1755
+ const name = m[1];
1756
+ out.push({ exportName: name, localName: name });
1757
+ }
1758
+ }
1759
+ {
1760
+ const re = /export\s+default\s+(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
1761
+ const m = re.exec(code);
1762
+ if (m?.[1]) {
1763
+ const local = m[1];
1764
+ if (isPascalCaseIdentifier(local)) {
1765
+ out.push({ exportName: "default", localName: local });
1766
+ }
1767
+ }
1768
+ }
1769
+ {
1770
+ const re = /export\s+default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*;?/g;
1771
+ let m;
1772
+ while (m = re.exec(code)) {
1773
+ const local = m[1];
1774
+ if (local === "function" || local === "class") continue;
1775
+ if (isPascalCaseIdentifier(local)) {
1776
+ out.push({ exportName: "default", localName: local });
1777
+ }
1778
+ }
1779
+ }
1780
+ {
1781
+ const re = /export\s+(default\s+)?class\s+([A-Z][A-Za-z0-9_$]*)\b/g;
1782
+ let m;
1783
+ while (m = re.exec(code)) {
1784
+ const isDefault = Boolean(m[1]);
1785
+ const local = m[2];
1786
+ if (!isPascalCaseIdentifier(local)) continue;
1787
+ out.push({ exportName: isDefault ? "default" : local, localName: local });
1788
+ }
1789
+ }
1790
+ {
1791
+ const re = /export\s+(?:const|let)\s+([A-Z][A-Za-z0-9_$]*)\s*=\s*class\b/g;
1792
+ let m;
1793
+ while (m = re.exec(code)) {
1794
+ const name = m[1];
1795
+ if (!isPascalCaseIdentifier(name)) continue;
1796
+ out.push({ exportName: name, localName: name });
1797
+ }
1798
+ }
1799
+ {
1800
+ const re = /export\s*{\s*([^}]+)\s*}\s*(?:from\s*(['"][^'"]+['"]))?\s*;?/g;
1801
+ let m;
1802
+ while (m = re.exec(code)) {
1803
+ const from = m[2];
1804
+ if (from) continue;
1805
+ const specList = m[1] ?? "";
1806
+ const parts = specList.split(",").map((p) => p.trim()).filter(Boolean);
1807
+ for (const part of parts) {
1808
+ if (part.startsWith("type ")) continue;
1809
+ const asMatch = part.split(/\s+as\s+/);
1810
+ const local = (asMatch[0] ?? "").trim();
1811
+ const exported = (asMatch[1] ?? local).trim();
1812
+ if (!local || !exported) continue;
1813
+ if (local === "default") continue;
1814
+ if (!isValidIdentifier(local) || !isValidIdentifier(exported)) continue;
1815
+ if (!isPascalCaseIdentifier(local)) continue;
1816
+ if (exported !== "default" && !isPascalCaseIdentifier(exported)) continue;
1817
+ out.push({ exportName: exported, localName: local });
1818
+ }
1819
+ }
1820
+ }
1821
+ return dedupeByExportName(out);
1822
+ }
1823
+ async function buildReactRefreshRegistrations(code, _filePath) {
1824
+ if (hasRefreshRegistrationsAlready(code)) return "";
1825
+ const candidates = detectRefreshBoundaryExports(code);
1826
+ if (!candidates.length) return "";
1827
+ const lines = candidates.map(({ exportName, localName }) => {
1828
+ return `window.$RefreshReg$?.(${localName}, normalizeRefreshModuleId(import.meta.url) + ":" + ${JSON.stringify(exportName)});`;
1829
+ });
1830
+ return "\n" + lines.join("\n") + "\n";
1831
+ }
1832
+ function needsReactRefresh(ext, isDev) {
1833
+ if (!isDev) return false;
1834
+ return ext === ".jsx" || ext === ".tsx";
1835
+ }
1836
+ async function instrumentReactRefresh(options) {
1837
+ const { code, filePath, ext, isDev, isEntry = false } = options;
1838
+ if (!needsReactRefresh(ext, isDev)) {
1839
+ return { shouldInstrument: false, prologue: "", registrations: "", epilogue: "" };
1840
+ }
1841
+ const registrations = isEntry ? "" : await buildReactRefreshRegistrations(code, filePath);
1842
+ const prologue = `import { setupReactRefresh, normalizeRefreshModuleId } from "/__ionify_react_refresh.js";
1843
+ const __ionifyRefresh__ = setupReactRefresh(import.meta.hot ?? { accept() {}, dispose() {} }, normalizeRefreshModuleId(import.meta.url));
1844
+ `;
1845
+ const epilogue = `
1846
+ __ionifyRefresh__?.finalize?.();
1847
+
1848
+ if (import.meta.hot) {
1849
+ import.meta.hot.accept((newModule) => {
1850
+ __ionifyRefresh__?.refresh?.(newModule);
1851
+ });
1852
+ import.meta.hot.dispose(() => {
1853
+ __ionifyRefresh__?.dispose?.();
1854
+ });
1855
+ }
1856
+ `;
1857
+ return { shouldInstrument: true, prologue, registrations, epilogue };
1858
+ }
1859
+
1860
+ // src/core/refresh/entryDetection.ts
1861
+ init_cjs_shims();
1862
+ var import_path11 = __toESM(require("path"), 1);
1863
+ var ENTRY_PATTERNS = [
1864
+ /\/src\/main\.(tsx?|jsx?)$/,
1865
+ /\/src\/index\.(tsx?|jsx?)$/
1866
+ ];
1867
+ function normalizePath(input) {
1868
+ const normalized = import_path11.default.normalize(input).replace(/\\/g, "/");
1869
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1870
+ }
1871
+ function isEntryModule(filePath, config) {
1872
+ const normalized = normalizePath(import_path11.default.resolve(filePath));
1873
+ if (config?.entry) {
1874
+ const root = config.root ?? process.cwd();
1875
+ const entries = Array.isArray(config.entry) ? config.entry : [config.entry];
1876
+ for (const entry of entries) {
1877
+ const resolvedEntry = import_path11.default.resolve(root, entry);
1878
+ const normalizedEntry = normalizePath(resolvedEntry);
1879
+ if (normalized === normalizedEntry) return true;
1880
+ }
1881
+ }
1882
+ return ENTRY_PATTERNS.some((pattern) => pattern.test(normalized));
1883
+ }
1884
+
1885
+ // src/core/refresh/refreshEligibility.ts
1886
+ init_cjs_shims();
1887
+ function containsJSX(code) {
1888
+ const sample = code.slice(0, 8 * 1024);
1889
+ if (sample.includes("React.createElement")) return true;
1890
+ if (/\bjsx(?:s)?\s*\(/.test(sample)) return true;
1891
+ if (sample.includes("<>") || sample.includes("</>")) return true;
1892
+ if (/<[A-Za-z][A-Za-z0-9.$_-]*\b[^>]*\/>/.test(sample)) return true;
1893
+ if (/<[A-Za-z][A-Za-z0-9.$_-]*\b[^>]*>/.test(sample) && /<\/[A-Za-z]/.test(sample)) {
1894
+ return true;
1895
+ }
1896
+ return false;
1897
+ }
1898
+ function shouldUseReactRefresh(options) {
1899
+ const { ext, code, isDev, config } = options;
1900
+ if (!isDev) return false;
1901
+ if (config?.fastRefresh === false) return false;
1902
+ if (ext === ".jsx" || ext === ".tsx") return containsJSX(code);
1903
+ if (ext === ".js" || ext === ".ts" || ext === ".mjs" || ext === ".mts") return containsJSX(code);
1531
1904
  return false;
1532
1905
  }
1906
+
1907
+ // src/core/loaders/js.ts
1908
+ var import_fs8 = __toESM(require("fs"), 1);
1909
+ var import_path12 = __toESM(require("path"), 1);
1910
+ var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
1533
1911
  function shouldTransform(ext, filePath) {
1534
1912
  if (!JS_EXTENSIONS.has(ext)) return false;
1535
1913
  if (filePath.endsWith(".d.ts")) return false;
1536
1914
  return true;
1537
1915
  }
1916
+ function computeSubpathForDep(fsPath, pkg) {
1917
+ const computed = computeSubpathFromEntryPath(fsPath);
1918
+ if (!computed && !import_fs8.default.existsSync(fsPath) && pkg && typeof pkg.subpath === "string") {
1919
+ const raw = pkg.subpath;
1920
+ const cleaned = raw.replace(/^\.\//, "").replace(/^\/+/, "");
1921
+ if (cleaned && cleaned !== "." && cleaned !== "index") {
1922
+ return cleaned;
1923
+ }
1924
+ }
1925
+ if (process.env.DEBUG_DEPS) {
1926
+ console.log(`[computeSubpathForDep] fsPath: ${fsPath}`);
1927
+ console.log(`[computeSubpathForDep] pkg.name: ${pkg?.name}, pkg.subpath: ${pkg?.subpath}`);
1928
+ console.log(`[computeSubpathForDep] computed: "${computed}"`);
1929
+ }
1930
+ return computed || null;
1931
+ }
1932
+ function looksLikeCjsWrapperSource(source) {
1933
+ const sample = source.slice(0, 16 * 1024);
1934
+ return sample.includes("module.exports") || sample.includes("exports.") || sample.includes("Object.defineProperty(exports") || sample.includes("Object.defineProperty(module.exports") || sample.includes("require(") || sample.includes("require (");
1935
+ }
1936
+ function looksLikeEsmSource(source) {
1937
+ const sample = source.slice(0, 16 * 1024);
1938
+ return sample.includes("import ") || sample.includes("export ") || sample.includes("import{") || sample.includes("export{") || sample.includes("import(");
1939
+ }
1940
+ function findNearestPackageJson(filePath) {
1941
+ let current = import_path12.default.dirname(filePath);
1942
+ for (let i = 0; i < 25; i++) {
1943
+ const candidate = import_path12.default.join(current, "package.json");
1944
+ if (import_fs8.default.existsSync(candidate)) return candidate;
1945
+ const parent = import_path12.default.dirname(current);
1946
+ if (parent === current) break;
1947
+ current = parent;
1948
+ }
1949
+ return null;
1950
+ }
1951
+ function makeDepsProxyForFile(filePath, code) {
1952
+ if (!looksLikeCjsWrapperSource(code)) return null;
1953
+ const pkgJsonPath = findNearestPackageJson(filePath);
1954
+ if (!pkgJsonPath) return null;
1955
+ try {
1956
+ const pkg = JSON.parse(import_fs8.default.readFileSync(pkgJsonPath, "utf8"));
1957
+ const fileName = registerDepEntry({
1958
+ entryPath: filePath,
1959
+ packageName: pkg?.name ?? "dep",
1960
+ packageVersion: pkg?.version ?? "0.0.0",
1961
+ subpath: null
1962
+ }).fileName;
1963
+ return `import * as __ionify_dep__ from "/@deps/${fileName}";
1964
+ export default __ionify_dep__;
1965
+ export * from "/@deps/${fileName}";
1966
+ `;
1967
+ } catch {
1968
+ return null;
1969
+ }
1970
+ }
1538
1971
  async function swcTranspile(code, filePath, ext, reactRefresh) {
1539
1972
  const isTypeScript = ext === ".ts" || ext === ".tsx";
1540
1973
  const isTsx = ext === ".tsx";
@@ -1580,18 +2013,30 @@ var jsLoader = {
1580
2013
  name: "js",
1581
2014
  order: 0,
1582
2015
  test: ({ ext, path: filePath }) => shouldTransform(ext, filePath),
1583
- transform: async ({ path: filePath, code, ext }) => {
2016
+ transform: async ({ path: filePath, code, ext, config }) => {
1584
2017
  const isNodeModules = filePath.includes("node_modules");
2018
+ const rewriteDebug = process.env.IONIFY_IMPORT_REWRITE_DEBUG === "1";
1585
2019
  let output = code;
1586
2020
  if (isNodeModules) {
1587
- const bundled = tryBundleNodeModule(filePath, code);
1588
- if (bundled) {
1589
- output = bundled;
2021
+ const depsProxy = makeDepsProxyForFile(filePath, code);
2022
+ if (depsProxy) {
2023
+ output = depsProxy;
1590
2024
  } else {
1591
- output = code;
2025
+ const shouldAttemptBundle = ext === ".cjs" || looksLikeCjsWrapperSource(code) || !looksLikeEsmSource(code) && ext !== ".mjs";
2026
+ if (shouldAttemptBundle) {
2027
+ const bundled = tryBundleNodeModule(filePath, code);
2028
+ if (bundled) {
2029
+ output = bundled;
2030
+ } else {
2031
+ output = code;
2032
+ }
2033
+ } else {
2034
+ output = code;
2035
+ }
1592
2036
  }
1593
2037
  } else {
1594
- const reactRefresh = needsReactRefresh(ext);
2038
+ const isDev = process.env.NODE_ENV !== "production";
2039
+ const reactRefresh = shouldUseReactRefresh({ ext, code, isDev, config });
1595
2040
  const mode = currentMode();
1596
2041
  const nativeResult = tryNativeTransform(mode, code, {
1597
2042
  filename: filePath,
@@ -1605,22 +2050,29 @@ var jsLoader = {
1605
2050
  output = await swcTranspile(code, filePath, ext, reactRefresh);
1606
2051
  }
1607
2052
  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
- }
2053
+ const isEntry = isEntryModule(filePath, config ?? void 0);
2054
+ if (process.env.IONIFY_REFRESH_DEBUG === "1") {
2055
+ console.log(`[Refresh] ${filePath} \u2192 isEntry=${isEntry}, ext=${ext}`);
2056
+ }
2057
+ const result = await instrumentReactRefresh({
2058
+ code: output,
2059
+ filePath,
2060
+ ext,
2061
+ isDev,
2062
+ isEntry
2063
+ });
2064
+ if (process.env.IONIFY_REFRESH_DEBUG === "1") {
2065
+ console.log(
2066
+ `[Refresh] instrument=${result.shouldInstrument} ${filePath} \u2192 isEntry=${isEntry}`
2067
+ );
2068
+ }
2069
+ if (result.shouldInstrument) {
2070
+ output = result.prologue + output + result.registrations + result.epilogue;
2071
+ } else {
2072
+ output += `
2073
+ if (import.meta.hot) import.meta.hot.accept();
1622
2074
  `;
1623
- output = prologue + output + epilogue;
2075
+ }
1624
2076
  } else {
1625
2077
  output += `
1626
2078
  if (import.meta.hot) {
@@ -1631,8 +2083,11 @@ if (import.meta.hot) {
1631
2083
  }
1632
2084
  await import_es_module_lexer.init;
1633
2085
  const [imports] = (0, import_es_module_lexer.parse)(output);
2086
+ if (rewriteDebug && ext === ".mjs" && isNodeModules) {
2087
+ console.warn(`[Ionify][rewrite] scanning ${imports.length} import(s) in ${filePath}`);
2088
+ }
1634
2089
  if (imports.length) {
1635
- const rootDir = process.cwd();
2090
+ const rootDir = config?.root ? import_path12.default.resolve(config.root) : process.cwd();
1636
2091
  let rewritten = "";
1637
2092
  let lastIndex = 0;
1638
2093
  let mutated = false;
@@ -1651,8 +2106,96 @@ if (import.meta.hot) {
1651
2106
  pathPart = spec.slice(0, splitIndex);
1652
2107
  suffix = spec.slice(splitIndex);
1653
2108
  }
2109
+ const isBare = !pathPart.startsWith(".") && !pathPart.startsWith("/") && !pathPart.startsWith("http://") && !pathPart.startsWith("https://");
2110
+ if (isBare && native?.resolveModule) {
2111
+ const resolvedNative = native.resolveModule(pathPart, filePath);
2112
+ const kind = resolvedNative?.kind;
2113
+ const fsPath = resolvedNative?.fsPath ?? resolvedNative?.fs_path ?? null;
2114
+ if (kind === "PkgCjs" && fsPath) {
2115
+ const pkg = resolvedNative?.pkg;
2116
+ const fileName = registerDepEntry({
2117
+ entryPath: fsPath,
2118
+ packageName: pkg?.name ?? pathPart,
2119
+ packageVersion: pkg?.version ?? "0.0.0",
2120
+ subpath: computeSubpathForDep(fsPath, pkg)
2121
+ }).fileName;
2122
+ const replacement2 = `/@deps/${fileName}`;
2123
+ if (!mutated) {
2124
+ mutated = true;
2125
+ }
2126
+ if (record.t === 2) {
2127
+ rewritten += output.slice(lastIndex, record.s + 1);
2128
+ rewritten += replacement2;
2129
+ rewritten += output[record.e - 1];
2130
+ lastIndex = record.e;
2131
+ } else {
2132
+ rewritten += output.slice(lastIndex, record.s);
2133
+ rewritten += replacement2;
2134
+ lastIndex = record.e;
2135
+ }
2136
+ continue;
2137
+ }
2138
+ if (kind === "PkgEsm" && fsPath) {
2139
+ try {
2140
+ const resolvedCode = import_fs8.default.readFileSync(fsPath, "utf8");
2141
+ if (looksLikeCjsWrapperSource(resolvedCode)) {
2142
+ const pkg2 = resolvedNative?.pkg;
2143
+ const fileName2 = registerDepEntry({
2144
+ entryPath: fsPath,
2145
+ packageName: pkg2?.name ?? pathPart,
2146
+ packageVersion: pkg2?.version ?? "0.0.0",
2147
+ subpath: computeSubpathForDep(fsPath, pkg2)
2148
+ }).fileName;
2149
+ const replacement3 = `/@deps/${fileName2}`;
2150
+ if (!mutated) mutated = true;
2151
+ if (record.t === 2) {
2152
+ rewritten += output.slice(lastIndex, record.s + 1);
2153
+ rewritten += replacement3;
2154
+ rewritten += output[record.e - 1];
2155
+ lastIndex = record.e;
2156
+ } else {
2157
+ rewritten += output.slice(lastIndex, record.s);
2158
+ rewritten += replacement3;
2159
+ lastIndex = record.e;
2160
+ }
2161
+ continue;
2162
+ }
2163
+ } catch {
2164
+ }
2165
+ const pkg = resolvedNative?.pkg;
2166
+ const fileName = registerDepEntry({
2167
+ entryPath: fsPath,
2168
+ packageName: pkg?.name ?? pathPart,
2169
+ packageVersion: pkg?.version ?? "0.0.0",
2170
+ subpath: computeSubpathForDep(fsPath, pkg)
2171
+ }).fileName;
2172
+ const replacement2 = `/@deps/${fileName}`;
2173
+ if (!mutated) mutated = true;
2174
+ if (record.t === 2) {
2175
+ rewritten += output.slice(lastIndex, record.s + 1);
2176
+ rewritten += replacement2;
2177
+ rewritten += output[record.e - 1];
2178
+ lastIndex = record.e;
2179
+ } else {
2180
+ rewritten += output.slice(lastIndex, record.s);
2181
+ rewritten += replacement2;
2182
+ lastIndex = record.e;
2183
+ }
2184
+ continue;
2185
+ }
2186
+ if (kind === "Builtin" || kind === "Virtual") {
2187
+ continue;
2188
+ }
2189
+ }
1654
2190
  const resolved = resolveImport(pathPart, filePath);
1655
- if (!resolved) continue;
2191
+ if (!resolved) {
2192
+ if (rewriteDebug) {
2193
+ console.warn(
2194
+ `[Ionify][rewrite] FAILED to resolve '${pathPart}' from '${filePath}'`
2195
+ );
2196
+ }
2197
+ continue;
2198
+ }
1656
2199
  const resolvedExt = resolved.slice(resolved.lastIndexOf("."));
1657
2200
  let augmentedSuffix = suffix;
1658
2201
  if (resolvedExt === ".css" && !suffix) {
@@ -1696,6 +2239,11 @@ if (import.meta.hot) {
1696
2239
  if (mutated) {
1697
2240
  rewritten += output.slice(lastIndex);
1698
2241
  output = rewritten;
2242
+ } else if (rewriteDebug && isNodeModules) {
2243
+ const sample = imports.slice(0, 8).map((r) => r.n).filter(Boolean).join(", ");
2244
+ console.warn(
2245
+ `[Ionify][rewrite] no rewrites applied for ${filePath}; first imports: ${sample}`
2246
+ );
1699
2247
  }
1700
2248
  }
1701
2249
  return { code: output };
@@ -1703,12 +2251,12 @@ if (import.meta.hot) {
1703
2251
  };
1704
2252
 
1705
2253
  // src/core/loaders/registry.ts
1706
- var registry = [];
2254
+ var registry2 = /* @__PURE__ */ new Set();
1707
2255
  function registerLoader(registration) {
1708
- registry.push(registration);
2256
+ registry2.add(registration);
1709
2257
  }
1710
2258
  async function applyRegisteredLoaders(engine, config) {
1711
- for (const registration of registry) {
2259
+ for (const registration of registry2) {
1712
2260
  await registration(engine, config ?? null);
1713
2261
  }
1714
2262
  if (config?.plugins) {
@@ -1720,9 +2268,8 @@ async function applyRegisteredLoaders(engine, config) {
1720
2268
  }
1721
2269
  if (plugin.setup) {
1722
2270
  const context = {
1723
- registerLoader: (loader) => {
1724
- engine.useLoader(loader);
1725
- }
2271
+ config: config ?? null,
2272
+ registerLoader: (loader) => engine.useLoader(loader)
1726
2273
  };
1727
2274
  await plugin.setup(context);
1728
2275
  }
@@ -1740,8 +2287,8 @@ registerLoader((engine) => {
1740
2287
 
1741
2288
  // src/cli/utils/config.ts
1742
2289
  init_cjs_shims();
1743
- var import_fs7 = __toESM(require("fs"), 1);
1744
- var import_path10 = __toESM(require("path"), 1);
2290
+ var import_fs9 = __toESM(require("fs"), 1);
2291
+ var import_path13 = __toESM(require("path"), 1);
1745
2292
  var import_url2 = require("url");
1746
2293
  var import_esbuild = require("esbuild");
1747
2294
  var CONFIG_BASENAMES = [
@@ -1754,7 +2301,7 @@ var CONFIG_BASENAMES = [
1754
2301
  var cachedConfig2 = null;
1755
2302
  var configLoaded = false;
1756
2303
  async function bundleConfig(entry) {
1757
- const absDir = import_path10.default.dirname(entry);
2304
+ const absDir = import_path13.default.dirname(entry);
1758
2305
  const inlineIonifyPlugin = {
1759
2306
  name: "inline-ionify",
1760
2307
  setup(build2) {
@@ -1802,8 +2349,8 @@ const __filename = ${filenameLiteral};
1802
2349
  }
1803
2350
  function findConfigFile(cwd) {
1804
2351
  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()) {
2352
+ const candidate = import_path13.default.resolve(cwd, name);
2353
+ if (import_fs9.default.existsSync(candidate) && import_fs9.default.statSync(candidate).isFile()) {
1807
2354
  return candidate;
1808
2355
  }
1809
2356
  }
@@ -1814,7 +2361,7 @@ async function loadIonifyConfig(cwd = process.cwd()) {
1814
2361
  configLoaded = true;
1815
2362
  const configPath = findConfigFile(cwd);
1816
2363
  if (!configPath) {
1817
- cachedConfig2 = null;
2364
+ cachedConfig2 = { root: cwd };
1818
2365
  configureResolverAliases(void 0, cwd);
1819
2366
  return cachedConfig2;
1820
2367
  }
@@ -1830,15 +2377,33 @@ async function loadIonifyConfig(cwd = process.cwd()) {
1830
2377
  resolved = await resolved;
1831
2378
  }
1832
2379
  if (resolved && typeof resolved === "object") {
2380
+ if (resolved.root) {
2381
+ const rootPath = import_path13.default.isAbsolute(resolved.root) ? resolved.root : import_path13.default.resolve(import_path13.default.dirname(configPath), resolved.root);
2382
+ if (!import_fs9.default.existsSync(rootPath)) {
2383
+ logError(`Config error: root directory does not exist: ${rootPath}`);
2384
+ throw new Error(`Invalid root: ${rootPath}`);
2385
+ }
2386
+ if (!import_fs9.default.statSync(rootPath).isDirectory()) {
2387
+ logError(`Config error: root must be a directory: ${rootPath}`);
2388
+ throw new Error(`Invalid root: ${rootPath}`);
2389
+ }
2390
+ resolved.root = rootPath;
2391
+ logInfo(`Using project root: ${import_path13.default.relative(cwd, rootPath)}`);
2392
+ } else {
2393
+ resolved.root = import_path13.default.dirname(configPath);
2394
+ }
2395
+ if (resolved.optimizeDeps?.esbuildOptions) {
2396
+ logWarn("optimizeDeps.esbuildOptions is not supported in Ionify (uses native Rust optimizer). This option will be ignored.");
2397
+ }
1833
2398
  cachedConfig2 = resolved;
1834
- const baseDir = import_path10.default.dirname(configPath);
2399
+ const baseDir = import_path13.default.dirname(configPath);
1835
2400
  const aliases = resolved?.resolve?.alias;
1836
2401
  if (aliases && typeof aliases === "object") {
1837
2402
  configureResolverAliases(aliases, baseDir);
1838
2403
  } else {
1839
2404
  configureResolverAliases(void 0, baseDir);
1840
2405
  }
1841
- logInfo(`Loaded ionify config from ${import_path10.default.relative(cwd, configPath)}`);
2406
+ logInfo(`Loaded ionify config from ${import_path13.default.relative(cwd, configPath)}`);
1842
2407
  } else {
1843
2408
  throw new Error("Config did not export an object");
1844
2409
  }
@@ -1869,14 +2434,11 @@ function resolveMinifier(config, opts = {}) {
1869
2434
  if (fromConfig) return fromConfig;
1870
2435
  return "auto";
1871
2436
  }
1872
- function applyMinifierEnv(choice) {
1873
- process.env.IONIFY_MINIFIER = choice;
1874
- }
1875
2437
 
1876
2438
  // src/cli/utils/env.ts
1877
2439
  init_cjs_shims();
1878
- var import_fs8 = __toESM(require("fs"), 1);
1879
- var import_path11 = __toESM(require("path"), 1);
2440
+ var import_fs10 = __toESM(require("fs"), 1);
2441
+ var import_path14 = __toESM(require("path"), 1);
1880
2442
  function parseValue(raw) {
1881
2443
  let value = raw.trim();
1882
2444
  if (!value) return "";
@@ -1908,11 +2470,11 @@ function loadEnv(mode = "development", rootDir = process.cwd()) {
1908
2470
  ];
1909
2471
  const merged = {};
1910
2472
  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()) {
2473
+ const filePath = import_path14.default.resolve(rootDir, name);
2474
+ if (!import_fs10.default.existsSync(filePath) || !import_fs10.default.statSync(filePath).isFile()) {
1913
2475
  continue;
1914
2476
  }
1915
- const contents = import_fs8.default.readFileSync(filePath, "utf8");
2477
+ const contents = import_fs10.default.readFileSync(filePath, "utf8");
1916
2478
  const parsed = parseEnvFile(contents);
1917
2479
  Object.assign(merged, parsed);
1918
2480
  }
@@ -1999,11 +2561,6 @@ function resolveTreeshake(input, options = {}) {
1999
2561
  }
2000
2562
  return resolved;
2001
2563
  }
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
2564
 
2008
2565
  // src/cli/utils/scope-hoist.ts
2009
2566
  init_cjs_shims();
@@ -2067,12 +2624,6 @@ function resolveScopeHoist(configValue, options = {}) {
2067
2624
  }
2068
2625
  return resolved;
2069
2626
  }
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
2627
 
2077
2628
  // src/cli/utils/parser.ts
2078
2629
  init_cjs_shims();
@@ -2098,19 +2649,97 @@ function applyParserEnv(mode) {
2098
2649
  // src/cli/commands/dev.ts
2099
2650
  init_cas();
2100
2651
  init_native();
2101
- var import_crypto3 = __toESM(require("crypto"), 1);
2652
+
2653
+ // src/core/utils/define.ts
2654
+ init_cjs_shims();
2655
+ function applyDefineReplacements(code, definitions) {
2656
+ if (!definitions || Object.keys(definitions).length === 0) {
2657
+ return code;
2658
+ }
2659
+ let result = code;
2660
+ const sortedKeys = Object.keys(definitions).sort((a, b) => b.length - a.length);
2661
+ for (const key of sortedKeys) {
2662
+ const value = definitions[key];
2663
+ let replacement;
2664
+ if (typeof value === "string") {
2665
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2666
+ replacement = value;
2667
+ } else {
2668
+ replacement = JSON.stringify(value);
2669
+ }
2670
+ } else if (typeof value === "number" || typeof value === "boolean") {
2671
+ replacement = String(value);
2672
+ } else if (value === null || value === void 0) {
2673
+ replacement = "null";
2674
+ } else {
2675
+ replacement = JSON.stringify(value);
2676
+ }
2677
+ if (key.includes(".")) {
2678
+ result = replaceMemberExpression(result, key, replacement);
2679
+ } else {
2680
+ result = replaceIdentifier(result, key, replacement);
2681
+ }
2682
+ }
2683
+ return result;
2684
+ }
2685
+ function replaceIdentifier(code, identifier, replacement) {
2686
+ const regex = new RegExp(
2687
+ `(?<![\\w.$])${escapeRegExp(identifier)}(?![\\w])`,
2688
+ "g"
2689
+ );
2690
+ return code.replace(regex, replacement);
2691
+ }
2692
+ function replaceMemberExpression(code, expression, replacement) {
2693
+ const parts = expression.split(".");
2694
+ const pattern = parts.map(escapeRegExp).join("\\s*\\.\\s*");
2695
+ const regex = new RegExp(
2696
+ `(?<![\\w.$])${pattern}(?![\\w.])`,
2697
+ "g"
2698
+ );
2699
+ return code.replace(regex, replacement);
2700
+ }
2701
+ function escapeRegExp(str) {
2702
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2703
+ }
2704
+ function buildDefineConfig(userDefine, envValues, envPrefix = ["VITE_", "IONIFY_"]) {
2705
+ const define = { ...userDefine || {} };
2706
+ const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix];
2707
+ for (const [key, value] of Object.entries(envValues)) {
2708
+ const hasPrefix = prefixes.some((prefix) => key.startsWith(prefix));
2709
+ if (hasPrefix || key === "NODE_ENV" || key === "MODE") {
2710
+ const importMetaKey = `import.meta.env.${key}`;
2711
+ if (!(importMetaKey in define)) {
2712
+ define[importMetaKey] = value;
2713
+ }
2714
+ }
2715
+ }
2716
+ if (!("import.meta.env.DEV" in define)) {
2717
+ define["import.meta.env.DEV"] = envValues.MODE !== "production";
2718
+ }
2719
+ if (!("import.meta.env.PROD" in define)) {
2720
+ define["import.meta.env.PROD"] = envValues.MODE === "production";
2721
+ }
2722
+ return define;
2723
+ }
2724
+
2725
+ // src/cli/commands/dev.ts
2726
+ var import_crypto4 = __toESM(require("crypto"), 1);
2727
+ var import_zlib = __toESM(require("zlib"), 1);
2728
+ var IONIFY_CSS_JS_MARKER = "// ionify:css";
2729
+ var IONIFY_VENDOR_PACK_MARKER = "// ionify:vendor-pack";
2102
2730
  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");
2731
+ var __dirname = import_path15.default.dirname(__filename2);
2732
+ var CLIENT_DIR = import_path15.default.resolve(__dirname, "../client");
2733
+ var CLIENT_FALLBACK_DIR = import_path15.default.resolve(process.cwd(), "src/client");
2734
+ var DEPS_PREFIX = "/@deps/";
2106
2735
  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") };
2736
+ const primary = import_path15.default.join(CLIENT_DIR, fileName);
2737
+ if (import_fs11.default.existsSync(primary)) {
2738
+ return { filePath: primary, code: import_fs11.default.readFileSync(primary, "utf8") };
2110
2739
  }
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") };
2740
+ const fallback = import_path15.default.join(CLIENT_FALLBACK_DIR, fileName);
2741
+ if (import_fs11.default.existsSync(fallback)) {
2742
+ return { filePath: fallback, code: import_fs11.default.readFileSync(fallback, "utf8") };
2114
2743
  }
2115
2744
  throw new Error(`Missing Ionify client asset: ${fileName}`);
2116
2745
  }
@@ -2118,7 +2747,7 @@ function readClientAsset(fileName) {
2118
2747
  return readClientAssetFile(fileName).code;
2119
2748
  }
2120
2749
  function guessContentType(filePath) {
2121
- const ext = import_path12.default.extname(filePath);
2750
+ const ext = import_path15.default.extname(filePath);
2122
2751
  if (ext === ".html") return "text/html; charset=utf-8";
2123
2752
  if (ext === ".css") return "text/css; charset=utf-8";
2124
2753
  if (ext === ".json") return "application/json; charset=utf-8";
@@ -2130,16 +2759,289 @@ function guessContentType(filePath) {
2130
2759
  return "application/json; charset=utf-8";
2131
2760
  return "text/plain; charset=utf-8";
2132
2761
  }
2762
+ function mergeVaryHeader(existing, next) {
2763
+ const parts = /* @__PURE__ */ new Set();
2764
+ const add = (value) => {
2765
+ value.split(",").map((v) => v.trim()).filter(Boolean).forEach((v) => parts.add(v));
2766
+ };
2767
+ if (typeof existing === "string") add(existing);
2768
+ else if (Array.isArray(existing)) existing.forEach(add);
2769
+ add(next);
2770
+ return Array.from(parts).join(", ");
2771
+ }
2772
+ function normalizeEtag(tag) {
2773
+ return tag.trim().replace(/^W\//, "");
2774
+ }
2775
+ function isNotModified(req, etag) {
2776
+ const header = req.headers["if-none-match"];
2777
+ const value = Array.isArray(header) ? header.join(",") : header;
2778
+ if (!value) return false;
2779
+ if (value.trim() === "*") return true;
2780
+ const expected = normalizeEtag(etag);
2781
+ return value.split(",").map((t) => t.trim()).filter(Boolean).some((t) => normalizeEtag(t) === expected);
2782
+ }
2783
+ function weakEtagFromStat(prefix, stat) {
2784
+ const mtime = Math.floor(stat.mtimeMs);
2785
+ return `W/"${prefix}-${stat.size}-${mtime}"`;
2786
+ }
2787
+ function shouldCompressContentType(contentType) {
2788
+ const ct = contentType.toLowerCase();
2789
+ return ct.startsWith("text/") || ct.includes("javascript") || ct.includes("json") || ct.includes("xml") || ct.includes("svg");
2790
+ }
2791
+ function selectCompressionEncoding(req) {
2792
+ const header = req.headers["accept-encoding"];
2793
+ const value = Array.isArray(header) ? header.join(",") : header;
2794
+ if (!value) return null;
2795
+ const enc = value.toLowerCase();
2796
+ if (enc.includes("gzip")) return "gzip";
2797
+ return null;
2798
+ }
2799
+ function looksLikeIonifyCssJsModule(body) {
2800
+ const head = body.subarray(0, 96).toString("utf8");
2801
+ return head.trimStart().startsWith(IONIFY_CSS_JS_MARKER);
2802
+ }
2803
+ function computeDepsStampHash(depsAbs) {
2804
+ if (!depsAbs.length) return "0";
2805
+ const entries = [];
2806
+ for (const dep of depsAbs) {
2807
+ const abs = import_path15.default.resolve(dep);
2808
+ try {
2809
+ const stat = import_fs11.default.statSync(abs);
2810
+ entries.push(`${abs}:${stat.size}:${Math.floor(stat.mtimeMs)}`);
2811
+ } catch {
2812
+ entries.push(`${abs}:missing`);
2813
+ }
2814
+ }
2815
+ entries.sort();
2816
+ return getCacheKey(entries.join("|"));
2817
+ }
2818
+ function sendBuffer(req, res, status, contentType, body, opts) {
2819
+ res.setHeader("Content-Type", contentType);
2820
+ res.setHeader("Cache-Control", opts?.cacheControl ?? "no-cache");
2821
+ const etag = opts?.etag;
2822
+ if (etag) {
2823
+ res.setHeader("ETag", etag);
2824
+ if (isNotModified(req, etag)) {
2825
+ res.statusCode = 304;
2826
+ res.end();
2827
+ return;
2828
+ }
2829
+ }
2830
+ const encoding = body.length >= 1024 && shouldCompressContentType(contentType) ? selectCompressionEncoding(req) : null;
2831
+ if (encoding === "gzip") {
2832
+ res.setHeader("Vary", mergeVaryHeader(res.getHeader("Vary"), "Accept-Encoding"));
2833
+ res.setHeader("Content-Encoding", "gzip");
2834
+ res.statusCode = status;
2835
+ res.end(import_zlib.default.gzipSync(body, { level: 1 }));
2836
+ return;
2837
+ }
2838
+ res.statusCode = status;
2839
+ res.end(body);
2840
+ }
2841
+ var LOCKFILE_ORDER = [
2842
+ "pnpm-lock.yaml",
2843
+ "package-lock.json",
2844
+ "yarn.lock",
2845
+ "bun.lockb"
2846
+ ];
2847
+ function readLockfile(rootDir) {
2848
+ for (const name of LOCKFILE_ORDER) {
2849
+ const filePath = import_path15.default.join(rootDir, name);
2850
+ if (!import_fs11.default.existsSync(filePath)) continue;
2851
+ const contents = import_fs11.default.readFileSync(filePath);
2852
+ const packageCount = estimateLockfilePackageCount(name, contents);
2853
+ return { name, path: filePath, contents, packageCount };
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function estimateLockfilePackageCount(name, contents) {
2858
+ if (name === "package-lock.json") {
2859
+ try {
2860
+ const parsed = JSON.parse(contents.toString("utf8"));
2861
+ if (parsed?.packages && typeof parsed.packages === "object") {
2862
+ return Object.keys(parsed.packages).length;
2863
+ }
2864
+ } catch {
2865
+ return null;
2866
+ }
2867
+ }
2868
+ if (name === "pnpm-lock.yaml") {
2869
+ const text = contents.toString("utf8");
2870
+ return text.split("\n").filter((line) => line.trimStart().startsWith("/")).length;
2871
+ }
2872
+ if (name === "yarn.lock") {
2873
+ const text = contents.toString("utf8");
2874
+ return text.split("\n").filter((line) => line && !line.startsWith(" ") && line.endsWith(":")).length;
2875
+ }
2876
+ return null;
2877
+ }
2878
+ function computeDepsHash(configHash, lockfile, opts) {
2879
+ const hash = import_crypto4.default.createHash("sha256");
2880
+ hash.update(configHash);
2881
+ if (lockfile) {
2882
+ hash.update(lockfile.contents);
2883
+ }
2884
+ hash.update(`NODE_ENV=${opts.nodeEnv}`);
2885
+ hash.update(`optimizeDeps.sourcemap=${opts.sourcemap ? "1" : "0"}`);
2886
+ hash.update(`optimizeDeps.bundleEsm=${opts.bundleEsm ? "1" : "0"}`);
2887
+ return hash.digest("hex").slice(0, 16);
2888
+ }
2889
+ function readProjectPackageJson(rootDir) {
2890
+ const pkgPath = import_path15.default.join(rootDir, "package.json");
2891
+ if (!import_fs11.default.existsSync(pkgPath)) return null;
2892
+ try {
2893
+ return JSON.parse(import_fs11.default.readFileSync(pkgPath, "utf8"));
2894
+ } catch {
2895
+ return null;
2896
+ }
2897
+ }
2898
+ function detectVendorSpecifiers(pkgJson) {
2899
+ if (!pkgJson || typeof pkgJson !== "object") return [];
2900
+ const deps = {
2901
+ ...pkgJson.dependencies || {},
2902
+ ...pkgJson.devDependencies || {},
2903
+ ...pkgJson.peerDependencies || {}
2904
+ };
2905
+ const has = (name) => Object.prototype.hasOwnProperty.call(deps, name);
2906
+ if (has("react") || has("react-dom")) {
2907
+ return [
2908
+ "react",
2909
+ "react-dom",
2910
+ "react-dom/client",
2911
+ "scheduler",
2912
+ "react/jsx-runtime",
2913
+ "react/jsx-dev-runtime",
2914
+ "react-refresh"
2915
+ ];
2916
+ }
2917
+ if (has("vue")) {
2918
+ return ["vue", "@vue/runtime-dom", "@vue/runtime-core"];
2919
+ }
2920
+ if (has("svelte")) {
2921
+ return ["svelte", "svelte/internal"];
2922
+ }
2923
+ return [];
2924
+ }
2925
+ function computeSubpathForDep2(fsPath, pkg) {
2926
+ const computed = computeSubpathFromEntryPath(fsPath);
2927
+ if (computed) return computed;
2928
+ if (import_fs11.default.existsSync(fsPath)) return null;
2929
+ const raw = pkg?.subpath;
2930
+ if (typeof raw === "string") {
2931
+ const cleaned = raw.replace(/^\.\//, "").replace(/^\/+/, "");
2932
+ if (cleaned && cleaned !== "." && cleaned !== "index") {
2933
+ return cleaned;
2934
+ }
2935
+ }
2936
+ return null;
2937
+ }
2938
+ function resolveVendorDeps(rootDir, specifiers) {
2939
+ if (!native?.resolveModule) return [];
2940
+ const seen = /* @__PURE__ */ new Set();
2941
+ const resolved = [];
2942
+ for (const spec of specifiers) {
2943
+ try {
2944
+ const r = native.resolveModule(spec, rootDir);
2945
+ const fsPath = r?.fsPath ?? r?.fs_path ?? null;
2946
+ if (!fsPath || typeof fsPath !== "string") continue;
2947
+ const pkg = r?.pkg ?? null;
2948
+ const packageName = typeof pkg?.name === "string" ? pkg.name : spec;
2949
+ const packageVersion = typeof pkg?.version === "string" ? pkg.version : "0.0.0";
2950
+ const subpath = computeSubpathForDep2(fsPath, pkg);
2951
+ const entry = registerDepEntry({
2952
+ entryPath: fsPath,
2953
+ packageName,
2954
+ packageVersion,
2955
+ subpath
2956
+ });
2957
+ if (seen.has(entry.fileName)) continue;
2958
+ seen.add(entry.fileName);
2959
+ resolved.push({
2960
+ specifier: spec,
2961
+ entryPath: fsPath,
2962
+ fileName: entry.fileName,
2963
+ packageLabel: formatDepLabel(packageName, subpath)
2964
+ });
2965
+ } catch {
2966
+ }
2967
+ }
2968
+ return resolved;
2969
+ }
2970
+ function injectModulePreload(html, href) {
2971
+ const tag = `<link rel="modulepreload" href="${href}">`;
2972
+ if (html.includes(tag)) return html;
2973
+ const headCloseMatch = html.match(/<\/head>/i);
2974
+ if (headCloseMatch?.index !== void 0) {
2975
+ const idx = headCloseMatch.index;
2976
+ return `${html.slice(0, idx)}${tag}
2977
+ ${html.slice(idx)}`;
2978
+ }
2979
+ const headOpenMatch = html.match(/<head[^>]*>/i);
2980
+ if (headOpenMatch?.index !== void 0) {
2981
+ const idx = headOpenMatch.index + headOpenMatch[0].length;
2982
+ return `${html.slice(0, idx)}
2983
+ ${tag}${html.slice(idx)}`;
2984
+ }
2985
+ return `${tag}
2986
+ ${html}`;
2987
+ }
2988
+ function pruneDepsCache(rootDir, depsHash) {
2989
+ const depsRoot = import_path15.default.join(rootDir, ".ionify", "deps");
2990
+ if (!import_fs11.default.existsSync(depsRoot)) return;
2991
+ const entries = import_fs11.default.readdirSync(depsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2992
+ const fullPath = import_path15.default.join(depsRoot, entry.name);
2993
+ const stat = import_fs11.default.statSync(fullPath);
2994
+ return { name: entry.name, path: fullPath, mtimeMs: stat.mtimeMs };
2995
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs);
2996
+ const keep = /* @__PURE__ */ new Set();
2997
+ keep.add(depsHash);
2998
+ for (const entry of entries.slice(0, 2)) {
2999
+ keep.add(entry.name);
3000
+ }
3001
+ for (const entry of entries) {
3002
+ if (!keep.has(entry.name)) {
3003
+ import_fs11.default.rmSync(entry.path, { recursive: true, force: true });
3004
+ }
3005
+ }
3006
+ }
3007
+ function loadDepsManifestIndex(depsRoot) {
3008
+ const manifestPath = import_path15.default.join(depsRoot, "manifest.json");
3009
+ if (!import_fs11.default.existsSync(manifestPath)) return /* @__PURE__ */ new Map();
3010
+ try {
3011
+ const raw = import_fs11.default.readFileSync(manifestPath, "utf8");
3012
+ const parsed = JSON.parse(raw);
3013
+ const entries = parsed?.entries ?? {};
3014
+ const map = /* @__PURE__ */ new Map();
3015
+ for (const [entryPath, entry] of Object.entries(entries)) {
3016
+ if (!entry?.outFile) continue;
3017
+ map.set(entry.outFile, {
3018
+ entryPath,
3019
+ packageLabel: entry.package || "unknown",
3020
+ hasSourcemap: entry.hasSourcemap === true
3021
+ });
3022
+ }
3023
+ return map;
3024
+ } catch {
3025
+ return /* @__PURE__ */ new Map();
3026
+ }
3027
+ }
3028
+ function formatDepLabel(name, subpath) {
3029
+ if (!subpath) return name;
3030
+ const cleaned = subpath.replace(/^\.\//, "").replace(/^\/+/, "");
3031
+ if (!cleaned || cleaned === ".") return name;
3032
+ return `${name}/${cleaned}`;
3033
+ }
2133
3034
  async function startDevServer({
2134
3035
  port = 5173,
3036
+ host = process.env.IONIFY_HOST || "127.0.0.1",
2135
3037
  enableSignalHandlers = true
2136
3038
  } = {}) {
2137
- const rootDir = process.cwd();
3039
+ const bootStartMs = Date.now();
3040
+ const userConfig = await loadIonifyConfig();
3041
+ const rootDir = userConfig?.root || process.cwd();
2138
3042
  const watcher = new IonifyWatcher(rootDir);
2139
3043
  const cacheDebug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
2140
- const userConfig = await loadIonifyConfig();
2141
3044
  const minifier = resolveMinifier(userConfig, { envVar: process.env.IONIFY_MINIFIER });
2142
- applyMinifierEnv(minifier);
2143
3045
  const parserMode = resolveParser(userConfig, { envMode: process.env.IONIFY_PARSER });
2144
3046
  applyParserEnv(parserMode);
2145
3047
  const treeshake = resolveTreeshake(userConfig?.treeshake, {
@@ -2147,15 +3049,15 @@ async function startDevServer({
2147
3049
  includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
2148
3050
  excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
2149
3051
  });
2150
- applyTreeshakeEnv(treeshake);
2151
3052
  const scopeHoist = resolveScopeHoist(userConfig?.scopeHoist, {
2152
3053
  envMode: process.env.IONIFY_SCOPE_HOIST,
2153
3054
  inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
2154
3055
  constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
2155
3056
  combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
2156
3057
  });
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;
3058
+ const resolvedEntries = userConfig?.entry ? (Array.isArray(userConfig.entry) ? userConfig.entry : [userConfig.entry]).map(
3059
+ (entry) => entry.startsWith("/") ? import_path15.default.join(rootDir, entry) : import_path15.default.resolve(rootDir, entry)
3060
+ ) : void 0;
2159
3061
  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
3062
  const rawVersionInputs = {
2161
3063
  parserMode,
@@ -2163,14 +3065,64 @@ async function startDevServer({
2163
3065
  treeshake,
2164
3066
  scopeHoist,
2165
3067
  plugins: pluginNames,
2166
- entry: resolvedEntry ? [resolvedEntry] : null,
3068
+ entry: resolvedEntries ?? null,
2167
3069
  cssOptions: userConfig?.css,
2168
3070
  assetOptions: userConfig?.assets ?? userConfig?.asset
2169
3071
  };
2170
3072
  const configHash = computeGraphVersion(rawVersionInputs);
2171
3073
  logInfo(`[Dev] Version hash: ${configHash}`);
2172
3074
  process.env.IONIFY_CONFIG_HASH = configHash;
2173
- const casRoot = import_path12.default.join(rootDir, ".ionify", "cas");
3075
+ const casRoot = import_path15.default.join(rootDir, ".ionify", "cas");
3076
+ const lockfile = readLockfile(rootDir);
3077
+ if (lockfile) {
3078
+ const countLabel = lockfile.packageCount === null ? "unknown" : lockfile.packageCount;
3079
+ logInfo(`[deps] SCAN lockfile: ${lockfile.name} (${countLabel} packages)`);
3080
+ }
3081
+ const depsSourcemapEnabled = userConfig?.optimizeDeps?.sourcemap === true;
3082
+ const depsBundleEsmEnabled = userConfig?.optimizeDeps?.bundleEsm !== false;
3083
+ const depsNodeEnv = process.env.NODE_ENV ?? "development";
3084
+ const depsHash = computeDepsHash(configHash, lockfile, {
3085
+ nodeEnv: depsNodeEnv,
3086
+ sourcemap: depsSourcemapEnabled,
3087
+ bundleEsm: depsBundleEsmEnabled
3088
+ });
3089
+ logInfo(`[deps] depsHash: ${depsHash} from ${lockfile?.name ?? "config"}`);
3090
+ const depsRoot = import_path15.default.join(rootDir, ".ionify", "deps", depsHash);
3091
+ import_fs11.default.mkdirSync(depsRoot, { recursive: true });
3092
+ pruneDepsCache(rootDir, depsHash);
3093
+ const depsManifestIndex = loadDepsManifestIndex(depsRoot);
3094
+ const optimizeVendorMode = userConfig?.optimizeDeps?.vendor ?? "auto";
3095
+ const optimizeExclude = Array.isArray(userConfig?.optimizeDeps?.exclude) ? new Set(userConfig.optimizeDeps.exclude) : null;
3096
+ const autoVendor = optimizeVendorMode === "auto";
3097
+ const vendorSpecifiersRaw = optimizeVendorMode === false ? [] : Array.isArray(optimizeVendorMode) ? optimizeVendorMode : autoVendor ? detectVendorSpecifiers(readProjectPackageJson(rootDir)) : [];
3098
+ const vendorSpecifiers = vendorSpecifiersRaw.map((s) => String(s).trim()).filter(Boolean).filter((s) => !optimizeExclude?.has(s));
3099
+ const vendorDeps = resolveVendorDeps(rootDir, vendorSpecifiers);
3100
+ const vendorPackFileName = vendorDeps.length > 0 ? `vendor.${depsHash}.js` : null;
3101
+ const vendorPackUrl = vendorPackFileName ? `${DEPS_PREFIX}${vendorPackFileName}` : null;
3102
+ const ensureVendorPackFile = () => {
3103
+ if (!vendorPackFileName || !vendorPackUrl || vendorDeps.length === 0) return;
3104
+ const vendorKey = getCacheKey(
3105
+ `vendor:v1:${vendorDeps.map((d) => `${d.specifier}:${d.fileName}`).sort().join("|")}`
3106
+ );
3107
+ const filePath = import_path15.default.join(depsRoot, vendorPackFileName);
3108
+ if (import_fs11.default.existsSync(filePath)) {
3109
+ try {
3110
+ const head = import_fs11.default.readFileSync(filePath, "utf8").slice(0, 256);
3111
+ if (head.includes(`${IONIFY_VENDOR_PACK_MARKER} ${vendorKey}`)) return;
3112
+ } catch {
3113
+ }
3114
+ }
3115
+ const imports = vendorDeps.slice().sort((a, b) => a.specifier.localeCompare(b.specifier)).map((d) => `import "${DEPS_PREFIX}${d.fileName}";`).join("\n");
3116
+ const body = `${IONIFY_VENDOR_PACK_MARKER} ${vendorKey}
3117
+ // depsHash: ${depsHash}
3118
+ // vendor: ${vendorDeps.map((d) => d.specifier).join(", ")}
3119
+ ${imports}
3120
+ `;
3121
+ try {
3122
+ import_fs11.default.writeFileSync(filePath, body, "utf8");
3123
+ } catch {
3124
+ }
3125
+ };
2174
3126
  const transformer = new TransformEngine({ casRoot, versionHash: configHash });
2175
3127
  const graph = new Graph(rawVersionInputs);
2176
3128
  if (native?.initAstCache) {
@@ -2181,7 +3133,7 @@ async function startDevServer({
2181
3133
  try {
2182
3134
  native.astCacheWarmup();
2183
3135
  } catch (err) {
2184
- logWarn(`AST cache warmup skipped: ${err}`);
3136
+ logWarn2(`AST cache warmup skipped: ${err}`);
2185
3137
  }
2186
3138
  }
2187
3139
  if (native?.astCacheStats) {
@@ -2212,6 +3164,9 @@ async function startDevServer({
2212
3164
  NODE_ENV: process.env.NODE_ENV,
2213
3165
  MODE: process.env.MODE
2214
3166
  };
3167
+ const envPrefix = userConfig?.envPrefix || ["VITE_", "IONIFY_"];
3168
+ const defineConfig = buildDefineConfig(userConfig?.define, envValues, envPrefix);
3169
+ logInfo(`[define] ${Object.keys(defineConfig).length} replacements configured`);
2215
3170
  const envPlaceholderPattern = /%([A-Z0-9_]+)%/g;
2216
3171
  const envEnabledExts = /* @__PURE__ */ new Set([
2217
3172
  ".html",
@@ -2255,7 +3210,7 @@ async function startDevServer({
2255
3210
  const buildUpdatePayload = async (modules) => {
2256
3211
  const updates = [];
2257
3212
  for (const mod of modules) {
2258
- const exists = import_fs9.default.existsSync(mod.absPath);
3213
+ const exists = import_fs11.default.existsSync(mod.absPath);
2259
3214
  if (mod.reason === "deleted" || !exists) {
2260
3215
  graph.removeFile(mod.absPath);
2261
3216
  watcher.unwatchFile(mod.absPath);
@@ -2269,9 +3224,53 @@ async function startDevServer({
2269
3224
  continue;
2270
3225
  }
2271
3226
  watcher.watchFile(mod.absPath);
3227
+ const ext = import_path15.default.extname(mod.absPath).toLowerCase();
3228
+ if (ext === ".css") {
3229
+ let hash2 = mod.hash;
3230
+ if (!hash2) {
3231
+ try {
3232
+ hash2 = getCacheKey(import_fs11.default.readFileSync(mod.absPath, "utf8"));
3233
+ } catch {
3234
+ hash2 = graph.getNode(mod.absPath)?.hash ?? getCacheKey(mod.absPath);
3235
+ }
3236
+ }
3237
+ const depsAbs2 = graph.getNode(mod.absPath)?.deps ?? [];
3238
+ const kind = /\.module\.css$/i.test(mod.absPath) ? "css-module" : "css";
3239
+ graph.recordFile(mod.absPath, hash2, depsAbs2, [], kind);
3240
+ updates.push({
3241
+ url: mod.url,
3242
+ hash: hash2,
3243
+ deps: depsAbs2.map((dep) => normalizeUrlFromFs(rootDir, dep)),
3244
+ reason: mod.reason,
3245
+ status: "updated",
3246
+ code: ""
3247
+ });
3248
+ continue;
3249
+ }
3250
+ if (isAssetExt(ext)) {
3251
+ let hash2 = mod.hash;
3252
+ if (!hash2) {
3253
+ try {
3254
+ const buf = import_fs11.default.readFileSync(mod.absPath);
3255
+ hash2 = import_crypto4.default.createHash("sha256").update(buf).digest("hex");
3256
+ } catch {
3257
+ hash2 = graph.getNode(mod.absPath)?.hash ?? getCacheKey(mod.absPath);
3258
+ }
3259
+ }
3260
+ graph.recordFile(mod.absPath, hash2, [], [], "asset");
3261
+ updates.push({
3262
+ url: mod.url,
3263
+ hash: hash2,
3264
+ deps: [],
3265
+ reason: mod.reason,
3266
+ status: "updated",
3267
+ code: ""
3268
+ });
3269
+ continue;
3270
+ }
2272
3271
  let code;
2273
3272
  try {
2274
- code = import_fs9.default.readFileSync(mod.absPath, "utf8");
3273
+ code = import_fs11.default.readFileSync(mod.absPath, "utf8");
2275
3274
  } catch (err) {
2276
3275
  logError("Failed to read module during HMR apply", err);
2277
3276
  throw err;
@@ -2299,13 +3298,14 @@ async function startDevServer({
2299
3298
  const result = await transformer.run({
2300
3299
  path: mod.absPath,
2301
3300
  code,
2302
- ext: import_path12.default.extname(mod.absPath),
2303
- moduleHash: hash
3301
+ ext: import_path15.default.extname(mod.absPath),
3302
+ moduleHash: hash,
3303
+ config: userConfig ?? null
2304
3304
  });
2305
3305
  const transformed = result.code;
2306
3306
  const envApplied = applyEnvPlaceholders(
2307
3307
  transformed,
2308
- import_path12.default.extname(mod.absPath)
3308
+ import_path15.default.extname(mod.absPath)
2309
3309
  );
2310
3310
  updates.push({
2311
3311
  url: mod.url,
@@ -2346,7 +3346,7 @@ async function startDevServer({
2346
3346
  const asset = readClientAssetFile("react-refresh-runtime.js");
2347
3347
  let reactRefreshPath;
2348
3348
  try {
2349
- const projectRequire = (0, import_module3.createRequire)(import_path12.default.join(rootDir, "package.json"));
3349
+ const projectRequire = (0, import_module3.createRequire)(import_path15.default.join(rootDir, "package.json"));
2350
3350
  reactRefreshPath = projectRequire.resolve("react-refresh/runtime");
2351
3351
  } catch (err) {
2352
3352
  logError("Failed to resolve react-refresh/runtime", err);
@@ -2429,6 +3429,108 @@ async function startDevServer({
2429
3429
  sendJson(res, 200, { ok: true });
2430
3430
  return;
2431
3431
  }
3432
+ if (reqPath.startsWith(DEPS_PREFIX)) {
3433
+ const fileName = reqPath.slice(DEPS_PREFIX.length);
3434
+ if (vendorPackFileName && fileName === vendorPackFileName) {
3435
+ ensureVendorPackFile();
3436
+ }
3437
+ if (fileName.endsWith(".js.map")) {
3438
+ const mapPath = import_path15.default.join(depsRoot, fileName);
3439
+ if (import_fs11.default.existsSync(mapPath)) {
3440
+ const stat = import_fs11.default.statSync(mapPath);
3441
+ const etag = weakEtagFromStat(`deps-map-${depsHash}`, stat);
3442
+ if (isNotModified(req, etag)) {
3443
+ res.setHeader("ETag", etag);
3444
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
3445
+ res.statusCode = 304;
3446
+ res.end();
3447
+ return;
3448
+ }
3449
+ sendBuffer(
3450
+ req,
3451
+ res,
3452
+ 200,
3453
+ "application/json; charset=utf-8",
3454
+ import_fs11.default.readFileSync(mapPath),
3455
+ { etag, cacheControl: "public, max-age=31536000, immutable" }
3456
+ );
3457
+ return;
3458
+ }
3459
+ }
3460
+ const depsFilePath = import_path15.default.join(depsRoot, fileName);
3461
+ const entryFromManifest = depsManifestIndex.get(fileName);
3462
+ const entryFromRegistry = getDepEntry(fileName);
3463
+ const entryPath = entryFromManifest?.entryPath ?? entryFromRegistry?.entryPath;
3464
+ const packageLabel = entryFromRegistry?.packageName ? formatDepLabel(entryFromRegistry.packageName, entryFromRegistry.subpath) : entryFromManifest?.packageLabel ?? fileName;
3465
+ if (import_fs11.default.existsSync(depsFilePath)) {
3466
+ const stat = import_fs11.default.statSync(depsFilePath);
3467
+ const etag = weakEtagFromStat(`deps-${depsHash}`, stat);
3468
+ if (isNotModified(req, etag)) {
3469
+ res.setHeader("ETag", etag);
3470
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
3471
+ res.statusCode = 304;
3472
+ res.end();
3473
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: HIT from cache (304)`);
3474
+ return;
3475
+ }
3476
+ sendBuffer(
3477
+ req,
3478
+ res,
3479
+ 200,
3480
+ "application/javascript; charset=utf-8",
3481
+ import_fs11.default.readFileSync(depsFilePath),
3482
+ { etag, cacheControl: "public, max-age=31536000, immutable" }
3483
+ );
3484
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: HIT from cache`);
3485
+ return;
3486
+ }
3487
+ if (!entryPath || !native?.optimizeDependency) {
3488
+ res.statusCode = 404;
3489
+ res.end("Dependency not found");
3490
+ return;
3491
+ }
3492
+ try {
3493
+ const start = Date.now();
3494
+ const rawSize = import_fs11.default.existsSync(entryPath) ? import_fs11.default.statSync(entryPath).size : 0;
3495
+ const result2 = native.optimizeDependency(entryPath, depsHash, depsSourcemapEnabled, depsBundleEsmEnabled);
3496
+ const outPath = result2?.out_path ?? result2?.outPath ?? depsFilePath;
3497
+ const mapPath = result2?.map_path ?? result2?.mapPath ?? null;
3498
+ const resolvedOutPath = import_path15.default.isAbsolute(outPath) ? outPath : import_path15.default.join(depsRoot, outPath);
3499
+ if (!import_fs11.default.existsSync(resolvedOutPath)) {
3500
+ throw new Error("Optimizer did not produce output");
3501
+ }
3502
+ const outBuffer = import_fs11.default.readFileSync(resolvedOutPath);
3503
+ const optimizedSize = outBuffer.length;
3504
+ const stat = import_fs11.default.statSync(resolvedOutPath);
3505
+ const etag = weakEtagFromStat(`deps-${depsHash}`, stat);
3506
+ sendBuffer(req, res, 200, "application/javascript; charset=utf-8", outBuffer, {
3507
+ etag,
3508
+ cacheControl: "public, max-age=31536000, immutable"
3509
+ });
3510
+ const elapsed = Date.now() - start;
3511
+ const rawKb = (rawSize / 1024).toFixed(1);
3512
+ const optKb = (optimizedSize / 1024).toFixed(1);
3513
+ const mapSuffix = mapPath ? ` map=${import_path15.default.basename(mapPath)}` : "";
3514
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: MISS \u2192 BUILD (${elapsed}ms, ${rawKb}KB \u2192 ${optKb}KB)${mapSuffix}`);
3515
+ const refreshed = loadDepsManifestIndex(depsRoot);
3516
+ refreshed.forEach((value, key) => depsManifestIndex.set(key, value));
3517
+ return;
3518
+ } catch (err) {
3519
+ logWarn2(
3520
+ `[deps] WARN: Optimization failed for ${packageLabel}, serving raw (fallback): ${String(err)}`
3521
+ );
3522
+ try {
3523
+ const raw = import_fs11.default.readFileSync(entryPath);
3524
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
3525
+ res.end(raw);
3526
+ } catch (readErr) {
3527
+ logError("Failed to read raw dependency", readErr);
3528
+ res.statusCode = 500;
3529
+ res.end("Dependency optimization failed");
3530
+ }
3531
+ return;
3532
+ }
3533
+ }
2432
3534
  const fsPath = decodePublicPath(rootDir, reqPath);
2433
3535
  if (!fsPath) {
2434
3536
  res.statusCode = 404;
@@ -2437,12 +3539,12 @@ async function startDevServer({
2437
3539
  }
2438
3540
  let effectiveFsPath = fsPath;
2439
3541
  let effectiveUrlPath = reqPath;
2440
- if (import_fs9.default.existsSync(effectiveFsPath) && import_fs9.default.statSync(effectiveFsPath).isDirectory()) {
3542
+ if (import_fs11.default.existsSync(effectiveFsPath) && import_fs11.default.statSync(effectiveFsPath).isDirectory()) {
2441
3543
  const indexExtensions = [".html", ".js", ".ts", ".tsx", ".jsx"];
2442
3544
  let found = false;
2443
3545
  for (const ext2 of indexExtensions) {
2444
- const indexFile = import_path12.default.join(effectiveFsPath, `index${ext2}`);
2445
- if (import_fs9.default.existsSync(indexFile)) {
3546
+ const indexFile = import_path15.default.join(effectiveFsPath, `index${ext2}`);
3547
+ if (import_fs11.default.existsSync(indexFile)) {
2446
3548
  effectiveFsPath = indexFile;
2447
3549
  effectiveUrlPath = effectiveUrlPath.endsWith("/") ? `${effectiveUrlPath}index${ext2}` : `${effectiveUrlPath}/index${ext2}`;
2448
3550
  found = true;
@@ -2450,13 +3552,13 @@ async function startDevServer({
2450
3552
  }
2451
3553
  }
2452
3554
  if (!found) {
2453
- const packageJson = import_path12.default.join(effectiveFsPath, "package.json");
2454
- if (import_fs9.default.existsSync(packageJson)) {
3555
+ const packageJson = import_path15.default.join(effectiveFsPath, "package.json");
3556
+ if (import_fs11.default.existsSync(packageJson)) {
2455
3557
  try {
2456
- const pkg = JSON.parse(import_fs9.default.readFileSync(packageJson, "utf8"));
3558
+ const pkg = JSON.parse(import_fs11.default.readFileSync(packageJson, "utf8"));
2457
3559
  if (pkg.main) {
2458
- const mainFile = import_path12.default.join(effectiveFsPath, pkg.main);
2459
- if (import_fs9.default.existsSync(mainFile)) {
3560
+ const mainFile = import_path15.default.join(effectiveFsPath, pkg.main);
3561
+ if (import_fs11.default.existsSync(mainFile)) {
2460
3562
  effectiveFsPath = mainFile;
2461
3563
  found = true;
2462
3564
  }
@@ -2467,8 +3569,8 @@ async function startDevServer({
2467
3569
  }
2468
3570
  if (!found) {
2469
3571
  for (const ext2 of indexExtensions) {
2470
- const moduleFile = import_path12.default.join(effectiveFsPath, `module${ext2}`);
2471
- if (import_fs9.default.existsSync(moduleFile)) {
3572
+ const moduleFile = import_path15.default.join(effectiveFsPath, `module${ext2}`);
3573
+ if (import_fs11.default.existsSync(moduleFile)) {
2472
3574
  effectiveFsPath = moduleFile;
2473
3575
  found = true;
2474
3576
  break;
@@ -2481,16 +3583,16 @@ async function startDevServer({
2481
3583
  return;
2482
3584
  }
2483
3585
  }
2484
- if (!import_fs9.default.existsSync(effectiveFsPath)) {
3586
+ if (!import_fs11.default.existsSync(effectiveFsPath)) {
2485
3587
  res.statusCode = 404;
2486
3588
  res.end("Not found");
2487
3589
  return;
2488
3590
  }
2489
- const ext = import_path12.default.extname(effectiveFsPath);
3591
+ const ext = import_path15.default.extname(effectiveFsPath);
2490
3592
  if (isAssetExt(ext)) {
2491
3593
  try {
2492
- const data = import_fs9.default.readFileSync(effectiveFsPath);
2493
- const assetHash = import_crypto3.default.createHash("sha256").update(data).digest("hex");
3594
+ const data = import_fs11.default.readFileSync(effectiveFsPath);
3595
+ const assetHash = import_crypto4.default.createHash("sha256").update(data).digest("hex");
2494
3596
  const kind = "asset";
2495
3597
  const changed2 = graph.recordFile(effectiveFsPath, assetHash, [], [], kind);
2496
3598
  watcher.watchFile(effectiveFsPath);
@@ -2506,67 +3608,115 @@ async function startDevServer({
2506
3608
  return;
2507
3609
  } else {
2508
3610
  res.writeHead(200, { "Content-Type": contentTypeForAsset(ext) });
2509
- import_fs9.default.createReadStream(effectiveFsPath).pipe(res);
3611
+ import_fs11.default.createReadStream(effectiveFsPath).pipe(res);
2510
3612
  return;
2511
3613
  }
2512
3614
  }
2513
3615
  if (ext === ".css") {
2514
3616
  try {
2515
- const cssSource = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
3617
+ const cssSource = import_fs11.default.readFileSync(effectiveFsPath, "utf8");
2516
3618
  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";
3619
+ const mode = "raw" in q ? "css:raw-string" : "url" in q ? "css:url" : isModule ? "css:module" : "inline" in q ? "css:inline" : "css:raw";
2519
3620
  const contentHash = getCacheKey(cssSource);
2520
3621
  watcher.watchFile(effectiveFsPath);
2521
3622
  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");
3623
+ const prevDeps = graph.getNode(effectiveFsPath)?.deps ?? [];
3624
+ for (const dep of prevDeps) {
3625
+ watcher.watchFile(dep);
3626
+ }
3627
+ const depsStampHash = computeDepsStampHash(prevDeps);
3628
+ let artifactHash = getCacheKey(
3629
+ `css:v2:${effectiveFsPath}:${contentHash}:${mode}:${depsStampHash}`
3630
+ );
3631
+ let casDir = getCasArtifactPath(casRoot, configHash, artifactHash);
3632
+ const jsMode = mode !== "css:raw";
3633
+ let casFile = import_path15.default.join(casDir, jsMode ? "transformed.js" : "transformed.css");
2525
3634
  let finalBuffer = null;
2526
- if (import_fs9.default.existsSync(casFile)) {
3635
+ if (import_fs11.default.existsSync(casFile)) {
2527
3636
  try {
2528
- finalBuffer = import_fs9.default.readFileSync(casFile);
2529
- res.setHeader("X-Ionify-Cache", "HIT");
3637
+ finalBuffer = import_fs11.default.readFileSync(casFile);
3638
+ const ok = jsMode ? looksLikeIonifyCssJsModule(finalBuffer) : !looksLikeIonifyCssJsModule(finalBuffer);
3639
+ if (ok) {
3640
+ res.setHeader("X-Ionify-Cache", "HIT");
3641
+ } else {
3642
+ finalBuffer = null;
3643
+ res.setHeader("X-Ionify-Cache", "MISMATCH");
3644
+ }
2530
3645
  } catch {
2531
3646
  finalBuffer = null;
2532
3647
  }
2533
3648
  }
2534
3649
  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;
3650
+ let body;
3651
+ if (mode === "css:url") {
3652
+ const rawUrl = `${effectiveUrlPath}?v=${contentHash}-${depsStampHash.slice(0, 8)}`;
3653
+ body = renderCssUrlModule(rawUrl);
3654
+ } else {
3655
+ const { css: compiledCss, tokens, deps } = await compileCss({
3656
+ code: cssSource,
3657
+ filePath: effectiveFsPath,
3658
+ rootDir,
3659
+ modules: isModule
3660
+ });
3661
+ const depsAbs2 = deps.map((d) => d.filePath).filter(Boolean);
3662
+ const nextDepsStampHash = computeDepsStampHash(depsAbs2);
3663
+ artifactHash = getCacheKey(
3664
+ `css:v2:${effectiveFsPath}:${contentHash}:${mode}:${nextDepsStampHash}`
3665
+ );
3666
+ casDir = getCasArtifactPath(casRoot, configHash, artifactHash);
3667
+ casFile = import_path15.default.join(casDir, jsMode ? "transformed.js" : "transformed.css");
3668
+ const changed2 = graph.recordFile(effectiveFsPath, contentHash, depsAbs2, [], kind);
3669
+ if (changed2) {
3670
+ logInfo(`[Graph] CSS updated: ${effectiveFsPath}`);
3671
+ }
3672
+ for (const dep of depsAbs2) {
3673
+ watcher.watchFile(dep);
3674
+ }
3675
+ if (mode === "css:raw") {
3676
+ body = compiledCss;
3677
+ } else if (mode === "css:raw-string") {
3678
+ body = renderCssRawStringModule(compiledCss);
3679
+ } else {
3680
+ body = renderCssModule({
3681
+ css: compiledCss,
3682
+ filePath: effectiveFsPath,
3683
+ tokens: isModule ? tokens ?? {} : void 0
3684
+ });
3685
+ }
3686
+ }
2546
3687
  finalBuffer = Buffer.from(body, "utf8");
2547
3688
  res.setHeader("X-Ionify-Cache", "MISS");
2548
3689
  try {
2549
- import_fs9.default.mkdirSync(casDir, { recursive: true });
2550
- import_fs9.default.writeFileSync(casFile, finalBuffer);
3690
+ import_fs11.default.mkdirSync(casDir, { recursive: true });
3691
+ import_fs11.default.writeFileSync(casFile, finalBuffer);
2551
3692
  } catch {
2552
3693
  }
2553
3694
  }
2554
- if (isModule || isInline) {
2555
- res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
3695
+ const etag = `W/"css-${configHash}-${artifactHash}-${mode}"`;
3696
+ if (jsMode) {
3697
+ sendBuffer(req, res, 200, "application/javascript; charset=utf-8", finalBuffer, {
3698
+ etag,
3699
+ cacheControl: "no-cache"
3700
+ });
2556
3701
  } else {
2557
- res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
3702
+ sendBuffer(req, res, 200, "text/css; charset=utf-8", finalBuffer, {
3703
+ etag,
3704
+ cacheControl: "no-cache"
3705
+ });
2558
3706
  }
2559
- res.end(finalBuffer);
2560
- logInfo(`Served: ${effectiveUrlPath} deps:0 ${changed2 ? "(updated)" : "(cached)"}`);
3707
+ logInfo(`Served: ${effectiveUrlPath} ${mode}`);
2561
3708
  return;
2562
3709
  } catch (err) {
2563
3710
  logError("Failed to process CSS", err);
3711
+ hmr.broadcastError({
3712
+ message: err instanceof Error ? `Failed to process CSS: ${err.stack || err.message}` : `Failed to process CSS: ${String(err)}`
3713
+ });
2564
3714
  res.statusCode = 500;
2565
3715
  res.end("Failed to process CSS");
2566
3716
  return;
2567
3717
  }
2568
3718
  }
2569
- const code = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
3719
+ const code = import_fs11.default.readFileSync(effectiveFsPath, "utf8");
2570
3720
  let hash;
2571
3721
  let specs;
2572
3722
  if (native?.parseModuleIr) {
@@ -2588,23 +3738,40 @@ async function startDevServer({
2588
3738
  for (const dep of depsAbs) {
2589
3739
  watcher.watchFile(dep);
2590
3740
  }
2591
- const result = await transformer.run({
2592
- path: effectiveFsPath,
2593
- code,
2594
- ext,
2595
- moduleHash: hash
2596
- });
3741
+ let result;
3742
+ try {
3743
+ result = await transformer.run({
3744
+ path: effectiveFsPath,
3745
+ code,
3746
+ ext,
3747
+ moduleHash: hash,
3748
+ config: userConfig ?? null
3749
+ });
3750
+ } catch (err) {
3751
+ const message = err instanceof Error ? err.stack || err.message : String(err);
3752
+ hmr.broadcastError({ message: `Failed to transform ${effectiveUrlPath}: ${message}` });
3753
+ throw err;
3754
+ }
2597
3755
  const transformedCode = result.code;
2598
3756
  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);
3757
+ const withDefine = applyDefineReplacements(transformedCode, defineConfig);
3758
+ const envApplied = applyEnvPlaceholders(withDefine, ext);
3759
+ if (import_path15.default.extname(effectiveFsPath) === ".html") {
3760
+ ensureVendorPackFile();
3761
+ const withVendor = vendorPackUrl ? injectModulePreload(envApplied, vendorPackUrl) : envApplied;
3762
+ const injected = injectHMRClient(withVendor);
3763
+ const etag = `W/"html-${configHash}-${hash}"`;
3764
+ sendBuffer(req, res, 200, "text/html; charset=utf-8", Buffer.from(injected, "utf8"), {
3765
+ etag,
3766
+ cacheControl: "no-cache"
3767
+ });
2604
3768
  } else {
2605
3769
  const finalBuffer = Buffer.from(envApplied);
2606
- res.setHeader("Content-Type", guessContentType(effectiveFsPath));
2607
- res.end(finalBuffer);
3770
+ const etag = `W/"mod-${configHash}-${hash}"`;
3771
+ sendBuffer(req, res, 200, guessContentType(effectiveFsPath), finalBuffer, {
3772
+ etag,
3773
+ cacheControl: "no-cache"
3774
+ });
2608
3775
  }
2609
3776
  logInfo(`Served: ${effectiveUrlPath} deps:${depsAbs.length} ${changed ? "(updated)" : "(cached)"}`);
2610
3777
  if (cacheDebug) {
@@ -2619,7 +3786,16 @@ async function startDevServer({
2619
3786
  });
2620
3787
  watcher.on("change", (file, status) => {
2621
3788
  logInfo(`[Watcher] ${status}: ${file}`);
2622
- const affected = graph.collectAffected([file]);
3789
+ const ext = import_path15.default.extname(file).toLowerCase();
3790
+ const isReactFastRefreshBoundary = status !== "deleted" && (ext === ".tsx" || ext === ".jsx");
3791
+ const isCssBoundary = status !== "deleted" && ext === ".css";
3792
+ const collected = graph.collectAffected([file]);
3793
+ const affected = isReactFastRefreshBoundary || isCssBoundary ? [
3794
+ file,
3795
+ ...collected.filter(
3796
+ (absPath) => absPath !== file && import_path15.default.extname(absPath).toLowerCase() === ".css"
3797
+ )
3798
+ ] : collected;
2623
3799
  if (!affected.includes(file)) {
2624
3800
  affected.unshift(file);
2625
3801
  }
@@ -2630,7 +3806,7 @@ async function startDevServer({
2630
3806
  if (reason !== "deleted") {
2631
3807
  if (absPath === file) {
2632
3808
  try {
2633
- const code = import_fs9.default.readFileSync(absPath, "utf8");
3809
+ const code = import_fs11.default.readFileSync(absPath, "utf8");
2634
3810
  hash = getCacheKey(code);
2635
3811
  } catch {
2636
3812
  hash = graph.getNode(absPath)?.hash ?? null;
@@ -2641,7 +3817,7 @@ async function startDevServer({
2641
3817
  }
2642
3818
  modules.push({
2643
3819
  absPath,
2644
- url: normalizeUrlFromFs(rootDir, absPath),
3820
+ url: import_path15.default.extname(absPath).toLowerCase() === ".css" ? `${normalizeUrlFromFs(rootDir, absPath)}?inline` : isAssetExt(import_path15.default.extname(absPath).toLowerCase()) ? `${normalizeUrlFromFs(rootDir, absPath)}?import` : normalizeUrlFromFs(rootDir, absPath),
2645
3821
  hash,
2646
3822
  reason
2647
3823
  });
@@ -2729,13 +3905,98 @@ async function startDevServer({
2729
3905
  signalHandlers.push({ event: "SIGINT", handler: onSignal });
2730
3906
  signalHandlers.push({ event: "SIGTERM", handler: onSignal });
2731
3907
  }
2732
- await new Promise((resolve) => {
2733
- server.listen(port, () => resolve());
3908
+ await new Promise((resolve, reject) => {
3909
+ const onError = (err) => reject(err);
3910
+ server.once("error", onError);
3911
+ server.listen(port, host, () => {
3912
+ server.off("error", onError);
3913
+ resolve();
3914
+ });
2734
3915
  });
2735
3916
  const address = server.address();
2736
3917
  const actualPort = address && typeof address === "object" && address?.port ? address.port : port;
2737
3918
  logInfo(`Ionify Dev Server (Phase 2) at http://localhost:${actualPort}`);
3919
+ logInfo(`Ready in ${Date.now() - bootStartMs}ms`);
2738
3920
  logInfo(`HMR listening at /__ionify_hmr (SSE)`);
3921
+ if (vendorDeps.length > 0) {
3922
+ const vendorLabels = vendorDeps.map((d) => d.packageLabel).join(", ");
3923
+ logInfo(`[deps] Vendor pack detected (${vendorDeps.length}): ${vendorLabels}`);
3924
+ ensureVendorPackFile();
3925
+ const missing = vendorDeps.filter((d) => !import_fs11.default.existsSync(import_path15.default.join(depsRoot, d.fileName)));
3926
+ if (missing.length > 0) {
3927
+ const entryCount = missing.length;
3928
+ logInfo(`[deps] Pre-warming vendor deps (${entryCount}) in parallel...`);
3929
+ Promise.resolve().then(() => {
3930
+ if (native?.optimizeDependenciesBatch && !depsSourcemapEnabled && depsBundleEsmEnabled) {
3931
+ const results = native.optimizeDependenciesBatch(
3932
+ missing.map((d) => ({ entryPath: d.entryPath, depsHash }))
3933
+ );
3934
+ results.forEach((r, idx) => {
3935
+ const dep = missing[idx];
3936
+ if (r?.error) {
3937
+ logWarn2(`[deps] Vendor prewarm failed ${dep.packageLabel}: ${r.error}`);
3938
+ } else if (r?.out_path || r?.outPath) {
3939
+ const outPath = r.out_path ?? r.outPath;
3940
+ logInfo(
3941
+ `[deps] \u2713 Vendor prewarmed ${dep.packageLabel} \u2192 ${import_path15.default.basename(outPath)}`
3942
+ );
3943
+ }
3944
+ });
3945
+ return;
3946
+ }
3947
+ if (!native?.optimizeDependency) return;
3948
+ for (const dep of missing) {
3949
+ try {
3950
+ const result = native.optimizeDependency(
3951
+ dep.entryPath,
3952
+ depsHash,
3953
+ depsSourcemapEnabled,
3954
+ depsBundleEsmEnabled
3955
+ );
3956
+ const outPath = result?.out_path ?? result?.outPath ?? null;
3957
+ if (outPath) {
3958
+ logInfo(`[deps] \u2713 Vendor prewarmed ${dep.packageLabel} \u2192 ${import_path15.default.basename(outPath)}`);
3959
+ }
3960
+ } catch (err) {
3961
+ logWarn2(`[deps] Vendor prewarm failed ${dep.packageLabel}: ${String(err)}`);
3962
+ }
3963
+ }
3964
+ }).catch((err) => {
3965
+ logWarn2(`[deps] Vendor prewarm error: ${err}`);
3966
+ });
3967
+ }
3968
+ }
3969
+ if (userConfig?.optimizeDeps?.include && Array.isArray(userConfig.optimizeDeps.include)) {
3970
+ const includes = userConfig.optimizeDeps.include;
3971
+ if (includes.length > 0) {
3972
+ logInfo(`[deps] Pre-warming ${includes.length} dependencies: ${includes.join(", ")}`);
3973
+ Promise.all(
3974
+ includes.map(async (pkgName) => {
3975
+ try {
3976
+ if (!native?.resolveModule || !native?.optimizeDependency) {
3977
+ logWarn2(`[deps] Cannot pre-warm ${pkgName}: native functions not available`);
3978
+ return;
3979
+ }
3980
+ const resolved = native.resolveModule(pkgName, rootDir);
3981
+ if (!resolved || !resolved.fsPath && !resolved.fs_path) {
3982
+ logWarn2(`[deps] Cannot pre-warm ${pkgName}: resolution failed`);
3983
+ return;
3984
+ }
3985
+ const entryPath = resolved.fsPath || resolved.fs_path;
3986
+ const result = native.optimizeDependency(entryPath, depsHash, depsSourcemapEnabled, depsBundleEsmEnabled);
3987
+ if (result?.out_path) {
3988
+ const fileName = import_path15.default.basename(result.out_path);
3989
+ logInfo(`[deps] \u2713 Pre-warmed ${pkgName} \u2192 ${fileName}`);
3990
+ }
3991
+ } catch (err) {
3992
+ logWarn2(`[deps] Failed to pre-warm ${pkgName}: ${err}`);
3993
+ }
3994
+ })
3995
+ ).catch((err) => {
3996
+ logWarn2(`[deps] Pre-warming error: ${err}`);
3997
+ });
3998
+ }
3999
+ }
2739
4000
  return {
2740
4001
  server,
2741
4002
  port: actualPort,
@@ -2747,14 +4008,14 @@ async function startDevServer({
2747
4008
 
2748
4009
  // src/cli/commands/analyze.ts
2749
4010
  init_cjs_shims();
2750
- var import_fs10 = __toESM(require("fs"), 1);
2751
- var import_path13 = __toESM(require("path"), 1);
4011
+ var import_fs12 = __toESM(require("fs"), 1);
4012
+ var import_path16 = __toESM(require("path"), 1);
2752
4013
  init_native();
2753
4014
  function readGraphFromDisk(root) {
2754
- const file = import_path13.default.join(root, ".ionify", "graph.json");
2755
- if (!import_fs10.default.existsSync(file)) return null;
4015
+ const file = import_path16.default.join(root, ".ionify", "graph.json");
4016
+ if (!import_fs12.default.existsSync(file)) return null;
2756
4017
  try {
2757
- const raw = import_fs10.default.readFileSync(file, "utf8");
4018
+ const raw = import_fs12.default.readFileSync(file, "utf8");
2758
4019
  const snapshot = JSON.parse(raw);
2759
4020
  if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
2760
4021
  return Object.entries(snapshot.nodes).map(([id, node]) => ({
@@ -2851,8 +4112,8 @@ async function runAnalyzeCommand(options = {}) {
2851
4112
 
2852
4113
  // src/cli/commands/build.ts
2853
4114
  init_cjs_shims();
2854
- var import_fs12 = __toESM(require("fs"), 1);
2855
- var import_path15 = __toESM(require("path"), 1);
4115
+ var import_fs14 = __toESM(require("fs"), 1);
4116
+ var import_path18 = __toESM(require("path"), 1);
2856
4117
  init_native();
2857
4118
  init_cas();
2858
4119
 
@@ -2945,9 +4206,9 @@ function resolveOptimizationLevel(configLevel, options = {}) {
2945
4206
 
2946
4207
  // src/core/bundler.ts
2947
4208
  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);
4209
+ var import_fs13 = __toESM(require("fs"), 1);
4210
+ var import_path17 = __toESM(require("path"), 1);
4211
+ var import_crypto5 = __toESM(require("crypto"), 1);
2951
4212
  init_native();
2952
4213
  init_cache();
2953
4214
  function readGraphSnapshot() {
@@ -2964,13 +4225,13 @@ function readGraphSnapshot() {
2964
4225
  }));
2965
4226
  }
2966
4227
  } catch (err) {
2967
- logWarn(`Failed to load native graph: ${String(err)}`);
4228
+ logWarn2(`Failed to load native graph: ${String(err)}`);
2968
4229
  }
2969
4230
  }
2970
- const file = import_path14.default.join(process.cwd(), ".ionify", "graph.json");
2971
- if (!import_fs11.default.existsSync(file)) return [];
4231
+ const file = import_path17.default.join(process.cwd(), ".ionify", "graph.json");
4232
+ if (!import_fs13.default.existsSync(file)) return [];
2972
4233
  try {
2973
- const raw = import_fs11.default.readFileSync(file, "utf8");
4234
+ const raw = import_fs13.default.readFileSync(file, "utf8");
2974
4235
  const snapshot = JSON.parse(raw);
2975
4236
  if (snapshot?.version !== 1 || !snapshot?.nodes) return [];
2976
4237
  return Object.entries(snapshot.nodes).map(([id, node]) => ({
@@ -2979,20 +4240,20 @@ function readGraphSnapshot() {
2979
4240
  deps: Array.isArray(node.deps) ? node.deps : []
2980
4241
  }));
2981
4242
  } catch (err) {
2982
- logWarn(`Failed to read graph snapshot: ${String(err)}`);
4243
+ logWarn2(`Failed to read graph snapshot: ${String(err)}`);
2983
4244
  return [];
2984
4245
  }
2985
4246
  }
2986
4247
  var JS_EXTENSIONS2 = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
2987
4248
  var CSS_EXTENSIONS = /* @__PURE__ */ new Set([".css"]);
2988
4249
  function classifyModuleKind(id) {
2989
- const ext = import_path14.default.extname(id).toLowerCase();
4250
+ const ext = import_path17.default.extname(id).toLowerCase();
2990
4251
  if (CSS_EXTENSIONS.has(ext)) return "css";
2991
4252
  if (JS_EXTENSIONS2.has(ext)) return "js";
2992
4253
  return "asset";
2993
4254
  }
2994
4255
  var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
2995
- var toPosix = (p) => p.split(import_path14.default.sep).join("/");
4256
+ var toPosix = (p) => p.split(import_path17.default.sep).join("/");
2996
4257
  function minifyCss(input) {
2997
4258
  return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,])\s*/g, "$1").trim();
2998
4259
  }
@@ -3127,7 +4388,7 @@ function fallbackPlan(entries) {
3127
4388
  async function generateBuildPlan(entries, versionInputs) {
3128
4389
  const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
3129
4390
  logInfo(`Graph version: ${version || "default"}`);
3130
- const graphDbPath = import_path14.default.join(process.cwd(), ".ionify", "graph.db");
4391
+ const graphDbPath = import_path17.default.join(process.cwd(), ".ionify", "graph.db");
3131
4392
  ensureNativeGraph(graphDbPath, version);
3132
4393
  let moduleCount = 0;
3133
4394
  if (native?.graphLoadMap) {
@@ -3140,19 +4401,19 @@ async function generateBuildPlan(entries, versionInputs) {
3140
4401
  logInfo(`Loaded persisted graph with ${graphSize} modules`);
3141
4402
  }
3142
4403
  } catch (err) {
3143
- logWarn(`Failed to load persisted graph: ${String(err)}`);
4404
+ logWarn2(`Failed to load persisted graph: ${String(err)}`);
3144
4405
  }
3145
4406
  } else {
3146
- logWarn(`graphLoadMap not available, native binding: ${!!native}`);
4407
+ logWarn2(`graphLoadMap not available, native binding: ${!!native}`);
3147
4408
  }
3148
4409
  if (moduleCount === 0 && entries?.length && native) {
3149
- logWarn(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
4410
+ logWarn2(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
3150
4411
  const queue = [...entries];
3151
4412
  const seen = new Set(queue);
3152
4413
  while (queue.length) {
3153
4414
  const file = queue.shift();
3154
- if (!import_fs11.default.existsSync(file)) continue;
3155
- const code = import_fs11.default.readFileSync(file, "utf8");
4415
+ if (!import_fs13.default.existsSync(file)) continue;
4416
+ const code = import_fs13.default.readFileSync(file, "utf8");
3156
4417
  let hash = getCacheKey(code);
3157
4418
  let specs = [];
3158
4419
  if (native.parseModuleIr) {
@@ -3198,7 +4459,7 @@ async function generateBuildPlan(entries, versionInputs) {
3198
4459
  logInfo(`[Planner] Native plan returned: ${plan.entries.length} entries, ${plan.chunks.length} chunks in ${Date.now() - start}ms`);
3199
4460
  return normalizePlan(plan);
3200
4461
  } catch (err) {
3201
- logWarn(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
4462
+ logWarn2(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
3202
4463
  }
3203
4464
  }
3204
4465
  return fallbackPlan(entries);
@@ -3224,14 +4485,14 @@ async function writeBuildManifest(outputDir, plan, artifacts) {
3224
4485
  files: filesByChunk.get(chunk.id) ?? { js: [], css: [], assets: [] }
3225
4486
  }))
3226
4487
  };
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");
4488
+ const dir = import_path17.default.resolve(outputDir);
4489
+ await import_fs13.default.promises.mkdir(dir, { recursive: true });
4490
+ const file = import_path17.default.join(dir, "manifest.json");
4491
+ await import_fs13.default.promises.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
3231
4492
  }
3232
4493
  async function emitChunks(outputDir, plan, moduleOutputs, opts) {
3233
4494
  if (!native?.buildChunks) {
3234
- logWarn("Native buildChunks binding is not available; using JS fallback emitter.");
4495
+ logWarn2("Native buildChunks binding is not available; using JS fallback emitter.");
3235
4496
  const rawArtifacts2 = buildJsFallbackArtifacts(plan, moduleOutputs);
3236
4497
  return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts2);
3237
4498
  }
@@ -3254,7 +4515,7 @@ ${output.code}`);
3254
4515
  }
3255
4516
  for (const assetPath of chunk.assets) {
3256
4517
  try {
3257
- const data = import_fs11.default.readFileSync(assetPath);
4518
+ const data = import_fs13.default.readFileSync(assetPath);
3258
4519
  if (data.length < 4096) {
3259
4520
  const mime = "application/octet-stream";
3260
4521
  const inline = `data:${mime};base64,${data.toString("base64")}`;
@@ -3262,15 +4523,15 @@ ${output.code}`);
3262
4523
  export const __ionify_asset = "${inline}";`);
3263
4524
  continue;
3264
4525
  }
3265
- const hash = import_crypto4.default.createHash("sha256").update(data).digest("hex").slice(0, 16);
3266
- const ext = import_path14.default.extname(assetPath) || ".bin";
4526
+ const hash = import_crypto5.default.createHash("sha256").update(data).digest("hex").slice(0, 16);
4527
+ const ext = import_path17.default.extname(assetPath) || ".bin";
3267
4528
  const fileName = `assets/${hash}${ext}`;
3268
4529
  assets.push({
3269
4530
  source: assetPath,
3270
4531
  file_name: fileName
3271
4532
  });
3272
4533
  } catch {
3273
- const fileName = import_path14.default.basename(assetPath) || "asset";
4534
+ const fileName = import_path17.default.basename(assetPath) || "asset";
3274
4535
  assets.push({
3275
4536
  source: assetPath,
3276
4537
  file_name: fileName
@@ -3303,15 +4564,15 @@ function normalizeNativeArtifact(raw) {
3303
4564
  const map_bytes = typeof raw.map_bytes === "number" ? raw.map_bytes : map ? Buffer.byteLength(map, "utf8") : 0;
3304
4565
  const assets = Array.isArray(raw.assets) ? raw.assets.map((asset) => ({
3305
4566
  source: asset.source,
3306
- file_name: asset.file_name ?? asset.fileName ?? import_path14.default.basename(asset.source ?? "asset")
4567
+ file_name: asset.file_name ?? asset.fileName ?? import_path17.default.basename(asset.source ?? "asset")
3307
4568
  })) : [];
3308
4569
  return { id, file_name, code, map, assets, code_bytes, map_bytes };
3309
4570
  }
3310
4571
  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 });
4572
+ const chunkDir = import_path17.default.join(outputDir, "chunks");
4573
+ await import_fs13.default.promises.mkdir(chunkDir, { recursive: true });
4574
+ const assetsDir = import_path17.default.join(outputDir, "assets");
4575
+ await import_fs13.default.promises.mkdir(assetsDir, { recursive: true });
3315
4576
  const enableSourceMaps = process.env.IONIFY_SOURCEMAPS === "true";
3316
4577
  const grouped = /* @__PURE__ */ new Map();
3317
4578
  for (const raw of rawArtifacts) {
@@ -3328,8 +4589,8 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3328
4589
  if (!artifacts || !artifacts.length) {
3329
4590
  throw new Error(`Native bundler did not emit artifacts for ${chunk.id}`);
3330
4591
  }
3331
- const chunkOutDir = import_path14.default.join(chunkDir, chunk.id);
3332
- await import_fs11.default.promises.mkdir(chunkOutDir, { recursive: true });
4592
+ const chunkOutDir = import_path17.default.join(chunkDir, chunk.id);
4593
+ await import_fs13.default.promises.mkdir(chunkOutDir, { recursive: true });
3333
4594
  artifacts.sort((a, b) => {
3334
4595
  if (a.id === chunk.id) return -1;
3335
4596
  if (b.id === chunk.id) return 1;
@@ -3342,14 +4603,14 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3342
4603
  const copyAssets = async (assets) => {
3343
4604
  for (const asset of assets) {
3344
4605
  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);
4606
+ const relName = asset.file_name ?? import_path17.default.basename(asset.source);
4607
+ const assetFile = import_path17.default.join(outputDir, relName);
3347
4608
  if (assetWritten.has(assetFile)) continue;
3348
4609
  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));
4610
+ const data = await import_fs13.default.promises.readFile(asset.source);
4611
+ await import_fs13.default.promises.mkdir(import_path17.default.dirname(assetFile), { recursive: true });
4612
+ await import_fs13.default.promises.writeFile(assetFile, data);
4613
+ const rel = toPosix(import_path17.default.relative(outputDir, assetFile));
3353
4614
  buildStats[rel] = {
3354
4615
  bytes: data.length,
3355
4616
  emitter: "native",
@@ -3358,7 +4619,7 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3358
4619
  assetFiles.push(rel);
3359
4620
  assetWritten.add(assetFile);
3360
4621
  } catch (err) {
3361
- logWarn(`Failed to emit asset ${asset.source}: ${String(err)}`);
4622
+ logWarn2(`Failed to emit asset ${asset.source}: ${String(err)}`);
3362
4623
  }
3363
4624
  }
3364
4625
  };
@@ -3369,11 +4630,11 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3369
4630
  const cssPieces = [];
3370
4631
  for (const cssPath of cssOrder) {
3371
4632
  let cssSource = moduleOutputs.get(cssPath)?.code;
3372
- if (!cssSource && import_fs11.default.existsSync(cssPath)) {
4633
+ if (!cssSource && import_fs13.default.existsSync(cssPath)) {
3373
4634
  try {
3374
- cssSource = await import_fs11.default.promises.readFile(cssPath, "utf8");
4635
+ cssSource = await import_fs13.default.promises.readFile(cssPath, "utf8");
3375
4636
  } catch (err) {
3376
- logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
4637
+ logWarn2(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3377
4638
  }
3378
4639
  }
3379
4640
  if (!cssSource) continue;
@@ -3388,9 +4649,9 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3388
4649
  const combinedCss = cssPieces.join("\n");
3389
4650
  const cssHash = getCacheKey(combinedCss).slice(0, 8);
3390
4651
  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));
4652
+ const cssFilePath = import_path17.default.join(outputDir, cssFileName);
4653
+ await import_fs13.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
4654
+ cssFileRel = toPosix(import_path17.default.relative(outputDir, cssFilePath));
3394
4655
  buildStats[cssFileRel] = {
3395
4656
  bytes: Buffer.byteLength(combinedCss),
3396
4657
  emitter: "native",
@@ -3400,11 +4661,11 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3400
4661
  }
3401
4662
  }
3402
4663
  for (const artifact of artifacts) {
3403
- const nativeFile = import_path14.default.join(chunkOutDir, artifact.file_name);
4664
+ const nativeFile = import_path17.default.join(chunkOutDir, artifact.file_name);
3404
4665
  let nativeCode = artifact.code;
3405
4666
  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));
4667
+ const absCss = import_path17.default.join(outputDir, cssFileRel);
4668
+ const relCss = toPosix(import_path17.default.relative(import_path17.default.dirname(nativeFile), absCss));
3408
4669
  const inject = `(()=>{const url=new URL(${JSON.stringify(
3409
4670
  relCss
3410
4671
  )},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);}})();`;
@@ -3413,10 +4674,10 @@ ${nativeCode}`;
3413
4674
  }
3414
4675
  if (enableSourceMaps && artifact.map) {
3415
4676
  const mapFile = `${nativeFile}.map`;
3416
- await import_fs11.default.promises.writeFile(mapFile, artifact.map, "utf8");
4677
+ await import_fs13.default.promises.writeFile(mapFile, artifact.map, "utf8");
3417
4678
  nativeCode = `${nativeCode}
3418
- //# sourceMappingURL=${import_path14.default.basename(mapFile)}`;
3419
- const relMap = toPosix(import_path14.default.relative(outputDir, mapFile));
4679
+ //# sourceMappingURL=${import_path17.default.basename(mapFile)}`;
4680
+ const relMap = toPosix(import_path17.default.relative(outputDir, mapFile));
3420
4681
  buildStats[relMap] = {
3421
4682
  bytes: artifact.map_bytes,
3422
4683
  emitter: "native",
@@ -3424,8 +4685,8 @@ ${nativeCode}`;
3424
4685
  };
3425
4686
  jsFiles.push(relMap);
3426
4687
  }
3427
- await import_fs11.default.promises.writeFile(nativeFile, nativeCode, "utf8");
3428
- const relNative = toPosix(import_path14.default.relative(outputDir, nativeFile));
4688
+ await import_fs13.default.promises.writeFile(nativeFile, nativeCode, "utf8");
4689
+ const relNative = toPosix(import_path17.default.relative(outputDir, nativeFile));
3429
4690
  buildStats[relNative] = {
3430
4691
  bytes: artifact.code_bytes,
3431
4692
  emitter: "native",
@@ -3442,21 +4703,21 @@ ${nativeCode}`;
3442
4703
  const output = moduleOutputs.get(cssPath);
3443
4704
  if (output?.type === "css") {
3444
4705
  cssSources.push(output.code);
3445
- } else if (import_fs11.default.existsSync(cssPath)) {
4706
+ } else if (import_fs13.default.existsSync(cssPath)) {
3446
4707
  try {
3447
- cssSources.push(await import_fs11.default.promises.readFile(cssPath, "utf8"));
4708
+ cssSources.push(await import_fs13.default.promises.readFile(cssPath, "utf8"));
3448
4709
  } catch (err) {
3449
- logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
4710
+ logWarn2(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3450
4711
  }
3451
4712
  }
3452
4713
  }
3453
4714
  if (cssSources.length) {
3454
4715
  const combinedCss = cssSources.join("\n\n");
3455
- const cssHash = import_crypto4.default.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
4716
+ const cssHash = import_crypto5.default.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
3456
4717
  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);
4718
+ const cssFilePath = import_path17.default.join(chunkOutDir, cssFileName);
4719
+ await import_fs13.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
4720
+ const relCss = import_path17.default.relative(outputDir, cssFilePath);
3460
4721
  buildStats[relCss] = {
3461
4722
  bytes: Buffer.byteLength(combinedCss),
3462
4723
  emitter: "native",
@@ -3477,14 +4738,14 @@ ${nativeCode}`;
3477
4738
  return { artifacts: results, stats: buildStats };
3478
4739
  }
3479
4740
  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");
4741
+ const dir = import_path17.default.resolve(outputDir);
4742
+ await import_fs13.default.promises.mkdir(dir, { recursive: true });
4743
+ const file = import_path17.default.join(dir, "manifest.assets.json");
3483
4744
  const payload = {
3484
4745
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3485
4746
  chunks: artifacts
3486
4747
  };
3487
- await import_fs11.default.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
4748
+ await import_fs13.default.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
3488
4749
  }
3489
4750
 
3490
4751
  // src/core/worker/pool.ts
@@ -3527,7 +4788,7 @@ var TransformWorkerPool = class {
3527
4788
  this.dequeue(worker);
3528
4789
  });
3529
4790
  worker.on("error", (err) => {
3530
- logWarn(`Transform worker error: ${String(err)}`);
4791
+ logWarn2(`Transform worker error: ${String(err)}`);
3531
4792
  const item = this.active.get(id);
3532
4793
  if (item) {
3533
4794
  this.active.delete(id);
@@ -3542,7 +4803,7 @@ var TransformWorkerPool = class {
3542
4803
  this.queue.unshift(item);
3543
4804
  }
3544
4805
  if (!this.closed && code !== 0) {
3545
- logWarn(`Transform worker exited unexpectedly (${code}), respawning`);
4806
+ logWarn2(`Transform worker exited unexpectedly (${code}), respawning`);
3546
4807
  this.spawnWorker();
3547
4808
  }
3548
4809
  });
@@ -3618,6 +4879,7 @@ init_cache();
3618
4879
  async function runBuildCommand(options = {}) {
3619
4880
  try {
3620
4881
  const config = await loadIonifyConfig();
4882
+ const rootDir = config?.root || process.cwd();
3621
4883
  const optLevel = resolveOptimizationLevel(config?.optimizationLevel, {
3622
4884
  cliLevel: options.level,
3623
4885
  envLevel: process.env.IONIFY_OPTIMIZATION_LEVEL
@@ -3646,11 +4908,10 @@ async function runBuildCommand(options = {}) {
3646
4908
  combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
3647
4909
  });
3648
4910
  }
3649
- applyMinifierEnv(minifier);
3650
4911
  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;
4912
+ const entries = config?.entry ? (Array.isArray(config.entry) ? config.entry : [config.entry]).map(
4913
+ (entry) => entry.startsWith("/") ? import_path18.default.join(rootDir, entry) : import_path18.default.resolve(rootDir, entry)
4914
+ ) : void 0;
3654
4915
  if (entries) {
3655
4916
  logInfo(`Build entries: ${entries.join(", ")}`);
3656
4917
  } else {
@@ -3692,11 +4953,11 @@ async function runBuildCommand(options = {}) {
3692
4953
  const moduleOutputs = /* @__PURE__ */ new Map();
3693
4954
  const pool = new TransformWorkerPool();
3694
4955
  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");
4956
+ const jobs = Array.from(uniqueModules).filter((filePath) => import_fs14.default.existsSync(filePath)).map((filePath) => {
4957
+ const code = import_fs14.default.readFileSync(filePath, "utf8");
3697
4958
  const sourceHash = getCacheKey(code);
3698
4959
  const moduleHash = moduleHashes.get(filePath) ?? sourceHash;
3699
- const cacheKey = getCacheKey(`build-worker:v1:${import_path15.default.extname(filePath)}:${moduleHash}:${filePath}`);
4960
+ const cacheKey = getCacheKey(`build-worker:v1:${import_path18.default.extname(filePath)}:${moduleHash}:${filePath}`);
3700
4961
  const cached = readCache(cacheKey);
3701
4962
  if (cached) {
3702
4963
  try {
@@ -3704,13 +4965,13 @@ async function runBuildCommand(options = {}) {
3704
4965
  if (parsed?.code) {
3705
4966
  const transformedHash = getCacheKey(parsed.code);
3706
4967
  moduleHashes.set(filePath, transformedHash);
3707
- const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
4968
+ const casRoot2 = import_path18.default.join(rootDir, ".ionify", "cas");
3708
4969
  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");
4970
+ if (!import_fs14.default.existsSync(import_path18.default.join(cacheDir, "transformed.js"))) {
4971
+ import_fs14.default.mkdirSync(cacheDir, { recursive: true });
4972
+ import_fs14.default.writeFileSync(import_path18.default.join(cacheDir, "transformed.js"), parsed.code, "utf8");
3712
4973
  if (parsed.map) {
3713
- import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
4974
+ import_fs14.default.writeFileSync(import_path18.default.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
3714
4975
  }
3715
4976
  }
3716
4977
  moduleOutputs.set(filePath, { code: parsed.code, type: parsed.type ?? "js" });
@@ -3722,7 +4983,7 @@ async function runBuildCommand(options = {}) {
3722
4983
  return {
3723
4984
  id: filePath,
3724
4985
  filePath,
3725
- ext: import_path15.default.extname(filePath),
4986
+ ext: import_path18.default.extname(filePath),
3726
4987
  code,
3727
4988
  cacheKey
3728
4989
  };
@@ -3745,13 +5006,13 @@ async function runBuildCommand(options = {}) {
3745
5006
  writeCache(job.cacheKey, Buffer.from(payload));
3746
5007
  const transformedHash = getCacheKey(result.code);
3747
5008
  const moduleHash = transformedHash;
3748
- const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
5009
+ const casRoot2 = import_path18.default.join(rootDir, ".ionify", "cas");
3749
5010
  const versionHash = configHash;
3750
5011
  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");
5012
+ import_fs14.default.mkdirSync(cacheDir, { recursive: true });
5013
+ import_fs14.default.writeFileSync(import_path18.default.join(cacheDir, "transformed.js"), result.code, "utf8");
3753
5014
  if (result.map) {
3754
- import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), result.map, "utf8");
5015
+ import_fs14.default.writeFileSync(import_path18.default.join(cacheDir, "transformed.js.map"), result.map, "utf8");
3755
5016
  }
3756
5017
  moduleHashes.set(job.filePath, moduleHash);
3757
5018
  for (const chunk of plan.chunks) {
@@ -3774,20 +5035,20 @@ async function runBuildCommand(options = {}) {
3774
5035
  }
3775
5036
  }
3776
5037
  }
3777
- const absOutDir = import_path15.default.resolve(outDir);
3778
- const casRoot = import_path15.default.join(process.cwd(), ".ionify", "cas");
5038
+ const absOutDir = import_path18.default.resolve(outDir);
5039
+ const casRoot = import_path18.default.join(rootDir, ".ionify", "cas");
3779
5040
  const { artifacts, stats } = await emitChunks(absOutDir, plan, moduleOutputs, {
3780
5041
  casRoot,
3781
5042
  versionHash: configHash
3782
5043
  });
3783
5044
  await writeBuildManifest(absOutDir, plan, artifacts);
3784
5045
  await writeAssetsManifest(absOutDir, artifacts);
3785
- await import_fs12.default.promises.writeFile(
3786
- import_path15.default.join(absOutDir, "build.stats.json"),
5046
+ await import_fs14.default.promises.writeFile(
5047
+ import_path18.default.join(absOutDir, "build.stats.json"),
3787
5048
  JSON.stringify(stats, null, 2),
3788
5049
  "utf8"
3789
5050
  );
3790
- logInfo(`Build plan generated \u2192 ${import_path15.default.join(absOutDir, "manifest.json")}`);
5051
+ logInfo(`Build plan generated \u2192 ${import_path18.default.join(absOutDir, "manifest.json")}`);
3791
5052
  logInfo(`Entries: ${plan.entries.length}, Chunks: ${plan.chunks.length}`);
3792
5053
  logInfo(`Modules transformed: ${moduleOutputs.size}`);
3793
5054
  } catch (err) {