@cordy/electro-cli 1.2.8 → 1.2.11

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 +243 -20
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -491,7 +491,7 @@ const cac = (name = "") => new CAC(name);
491
491
 
492
492
  //#endregion
493
493
  //#region package.json
494
- var version$1 = "1.2.8";
494
+ var version$1 = "1.2.11";
495
495
 
496
496
  //#endregion
497
497
  //#region src/dev/logger.ts
@@ -815,16 +815,48 @@ async function resolveExternals(root) {
815
815
  deps.delete("@cordy/electro");
816
816
  const builtins = builtinModules.flatMap((m) => [m, `node:${m}`]);
817
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);
818
822
  const deepPattern = depsArray.length > 0 ? new RegExp(`^(${depsArray.map(escapeRegExp).join("|")})/.+`) : null;
819
- return deepPattern ? [
820
- ...depsArray,
821
- ...builtins,
822
- deepPattern
823
- ] : [...depsArray, ...builtins];
823
+ return {
824
+ externals: deepPattern ? [
825
+ ...depsArray,
826
+ ...builtins,
827
+ deepPattern
828
+ ] : [...depsArray, ...builtins],
829
+ cjsInteropDeps
830
+ };
824
831
  }
825
832
  function escapeRegExp(str) {
826
833
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
827
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
+ }
828
860
 
829
861
  //#endregion
830
862
  //#region src/dev/node-format.ts
@@ -1878,6 +1910,96 @@ var MagicString = class MagicString {
1878
1910
  }
1879
1911
  };
1880
1912
 
1913
+ //#endregion
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$]*$/;
1917
+ /**
1918
+ * Rewrites named imports from external CommonJS deps to runtime-safe accessors.
1919
+ *
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
1925
+ */
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
+
1881
2003
  //#endregion
1882
2004
  //#region src/plugins/esm-shim.ts
