@cordy/electro-cli 1.2.7 → 1.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +316 -52
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ import { build, createLogger, createServer, mergeConfig, version } from "vite";
8
8
  import { existsSync } from "node:fs";
9
9
  import { tmpdir } from "node:os";
10
10
  import { spawn } from "node:child_process";
11
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
11
12
 
12
13
  //#region ../../node_modules/.bun/cac@6.7.14/node_modules/cac/dist/index.mjs
13
14
  function toArr(any) {
@@ -490,7 +491,7 @@ const cac = (name = "") => new CAC(name);
490
491
 
491
492
  //#endregion
492
493
  //#region package.json
493
- var version$1 = "1.2.7";
494
+ var version$1 = "1.2.10";
494
495
 
495
496
  //#endregion
496
497
  //#region src/dev/logger.ts
@@ -752,7 +753,7 @@ function validateMergedNodeConfig(config, scope) {
752
753
  const output = rolldownOpts.output;
753
754
  if (output && typeof output === "object" && !Array.isArray(output)) {
754
755
  const fmt = output.format;
755
- if (fmt && fmt !== "es") throw new Error(`[electro] ${scope}: invalid output format "${fmt}". Electro is ESM-only (format: "es"). Remove the format override from your config.`);
756
+ if (fmt && fmt !== "es" && fmt !== "cjs") throw new Error(`[electro] ${scope}: invalid output format "${fmt}". Electro supports "es" and "cjs" for Node scopes. Update build.rolldownOptions.output.format in your config.`);
756
757
  }
757
758
  }
758
759
  if (config.ssr === false || config.build?.ssr === false) throw new Error(`[electro] ${scope}: SSR cannot be disabled in Node scopes. SSR mode is required for correct Node.js module resolution.`);
@@ -766,11 +767,6 @@ function enforceMergedNodeConfig(config, _scope) {
766
767
  const build = config.build;
767
768
  if (!build) return;
768
769
  if (build.target !== "esnext") build.target = "esnext";
769
- const rolldownOpts = build.rolldownOptions;
770
- if (rolldownOpts) {
771
- const output = rolldownOpts.output;
772
- if (output && output.format !== "es") output.format = "es";
773
- }
774
770
  if (!build.ssr) build.ssr = true;
775
771
  }
776
772
  /** Validate that installed Vite version is within the supported range. */
@@ -805,15 +801,101 @@ async function loadConfig(configPath) {
805
801
  /**
806
802
  * Resolve externals for Node scope builds (main/preload).
807
803
  *
808
- * Only externalizes electron and Node builtins.
809
- * All npm dependencies are bundled by Vite (handles CJS→ESM conversion).
804
+ * Auto-externalizes: electron, Node builtins (bare + node: prefixed),
805
+ * package.json dependencies + optionalDependencies, and deep imports (pkg/subpath).
810
806
  */
811
- function resolveExternals() {
812
- return [
807
+ async function resolveExternals(root) {
808
+ const pkgPath = resolve(root, "package.json");
809
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
810
+ const deps = new Set([
813
811
  "electron",
814
- /^electron\/.+/,
815
- ...builtinModules.flatMap((m) => [m, `node:${m}`])
816
- ];
812
+ ...Object.keys(pkg.dependencies ?? {}),
813
+ ...Object.keys(pkg.optionalDependencies ?? {})
814
+ ]);
815
+ deps.delete("@cordy/electro");
816
+ const builtins = builtinModules.flatMap((m) => [m, `node:${m}`]);
817
+ const depsArray = [...deps];
818
+ const cjsInteropDeps = (await Promise.all(depsArray.map(async (dep) => ({
819
+ dep,
820
+ isCommonJs: await isLikelyCommonJsDependency(root, dep)
821
+ })))).filter((entry) => entry.isCommonJs).map((entry) => entry.dep);
822
+ const deepPattern = depsArray.length > 0 ? new RegExp(`^(${depsArray.map(escapeRegExp).join("|")})/.+`) : null;
823
+ return {
824
+ externals: deepPattern ? [
825
+ ...depsArray,
826
+ ...builtins,
827
+ deepPattern
828
+ ] : [...depsArray, ...builtins],
829
+ cjsInteropDeps
830
+ };
831
+ }
832
+ function escapeRegExp(str) {
833
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
834
+ }
835
+ async function isLikelyCommonJsDependency(root, dep) {
836
+ if (dep === "electron" || dep.startsWith("node:")) return false;
837
+ const depPkgPath = resolve(root, "node_modules", dep, "package.json");
838
+ try {
839
+ return !isLikelyEsmPackage(JSON.parse(await readFile(depPkgPath, "utf-8")));
840
+ } catch {
841
+ return false;
842
+ }
843
+ }
844
+ function isLikelyEsmPackage(pkg) {
845
+ if (pkg.type === "module") return true;
846
+ if (typeof pkg.module === "string") return true;
847
+ if (typeof pkg.main === "string" && pkg.main.endsWith(".mjs")) return true;
848
+ if (typeof pkg.exports === "string" && pkg.exports.endsWith(".mjs")) return true;
849
+ if (hasImportCondition(pkg.exports)) return true;
850
+ return false;
851
+ }
852
+ function hasImportCondition(value) {
853
+ if (!value || typeof value !== "object") return false;
854
+ if (Array.isArray(value)) return value.some(hasImportCondition);
855
+ const record = value;
856
+ if ("import" in record) return true;
857
+ for (const nested of Object.values(record)) if (hasImportCondition(nested)) return true;
858
+ return false;
859
+ }
860
+
861
+ //#endregion
862
+ //#region src/dev/node-format.ts
863
+ /**
864
+ * Mirrors Node package behavior:
865
+ * - package.json "type": "module" => ESM output
866
+ * - otherwise => CJS output
867
+ */
868
+ async function resolveNodeOutputFormat(root) {
869
+ const pkgPath = resolve(root, "package.json");
870
+ try {
871
+ const raw = await readFile(pkgPath, "utf-8");
872
+ return JSON.parse(raw).type === "module" ? "es" : "cjs";
873
+ } catch {
874
+ return "cjs";
875
+ }
876
+ }
877
+ const MAIN_ENTRY_CANDIDATES = [
878
+ "index.mjs",
879
+ "index.cjs",
880
+ "index.js"
881
+ ];
882
+ /**
883
+ * Find built main entry regardless of selected output format.
884
+ */
885
+ async function resolveMainEntryPath(mainOutDir) {
886
+ for (const candidate of MAIN_ENTRY_CANDIDATES) {
887
+ const fullPath = resolve(mainOutDir, candidate);
888
+ if (await pathExists(fullPath)) return fullPath;
889
+ }
890
+ throw new Error(`Main entry not found in ${mainOutDir}. Expected one of: ${MAIN_ENTRY_CANDIDATES.join(", ")}`);
891
+ }
892
+ async function pathExists(filePath) {
893
+ try {
894
+ await access(filePath);
895
+ return true;
896
+ } catch {
897
+ return false;
898
+ }
817
899
  }
818
900
 
819
901
  //#endregion
@@ -1829,30 +1911,117 @@ var MagicString = class MagicString {
1829
1911
  };
1830
1912
 
1831
1913
  //#endregion
1832
- //#region src/plugins/esm-shim.ts
1914
+ //#region src/plugins/cjs-external-interop.ts
1915
+ const STATIC_IMPORT_RE = /(?<=\s|^|;)import\s*([\s"']*(?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu;
1916
+ const IDENT_RE = /^[A-Za-z_$][\w$]*$/;
1833
1917
  /**
1834
- * ESM Shim Plugin injects CommonJS shims into ESM output.
1835
- *
1836
- * When bundling CJS dependencies into ESM format, the output may contain
1837
- * references to `__filename`, `__dirname`, or `require()` which don't exist
1838
- * in ES modules. This plugin detects those references and injects shims.
1918
+ * Rewrites named imports from external CommonJS deps to runtime-safe accessors.
1839
1919
  *
1840
- * Based on electron-vite's esmShimPlugin (from unbuild).
1920
+ * Example:
1921
+ * import { autoUpdater } from "electron-updater"
1922
+ * becomes
1923
+ * import * as __cjs_ext_0__ from "electron-updater"
1924
+ * const autoUpdater = __cjs_ext_0__.autoUpdater ?? __cjs_ext_0__.default?.autoUpdater
1841
1925
  */
1842
- const CJSyntaxRe = /__filename|__dirname|require\(|require\.resolve\(/;
1843
- const CJSShim = `
1926
+ function cjsExternalInteropPlugin(cjsDeps) {
1927
+ if (cjsDeps.length === 0) return {
1928
+ name: "electro:cjs-external-interop",
1929
+ apply: "build"
1930
+ };
1931
+ const cjsDepSet = new Set(cjsDeps);
1932
+ return {
1933
+ name: "electro:cjs-external-interop",
1934
+ apply: "build",
1935
+ enforce: "post",
1936
+ renderChunk(code, _chunk, { format, sourcemap }) {
1937
+ if (format !== "es") return null;
1938
+ let s = null;
1939
+ let counter = 0;
1940
+ for (const match of code.matchAll(STATIC_IMPORT_RE)) {
1941
+ const statement = match[0];
1942
+ const start = match.index ?? 0;
1943
+ const specifier = match.groups?.specifier?.trim();
1944
+ const importsClause = match.groups?.imports?.trim();
1945
+ if (!specifier || !importsClause) continue;
1946
+ if (!isCjsExternalSpecifier(specifier, cjsDepSet)) continue;
1947
+ const parsed = parseImportClause(importsClause);
1948
+ if (!parsed) continue;
1949
+ const ns = `__cjs_ext_${counter++}__`;
1950
+ const lines = [`import * as ${ns} from ${JSON.stringify(specifier)};`];
1951
+ if (parsed.defaultImport) lines.push(`const ${parsed.defaultImport} = ${ns}.default ?? ${ns};`);
1952
+ for (const { imported, local } of parsed.named) lines.push(`const ${local} = ${ns}.${imported} ?? ${ns}.default?.${imported};`);
1953
+ s ??= new MagicString(code);
1954
+ s.overwrite(start, start + statement.length, lines.join("\n"));
1955
+ }
1956
+ if (!s) return null;
1957
+ return {
1958
+ code: s.toString(),
1959
+ map: sourcemap ? s.generateMap({ hires: "boundary" }) : null
1960
+ };
1961
+ }
1962
+ };
1963
+ }
1964
+ function isCjsExternalSpecifier(specifier, cjsDepSet) {
1965
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("\0") || specifier.startsWith("node:")) return false;
1966
+ if (cjsDepSet.has(specifier)) return true;
1967
+ for (const dep of cjsDepSet) if (specifier.startsWith(`${dep}/`)) return true;
1968
+ return false;
1969
+ }
1970
+ function parseImportClause(clause) {
1971
+ const braceStart = clause.indexOf("{");
1972
+ const braceEnd = clause.lastIndexOf("}");
1973
+ if (braceStart < 0 || braceEnd < braceStart) return null;
1974
+ const defaultPart = clause.slice(0, braceStart).trim().replace(/,$/, "").trim();
1975
+ if (defaultPart.startsWith("*")) return null;
1976
+ let defaultImport;
1977
+ if (defaultPart.length > 0) {
1978
+ const normalized = defaultPart.replace(/^type\s+/, "").trim();
1979
+ if (!IDENT_RE.test(normalized)) return null;
1980
+ defaultImport = normalized;
1981
+ }
1982
+ const namedPart = clause.slice(braceStart + 1, braceEnd).trim();
1983
+ if (namedPart.length === 0) return null;
1984
+ const named = [];
1985
+ for (const raw of namedPart.split(",")) {
1986
+ const token = raw.trim();
1987
+ if (token.length === 0) continue;
1988
+ const normalized = token.replace(/^type\s+/, "").trim();
1989
+ const match = /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/.exec(normalized);
1990
+ if (!match) return null;
1991
+ named.push({
1992
+ imported: match[1],
1993
+ local: match[2] ?? match[1]
1994
+ });
1995
+ }
1996
+ if (named.length === 0) return null;
1997
+ return {
1998
+ defaultImport,
1999
+ named
2000
+ };
2001
+ }
2002
+
2003
+ //#endregion
2004
+ //#region src/plugins/esm-shim.ts
2005
+ const CJSYNTAX_RE = /__filename|__dirname|require\(|require\.resolve\(/;
2006
+ const CJS_SHIM = `
1844
2007
  // -- CommonJS Shims --
1845
- import __cjs_mod__ from 'node:module';
1846
- const __filename = import.meta.filename;
1847
- const __dirname = import.meta.dirname;
2008
+ import __cjs_url__ from "node:url";
2009
+ import __cjs_path__ from "node:path";
2010
+ import __cjs_mod__ from "node:module";
2011
+ const __filename = __cjs_url__.fileURLToPath(import.meta.url);
2012
+ const __dirname = __cjs_path__.dirname(__filename);
1848
2013
  const require = __cjs_mod__.createRequire(import.meta.url);
1849
2014
  `;
1850
- const ESMStaticImportRe = /(?<=\s|^|;)import\s*([\s"']*(?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu;
2015
+ const ESM_STATIC_IMPORT_RE = /(?<=\s|^|;)import\s*([\s"']*(?<imports>[\p{L}\p{M}\w\t\n\r $*,/{}@.]+)from\s*)?["']\s*(?<specifier>(?<="\s*)[^"]*[^\s"](?=\s*")|(?<='\s*)[^']*[^\s'](?=\s*'))\s*["'][\s;]*/gmu;
1851
2016
  function findStaticImports(code) {
1852
2017
  const matches = [];
1853
- for (const match of code.matchAll(ESMStaticImportRe)) matches.push({ end: (match.index || 0) + match[0].length });
2018
+ for (const match of code.matchAll(ESM_STATIC_IMPORT_RE)) matches.push({ end: (match.index || 0) + match[0].length });
1854
2019
  return matches;
1855
2020
  }
2021
+ /**
2022
+ * Inject CommonJS shims into ESM output when bundled code contains
2023
+ * require/__dirname/__filename references.
2024
+ */
1856
2025
  function esmShimPlugin() {
1857
2026
  return {
1858
2027
  name: "electro:esm-shim",
@@ -1860,11 +2029,11 @@ function esmShimPlugin() {
1860
2029
  enforce: "post",
1861
2030
  renderChunk(code, _chunk, { format, sourcemap }) {
1862
2031
  if (format !== "es") return null;
1863
- if (code.includes(CJSShim) || !CJSyntaxRe.test(code)) return null;
1864
- const lastESMImport = findStaticImports(code).pop();
1865
- const indexToAppend = lastESMImport ? lastESMImport.end : 0;
2032
+ if (code.includes(CJS_SHIM) || !CJSYNTAX_RE.test(code)) return null;
2033
+ const lastImport = findStaticImports(code).pop();
2034
+ const indexToAppend = lastImport ? lastImport.end : 0;
1866
2035
  const s = new MagicString(code);
1867
- s.appendRight(indexToAppend, CJSShim);
2036
+ s.appendRight(indexToAppend, CJS_SHIM);
1868
2037
  return {
1869
2038
  code: s.toString(),
1870
2039
  map: sourcemap ? s.generateMap({ hires: "boundary" }) : null
@@ -1873,6 +2042,26 @@ function esmShimPlugin() {
1873
2042
  };
1874
2043
  }
1875
2044
 
2045
+ //#endregion
2046
+ //#region src/plugins/import-meta.ts
2047
+ /**
2048
+ * Rewrites import.meta.* expressions for CommonJS output format.
2049
+ */
2050
+ function importMetaPlugin() {
2051
+ return {
2052
+ name: "electro:import-meta",
2053
+ apply: "build",
2054
+ enforce: "pre",
2055
+ resolveImportMeta(property, { format }) {
2056
+ if (format !== "cjs") return null;
2057
+ if (property === "url") return `require("node:url").pathToFileURL(__filename).href`;
2058
+ if (property === "filename") return `__filename`;
2059
+ if (property === "dirname") return `__dirname`;
2060
+ return null;
2061
+ }
2062
+ };
2063
+ }
2064
+
1876
2065
  //#endregion
1877
2066
  //#region src/dev/vite-node-config.ts
1878
2067
  function resolveSourcemap$1(mode) {
@@ -1882,16 +2071,24 @@ function resolveSourcemap$1(mode) {
1882
2071
  return true;
1883
2072
  }
1884
2073
  function createNodeConfig(opts) {
2074
+ const format = opts.format ?? "es";
2075
+ const moduleCondition = format === "cjs" ? "require" : "import";
1885
2076
  const resolveConditions = opts.scope === "preload" ? [
1886
2077
  "node",
1887
- "import",
2078
+ moduleCondition,
1888
2079
  "default"
1889
- ] : ["node", "import"];
2080
+ ] : ["node", moduleCondition];
1890
2081
  const envPrefix = opts.scope === "main" ? ["MAIN_VITE_", "VITE_"] : ["PRELOAD_VITE_", "VITE_"];
2082
+ const entryExt = format === "cjs" ? "cjs" : "mjs";
1891
2083
  const config = {
1892
2084
  configFile: false,
1893
2085
  root: opts.root,
1894
- plugins: [esmShimPlugin(), ...opts.plugins ?? []],
2086
+ plugins: [
2087
+ importMetaPlugin(),
2088
+ ...opts.plugins ?? [],
2089
+ cjsExternalInteropPlugin(opts.cjsInteropDeps ?? []),
2090
+ esmShimPlugin()
2091
+ ],
1895
2092
  customLogger: opts.customLogger,
1896
2093
  envPrefix,
1897
2094
  define: {
@@ -1905,8 +2102,8 @@ function createNodeConfig(opts) {
1905
2102
  emptyOutDir: true,
1906
2103
  rolldownOptions: {
1907
2104
  output: {
1908
- format: "es",
1909
- entryFileNames: "index.mjs"
2105
+ format,
2106
+ entryFileNames: `index.${entryExt}`
1910
2107
  },
1911
2108
  external: opts.externals
1912
2109
  },
@@ -1919,7 +2116,7 @@ function createNodeConfig(opts) {
1919
2116
  },
1920
2117
  ssr: {
1921
2118
  target: "node",
1922
- noExternal: true
2119
+ noExternal: ["@cordy/electro"]
1923
2120
  },
1924
2121
  resolve: { conditions: resolveConditions },
1925
2122
  logLevel: opts.logLevel ?? "warn",
@@ -2689,6 +2886,7 @@ async function build$1(options) {
2689
2886
  const root = loaded.root;
2690
2887
  const outDir = resolve(root, options.outDir);
2691
2888
  const codegenDir = resolve(root, ".electro");
2889
+ const nodeFormat = await resolveNodeOutputFormat(root);
2692
2890
  const views = config.views ?? [];
2693
2891
  const rendererViews = views.filter((v) => v.entry);
2694
2892
  const srcDir = resolve(root, "src");
@@ -2725,7 +2923,9 @@ async function build$1(options) {
2725
2923
  stepFail("codegen", err instanceof Error ? err.message : String(err));
2726
2924
  process.exit(1);
2727
2925
  }
2728
- const externals = resolveExternals();
2926
+ const resolvedExternals = await resolveExternals(root);
2927
+ const externals = resolvedExternals.externals;
2928
+ const cjsInteropDeps = resolvedExternals.cjsInteropDeps;
2729
2929
  const logger = createBuildLogger();
2730
2930
  try {
2731
2931
  buildScope("main");
@@ -2736,7 +2936,9 @@ async function build$1(options) {
2736
2936
  externals,
2737
2937
  sourcemap: options.sourcemap,
2738
2938
  logger,
2739
- bytecode: options.bytecode
2939
+ bytecode: options.bytecode,
2940
+ format: nodeFormat,
2941
+ cjsInteropDeps
2740
2942
  });
2741
2943
  } catch (err) {
2742
2944
  stepFail("main", err instanceof Error ? err.message : String(err));
@@ -2752,7 +2954,9 @@ async function build$1(options) {
2752
2954
  externals,
2753
2955
  sourcemap: options.sourcemap,
2754
2956
  logger,
2755
- bytecode: options.bytecode
2957
+ bytecode: options.bytecode,
2958
+ format: nodeFormat,
2959
+ cjsInteropDeps
2756
2960
  });
2757
2961
  } catch (err) {
2758
2962
  stepFail("preload", err instanceof Error ? err.message : String(err));
@@ -2804,6 +3008,8 @@ async function buildMain(args) {
2804
3008
  sourcemap: args.sourcemap,
2805
3009
  customLogger: args.logger,
2806
3010
  logLevel: "info",
3011
+ format: args.format,
3012
+ cjsInteropDeps: args.cjsInteropDeps,
2807
3013
  define: { __ELECTRO_VIEW_REGISTRY__: JSON.stringify(viewRegistry) }
2808
3014
  }));
2809
3015
  }
@@ -2829,7 +3035,9 @@ async function buildPreload(args) {
2829
3035
  plugins: preloadPlugins,
2830
3036
  sourcemap: args.sourcemap,
2831
3037
  customLogger: args.logger,
2832
- logLevel: "info"
3038
+ logLevel: "info",
3039
+ format: args.format,
3040
+ cjsInteropDeps: args.cjsInteropDeps
2833
3041
  });
2834
3042
  if (Object.keys(input).length > 1) {
2835
3043
  const subBuildConfig = createNodeConfig({
@@ -2846,7 +3054,9 @@ async function buildPreload(args) {
2846
3054
  ],
2847
3055
  sourcemap: args.sourcemap,
2848
3056
  customLogger: args.logger,
2849
- logLevel: "info"
3057
+ logLevel: "info",
3058
+ format: args.format,
3059
+ cjsInteropDeps: args.cjsInteropDeps
2850
3060
  });
2851
3061
  baseConfig.plugins.push(isolateEntriesPlugin(subBuildConfig));
2852
3062
  }
@@ -2891,6 +3101,8 @@ async function flattenRendererOutput(rendererDir, views, root) {
2891
3101
  //#region src/dev/dev-server.ts
2892
3102
  const MAIN_RESTART_DEBOUNCE_MS = 80;
2893
3103
  const CONFIG_DEBOUNCE_MS = 300;
3104
+ const MAIN_ENTRY_WAIT_TIMEOUT_MS = 1e4;
3105
+ const MAIN_ENTRY_WAIT_INTERVAL_MS = 50;
2894
3106
  var DevServer = class {
2895
3107
  rendererServer = null;
2896
3108
  electronProcess = null;
@@ -2909,6 +3121,9 @@ var DevServer = class {
2909
3121
  mainWatch = null;
2910
3122
  preloadWatch = null;
2911
3123
  outputDir = "";
3124
+ nodeFormat = "es";
3125
+ mainInitialBuildPromise = null;
3126
+ resolveMainInitialBuild = null;
2912
3127
  logLevel;
2913
3128
  clearScreen;
2914
3129
  rendererOnly;
@@ -2936,6 +3151,7 @@ var DevServer = class {
2936
3151
  this.config = loaded.config;
2937
3152
  this.root = loaded.root;
2938
3153
  this.outputDir = this.outDirOverride ? resolve(this.root, this.outDirOverride) : resolve(this.root, ".electro");
3154
+ this.nodeFormat = await resolveNodeOutputFormat(this.root);
2939
3155
  this.configPaths.add(loaded.configPath);
2940
3156
  for (const view of this.config.views ?? []) this.configPaths.add(view.__source);
2941
3157
  const views = this.config.views ?? [];
@@ -2976,11 +3192,13 @@ var DevServer = class {
2976
3192
  this.attachConfigWatcher();
2977
3193
  return;
2978
3194
  }
2979
- const externals = resolveExternals();
3195
+ const resolvedExternals = await resolveExternals(this.root);
3196
+ const externals = resolvedExternals.externals;
3197
+ const cjsInteropDeps = resolvedExternals.cjsInteropDeps;
2980
3198
  if (views.length > 0) {
2981
3199
  const preloadTimer = startTimer();
2982
3200
  try {
2983
- await this.buildPreload(externals);
3201
+ await this.buildPreload(externals, cjsInteropDeps);
2984
3202
  step("preload", preloadTimer());
2985
3203
  } catch (err) {
2986
3204
  stepFail("preload", err instanceof Error ? err.message : String(err));
@@ -2989,7 +3207,7 @@ var DevServer = class {
2989
3207
  }
2990
3208
  const mainBuildTimer = startTimer();
2991
3209
  try {
2992
- await this.buildMain(externals);
3210
+ await this.buildMain(externals, cjsInteropDeps);
2993
3211
  step("main", mainBuildTimer());
2994
3212
  } catch (err) {
2995
3213
  stepFail("main", err instanceof Error ? err.message : String(err));
@@ -2997,6 +3215,7 @@ var DevServer = class {
2997
3215
  }
2998
3216
  const electronTimer = startTimer();
2999
3217
  try {
3218
+ await this.waitForMainInitialBuild();
3000
3219
  await this.attachElectronProcess();
3001
3220
  step("electron", electronTimer());
3002
3221
  } catch (err) {
@@ -3025,6 +3244,8 @@ var DevServer = class {
3025
3244
  this.mainWatch = null;
3026
3245
  this.preloadWatch?.close();
3027
3246
  this.preloadWatch = null;
3247
+ this.resolveMainInitialBuild = null;
3248
+ this.mainInitialBuildPromise = null;
3028
3249
  if (this.mainRestartFlushTimer) {
3029
3250
  clearTimeout(this.mainRestartFlushTimer);
3030
3251
  this.mainRestartFlushTimer = null;
@@ -3073,7 +3294,7 @@ var DevServer = class {
3073
3294
  const addr = this.rendererServer.httpServer?.address();
3074
3295
  this.rendererUrl = `http://localhost:${typeof addr === "object" && addr ? addr.port : 5173}`;
3075
3296
  }
3076
- async buildPreload(externals) {
3297
+ async buildPreload(externals, cjsInteropDeps) {
3077
3298
  const views = (this.config.views ?? []).filter((v) => v.entry);
3078
3299
  const preloadOutDir = resolve(this.outputDir, "preload");
3079
3300
  const input = {};
@@ -3093,7 +3314,9 @@ var DevServer = class {
3093
3314
  ],
3094
3315
  logLevel: this.logLevel,
3095
3316
  clearScreen: this.clearScreen,
3096
- sourcemap: this.sourcemap
3317
+ sourcemap: this.sourcemap,
3318
+ format: this.nodeFormat,
3319
+ cjsInteropDeps
3097
3320
  });
3098
3321
  if (Object.keys(input).length > 1) {
3099
3322
  const subBuildConfig = createNodeConfig({
@@ -3110,7 +3333,9 @@ var DevServer = class {
3110
3333
  ],
3111
3334
  logLevel: this.logLevel,
3112
3335
  clearScreen: this.clearScreen,
3113
- sourcemap: this.sourcemap
3336
+ sourcemap: this.sourcemap,
3337
+ format: this.nodeFormat,
3338
+ cjsInteropDeps
3114
3339
  });
3115
3340
  baseConfig.plugins.push(isolateEntriesPlugin(subBuildConfig));
3116
3341
  }
@@ -3136,9 +3361,15 @@ var DevServer = class {
3136
3361
  });
3137
3362
  this.preloadWatch = await build(baseConfig);
3138
3363
  }
3139
- async buildMain(externals) {
3364
+ async buildMain(externals, cjsInteropDeps) {
3140
3365
  const runtimeEntry = this.config.runtime.entry;
3141
3366
  const entry = resolve(dirname(this.config.runtime.__source), runtimeEntry);
3367
+ this.mainInitialBuildPromise = new Promise((resolve) => {
3368
+ this.resolveMainInitialBuild = () => {
3369
+ resolve();
3370
+ this.resolveMainInitialBuild = null;
3371
+ };
3372
+ });
3142
3373
  const viewRegistry = (this.config.views ?? []).map((v) => ({
3143
3374
  id: v.name,
3144
3375
  hasRenderer: !!v.entry,
@@ -3161,6 +3392,8 @@ var DevServer = class {
3161
3392
  clearScreen: this.clearScreen,
3162
3393
  userViteConfig: this.config.runtime.vite,
3163
3394
  sourcemap: this.sourcemap,
3395
+ format: this.nodeFormat,
3396
+ cjsInteropDeps,
3164
3397
  define: { __ELECTRO_VIEW_REGISTRY__: JSON.stringify(viewRegistry) }
3165
3398
  });
3166
3399
  const self = this;
@@ -3176,6 +3409,7 @@ var DevServer = class {
3176
3409
  if (firstBuild) {
3177
3410
  firstBuild = false;
3178
3411
  changedFile = null;
3412
+ self.resolveMainInitialBuild?.();
3179
3413
  return;
3180
3414
  }
3181
3415
  const currentChanged = changedFile;
@@ -3191,8 +3425,21 @@ var DevServer = class {
3191
3425
  });
3192
3426
  this.mainWatch = await build(mainConfig);
3193
3427
  }
3428
+ /**
3429
+ * Primary startup synchronization for dev mode:
3430
+ * wait until the initial main watch build has completed.
3431
+ */
3432
+ async waitForMainInitialBuild() {
3433
+ if (!this.mainInitialBuildPromise) return;
3434
+ const timeout = setTimeout$1(MAIN_ENTRY_WAIT_TIMEOUT_MS).then(() => {
3435
+ throw new Error(`Main initial build did not finish in ${MAIN_ENTRY_WAIT_TIMEOUT_MS}ms.`);
3436
+ });
3437
+ const promise = this.mainInitialBuildPromise;
3438
+ this.mainInitialBuildPromise = null;
3439
+ await Promise.race([promise, timeout]);
3440
+ }
3194
3441
  async attachElectronProcess() {
3195
- const mainEntry = resolve(this.outputDir, "main/index.mjs");
3442
+ const mainEntry = await this.waitForMainEntry();
3196
3443
  const env = { ELECTRO_DEV: "true" };
3197
3444
  if (this.rendererServer) {
3198
3445
  const addr = this.rendererServer.httpServer?.address();
@@ -3221,6 +3468,23 @@ var DevServer = class {
3221
3468
  });
3222
3469
  }
3223
3470
  /**
3471
+ * In watch mode, Vite can return the watcher before the first output file
3472
+ * is written. Wait for the built main entry to appear before spawning Electron.
3473
+ */
3474
+ async waitForMainEntry() {
3475
+ const mainOutDir = resolve(this.outputDir, "main");
3476
+ const deadline = Date.now() + MAIN_ENTRY_WAIT_TIMEOUT_MS;
3477
+ let lastError;
3478
+ while (Date.now() < deadline) try {
3479
+ return await resolveMainEntryPath(mainOutDir);
3480
+ } catch (err) {
3481
+ lastError = err;
3482
+ await setTimeout$1(MAIN_ENTRY_WAIT_INTERVAL_MS);
3483
+ }
3484
+ const detail = lastError instanceof Error ? ` Last error: ${lastError.message}` : "";
3485
+ throw new Error(`Main entry was not generated in time (${MAIN_ENTRY_WAIT_TIMEOUT_MS}ms). Checked in: ${mainOutDir}.${detail}`);
3486
+ }
3487
+ /**
3224
3488
  * Restart Electron — handles queued restarts if another rebuild
3225
3489
  * arrives while restart is in flight.
3226
3490
  */
@@ -3374,7 +3638,7 @@ async function preview(options) {
3374
3638
  if (!options.skipBuild) await build$1(options);
3375
3639
  else note("Skipped build (--skip-build)");
3376
3640
  const root = process.cwd();
3377
- const mainEntry = resolve(resolve(root, options.outDir), "main/index.mjs");
3641
+ const mainEntry = await resolveMainEntryPath(resolve(resolve(root, options.outDir), "main"));
3378
3642
  const launchTimer = startTimer();
3379
3643
  try {
3380
3644
  const proc = await launchElectron({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cordy/electro-cli",
3
- "version": "1.2.7",
3
+ "version": "1.2.10",
4
4
  "description": "CLI for @cordy/electro — dev server, build, and code generation commands",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,15 +47,15 @@
47
47
  "prepublishOnly": "bun run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@cordy/electro": "1.2.7",
50
+ "@cordy/electro": "1.2.8",
51
51
  "electron": ">=40.4.1",
52
52
  "vite": ">=8.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@cordy/electro-generator": "1.2.7"
55
+ "@cordy/electro-generator": "1.2.8"
56
56
  },
57
57
  "devDependencies": {
58
- "@cordy/electro": "1.2.7",
58
+ "@cordy/electro": "1.2.9",
59
59
  "@types/node": "^25.2.3",
60
60
  "cac": "^6.7.14",
61
61
  "electron": "^40.4.1",