1883
2005
  const CJSYNTAX_RE = /__filename|__dirname|require\(|require\.resolve\(/;
@@ -1964,6 +2086,7 @@ function createNodeConfig(opts) {
1964
2086
  plugins: [
1965
2087
  importMetaPlugin(),
1966
2088
  ...opts.plugins ?? [],
2089
+ cjsExternalInteropPlugin(opts.cjsInteropDeps ?? []),
1967
2090
  esmShimPlugin()
1968
2091
  ],
1969
2092
  customLogger: opts.customLogger,
@@ -2078,6 +2201,30 @@ function deduplicatePlugins(plugins) {
2078
2201
  return result;
2079
2202
  }
2080
2203
 
2204
+ //#endregion
2205
+ //#region src/dev/bridge-types.ts
2206
+ const GENERATED_BRIDGE_DIRS = ["views", "windows"];
2207
+ /**
2208
+ * Resolve generated bridge declaration file for a view.
2209
+ * Supports both historical `generated/windows/*` and current `generated/views/*`.
2210
+ */
2211
+ function findBridgeTypesForView(files, viewName) {
2212
+ const byPath = files.find((f) => f.path === `generated/views/${viewName}.bridge.d.ts`) ?? files.find((f) => f.path === `generated/windows/${viewName}.bridge.d.ts`);
2213
+ if (byPath) return byPath;
2214
+ return files.find((f) => f.path.endsWith(`/${viewName}.bridge.d.ts`)) ?? null;
2215
+ }
2216
+ /** Target location for per-view bridge types next to the config file. */
2217
+ function resolveViewBridgePath(view) {
2218
+ if (!view.__source) return null;
2219
+ return resolve(dirname(view.__source), "bridge.d.ts");
2220
+ }
2221
+ function isGeneratedBridgeTypesPath(path) {
2222
+ return GENERATED_BRIDGE_DIRS.some((dir) => path.startsWith(`generated/${dir}/`) && path.endsWith(".bridge.d.ts"));
2223
+ }
2224
+ function generatedBridgeTypesPaths(viewName) {
2225
+ return GENERATED_BRIDGE_DIRS.map((dir) => `generated/${dir}/${viewName}.bridge.d.ts`);
2226
+ }
2227
+
2081
2228
  //#endregion
2082
2229
  //#region src/plugins/utils.ts
2083
2230
  /** Strip query and hash from a URL/path. */
@@ -2788,19 +2935,36 @@ async function build$1(options) {
2788
2935
  });
2789
2936
  await mkdir(codegenDir, { recursive: true });
2790
2937
  for (const file of files) {
2938
+ if (isGeneratedBridgeTypesPath(file.path)) continue;
2791
2939
  const filePath = resolve(codegenDir, file.path);
2792
2940
  await mkdir(dirname(filePath), { recursive: true });
2793
2941
  await writeFile(filePath, file.content);
2794
2942
  }
2943
+ for (const view of views) {
2944
+ const bridge = findBridgeTypesForView(files, view.name);
2945
+ const bridgePath = resolveViewBridgePath(view);
2946
+ if (bridge && bridgePath) {
2947
+ await mkdir(dirname(bridgePath), { recursive: true });
2948
+ await writeFileIfChanged$2(bridgePath, bridge.content);
2949
+ }
2950
+ for (const relPath of generatedBridgeTypesPaths(view.name)) {
2951
+ const legacyPath = resolve(codegenDir, relPath);
2952
+ try {
2953
+ await unlink(legacyPath);
2954
+ } catch {}
2955
+ }
2956
+ }
2795
2957
  const envTypesPath = resolve(srcDir, envTypes.path);
2796
2958
  await mkdir(dirname(envTypesPath), { recursive: true });
2797
- await writeFile(envTypesPath, envTypes.content);
2959
+ await writeFileIfChanged$2(envTypesPath, envTypes.content);
2798
2960
  step("codegen", codegenTimer());
2799
2961
  } catch (err) {
2800
2962
  stepFail("codegen", err instanceof Error ? err.message : String(err));
2801
2963
  process.exit(1);
2802
2964
  }
2803
- const externals = await resolveExternals(root);
2965
+ const resolvedExternals = await resolveExternals(root);
2966
+ const externals = resolvedExternals.externals;
2967
+ const cjsInteropDeps = resolvedExternals.cjsInteropDeps;
2804
2968
  const logger = createBuildLogger();
2805
2969
  try {
2806
2970
  buildScope("main");
@@ -2812,7 +2976,8 @@ async function build$1(options) {
2812
2976
  sourcemap: options.sourcemap,
2813
2977
  logger,
2814
2978
  bytecode: options.bytecode,
2815
- format: nodeFormat
2979
+ format: nodeFormat,
2980
+ cjsInteropDeps
2816
2981
  });
2817
2982
  } catch (err) {
2818
2983
  stepFail("main", err instanceof Error ? err.message : String(err));
@@ -2829,7 +2994,8 @@ async function build$1(options) {
2829
2994
  sourcemap: options.sourcemap,
2830
2995
  logger,
2831
2996
  bytecode: options.bytecode,
2832
- format: nodeFormat
2997
+ format: nodeFormat,
2998
+ cjsInteropDeps
2833
2999
  });
2834
3000
  } catch (err) {
2835
3001
  stepFail("preload", err instanceof Error ? err.message : String(err));
@@ -2855,6 +3021,12 @@ async function build$1(options) {
2855
3021
  }
2856
3022
  footer(`Built in ${totalTimer()}`, outDir);
2857
3023
  }
3024
+ async function writeFileIfChanged$2(filePath, content) {
3025
+ try {
3026
+ if (await readFile(filePath, "utf-8") === content) return;
3027
+ } catch {}
3028
+ await writeFile(filePath, content);
3029
+ }
2858
3030
  async function buildMain(args) {
2859
3031
  const runtimeEntry = args.config.runtime.entry;
2860
3032
  const entry = resolve(dirname(args.config.runtime.__source), runtimeEntry);
@@ -2882,6 +3054,7 @@ async function buildMain(args) {
2882
3054
  customLogger: args.logger,
2883
3055
  logLevel: "info",
2884
3056
  format: args.format,
3057
+ cjsInteropDeps: args.cjsInteropDeps,
2885
3058
  define: { __ELECTRO_VIEW_REGISTRY__: JSON.stringify(viewRegistry) }
2886
3059
  }));
2887
3060
  }
@@ -2908,7 +3081,8 @@ async function buildPreload(args) {
2908
3081
  sourcemap: args.sourcemap,
2909
3082
  customLogger: args.logger,
2910
3083
  logLevel: "info",
2911
- format: args.format
3084
+ format: args.format,
3085
+ cjsInteropDeps: args.cjsInteropDeps
2912
3086
  });
2913
3087
  if (Object.keys(input).length > 1) {
2914
3088
  const subBuildConfig = createNodeConfig({
@@ -2926,7 +3100,8 @@ async function buildPreload(args) {
2926
3100
  sourcemap: args.sourcemap,
2927
3101
  customLogger: args.logger,
2928
3102
  logLevel: "info",
2929
- format: args.format
3103
+ format: args.format,
3104
+ cjsInteropDeps: args.cjsInteropDeps
2930
3105
  });
2931
3106
  baseConfig.plugins.push(isolateEntriesPlugin(subBuildConfig));
2932
3107
  }
@@ -3062,11 +3237,13 @@ var DevServer = class {
3062
3237
  this.attachConfigWatcher();
3063
3238
  return;
3064
3239
  }
3065
- const externals = await resolveExternals(this.root);
3240
+ const resolvedExternals = await resolveExternals(this.root);
3241
+ const externals = resolvedExternals.externals;
3242
+ const cjsInteropDeps = resolvedExternals.cjsInteropDeps;
3066
3243
  if (views.length > 0) {
3067
3244
  const preloadTimer = startTimer();
3068
3245
  try {
3069
- await this.buildPreload(externals);
3246
+ await this.buildPreload(externals, cjsInteropDeps);
3070
3247
  step("preload", preloadTimer());
3071
3248
  } catch (err) {
3072
3249
  stepFail("preload", err instanceof Error ? err.message : String(err));
@@ -3075,7 +3252,7 @@ var DevServer = class {
3075
3252
  }
3076
3253
  const mainBuildTimer = startTimer();
3077
3254
  try {
3078
- await this.buildMain(externals);
3255
+ await this.buildMain(externals, cjsInteropDeps);
3079
3256
  step("main", mainBuildTimer());
3080
3257
  } catch (err) {
3081
3258
  stepFail("main", err instanceof Error ? err.message : String(err));
@@ -3139,13 +3316,28 @@ var DevServer = class {
3139
3316
  });
3140
3317
  await mkdir(outputDir, { recursive: true });
3141
3318
  for (const file of files) {
3319
+ if (isGeneratedBridgeTypesPath(file.path)) continue;
3142
3320
  const filePath = resolve(outputDir, file.path);
3143
3321
  await mkdir(dirname(filePath), { recursive: true });
3144
3322
  await writeFile(filePath, file.content);
3145
3323
  }
3324
+ for (const view of this.config.views ?? []) {
3325
+ const bridge = findBridgeTypesForView(files, view.name);
3326
+ const bridgePath = resolveViewBridgePath(view);
3327
+ if (bridge && bridgePath) {
3328
+ await mkdir(dirname(bridgePath), { recursive: true });
3329
+ await writeFileIfChanged$1(bridgePath, bridge.content);
3330
+ }
3331
+ for (const relPath of generatedBridgeTypesPaths(view.name)) {
3332
+ const legacyPath = resolve(outputDir, relPath);
3333
+ try {
3334
+ await unlink(legacyPath);
3335
+ } catch {}
3336
+ }
3337
+ }
3146
3338
  const envTypesPath = resolve(srcDir, envTypes.path);
3147
3339
  await mkdir(dirname(envTypesPath), { recursive: true });
3148
- await writeFile(envTypesPath, envTypes.content);
3340
+ await writeFileIfChanged$1(envTypesPath, envTypes.content);
3149
3341
  }
3150
3342
  async startRenderer() {
3151
3343
  const views = (this.config.views ?? []).filter((v) => v.entry);
@@ -3162,7 +3354,7 @@ var DevServer = class {
3162
3354
  const addr = this.rendererServer.httpServer?.address();
3163
3355
  this.rendererUrl = `http://localhost:${typeof addr === "object" && addr ? addr.port : 5173}`;
3164
3356
  }
3165
- async buildPreload(externals) {
3357
+ async buildPreload(externals, cjsInteropDeps) {
3166
3358
  const views = (this.config.views ?? []).filter((v) => v.entry);
3167
3359
  const preloadOutDir = resolve(this.outputDir, "preload");
3168
3360
  const input = {};
@@ -3183,7 +3375,8 @@ var DevServer = class {
3183
3375
  logLevel: this.logLevel,
3184
3376
  clearScreen: this.clearScreen,
3185
3377
  sourcemap: this.sourcemap,
3186
- format: this.nodeFormat
3378
+ format: this.nodeFormat,
3379
+ cjsInteropDeps
3187
3380
  });
3188
3381
  if (Object.keys(input).length > 1) {
3189
3382
  const subBuildConfig = createNodeConfig({
@@ -3201,7 +3394,8 @@ var DevServer = class {
3201
3394
  logLevel: this.logLevel,
3202
3395
  clearScreen: this.clearScreen,
3203
3396
  sourcemap: this.sourcemap,
3204
- format: this.nodeFormat
3397
+ format: this.nodeFormat,
3398
+ cjsInteropDeps
3205
3399
  });
3206
3400
  baseConfig.plugins.push(isolateEntriesPlugin(subBuildConfig));
3207
3401
  }
@@ -3227,7 +3421,7 @@ var DevServer = class {
3227
3421
  });
3228
3422
  this.preloadWatch = await build(baseConfig);
3229
3423
  }
3230
- async buildMain(externals) {
3424
+ async buildMain(externals, cjsInteropDeps) {
3231
3425
  const runtimeEntry = this.config.runtime.entry;
3232
3426
  const entry = resolve(dirname(this.config.runtime.__source), runtimeEntry);
3233
3427
  this.mainInitialBuildPromise = new Promise((resolve) => {
@@ -3259,6 +3453,7 @@ var DevServer = class {
3259
3453
  userViteConfig: this.config.runtime.vite,
3260
3454
  sourcemap: this.sourcemap,
3261
3455
  format: this.nodeFormat,
3456
+ cjsInteropDeps,
3262
3457
  define: { __ELECTRO_VIEW_REGISTRY__: JSON.stringify(viewRegistry) }
3263
3458
  });
3264
3459
  const self = this;
@@ -3422,6 +3617,12 @@ var DevServer = class {
3422
3617
  });
3423
3618
  }
3424
3619
  };
3620
+ async function writeFileIfChanged$1(filePath, content) {
3621
+ try {
3622
+ if (await readFile(filePath, "utf-8") === content) return;
3623
+ } catch {}
3624
+ await writeFile(filePath, content);
3625
+ }
3425
3626
 
3426
3627
  //#endregion
3427
3628
  //#region src/commands/dev.ts
@@ -3482,17 +3683,39 @@ async function generate$1(options) {
3482
3683
  });
3483
3684
  console.log(`Generating ${files.length + 1} file(s)...`);
3484
3685
  for (const file of files) {
3686
+ if (isGeneratedBridgeTypesPath(file.path)) continue;
3485
3687
  const fullPath = resolve(outputDir, file.path);
3486
3688
  await mkdir(dirname(fullPath), { recursive: true });
3487
3689
  await writeFile(fullPath, file.content);
3488
3690
  console.log(` .electro/${file.path}`);
3489
3691
  }
3692
+ for (const view of views) {
3693
+ const bridge = findBridgeTypesForView(files, view.name);
3694
+ const bridgePath = resolveViewBridgePath(view);
3695
+ if (bridge && bridgePath) {
3696
+ await mkdir(dirname(bridgePath), { recursive: true });
3697
+ await writeFileIfChanged(bridgePath, bridge.content);
3698
+ console.log(` ${relative(process.cwd(), bridgePath)}`);
3699
+ }
3700
+ for (const relPath of generatedBridgeTypesPaths(view.name)) {
3701
+ const legacyPath = resolve(outputDir, relPath);
3702
+ try {
3703
+ await unlink(legacyPath);
3704
+ } catch {}
3705
+ }
3706
+ }
3490
3707
  const envTypesPath = resolve(srcDir, envTypes.path);
3491
3708
  await mkdir(dirname(envTypesPath), { recursive: true });
3492
3709
  await writeFile(envTypesPath, envTypes.content);
3493
3710
  console.log(` src/${envTypes.path}`);
3494
3711
  console.log("Done.");
3495
3712
  }
3713
+ async function writeFileIfChanged(filePath, content) {
3714
+ try {
3715
+ if (await readFile(filePath, "utf-8") === content) return;
3716
+ } catch {}
3717
+ await writeFile(filePath, content);
3718
+ }
3496
3719
 
3497
3720
  //#endregion
3498
3721
  //#region src/commands/preview.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cordy/electro-cli",
3
- "version": "1.2.8",
3
+ "version": "1.2.11",
4
4
  "description": "CLI for @cordy/electro — dev server, build, and code generation commands",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,7 +55,7 @@
55
55
  "@cordy/electro-generator": "1.2.8"
56
56
  },
57
57
  "devDependencies": {
58
- "@cordy/electro": "1.2.6",
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",