@next2d/builder 0.1.11 → 0.3.0

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 (54) hide show
  1. package/README.md +90 -57
  2. package/dist/cli.d.ts +7 -0
  3. package/dist/cli.js +68 -3
  4. package/dist/colors.d.ts +6 -0
  5. package/dist/colors.js +7 -0
  6. package/dist/context.d.ts +10 -1
  7. package/dist/context.js +7 -0
  8. package/dist/description.d.ts +2 -0
  9. package/dist/description.js +8 -0
  10. package/dist/electron-config.d.ts +62 -0
  11. package/dist/electron-config.js +134 -0
  12. package/dist/electron-host.d.ts +7 -0
  13. package/dist/electron-host.js +54 -0
  14. package/dist/electron-native.d.ts +15 -0
  15. package/dist/electron-native.js +73 -0
  16. package/dist/electron.d.ts +4 -9
  17. package/dist/electron.js +85 -133
  18. package/dist/index.js +21 -0
  19. package/dist/native.d.ts +9 -24
  20. package/dist/native.js +48 -106
  21. package/dist/project-vite.d.ts +3 -0
  22. package/dist/project-vite.js +21 -0
  23. package/dist/resolve-tool-packages.d.ts +1 -0
  24. package/dist/resolve-tool-packages.js +29 -0
  25. package/dist/steam-upload.d.ts +27 -0
  26. package/dist/steam-upload.js +131 -0
  27. package/dist/steam.d.ts +22 -0
  28. package/dist/steam.js +193 -0
  29. package/dist/tool-packages.d.ts +15 -0
  30. package/dist/tool-packages.js +74 -0
  31. package/dist/web.js +10 -4
  32. package/dist/xbox.d.ts +1 -1
  33. package/dist/xbox.js +60 -57
  34. package/docs/capacitor.md +201 -0
  35. package/docs/native-bridge.md +147 -0
  36. package/docs/steam.md +384 -0
  37. package/docs/xbox.md +145 -0
  38. package/package.json +9 -12
  39. package/templates/electron/entitlements.plist +12 -0
  40. package/templates/electron/icons/icon.icns +0 -0
  41. package/templates/electron/icons/icon.ico +0 -0
  42. package/templates/electron/icons/icon.png +0 -0
  43. package/templates/electron/index.js +119 -0
  44. package/templates/electron/local-assets.cjs +24 -0
  45. package/templates/electron/native-bridge.cjs +98 -0
  46. package/templates/electron/package.json +11 -0
  47. package/templates/electron/preload.cjs +11 -0
  48. package/templates/xbox/src/platform/StbTextRasterizer.cpp +14 -10
  49. package/templates/xbox/tests/check_local.sh +12 -4
  50. package/templates/xbox/tests/stb_safety_test.cpp +126 -0
  51. package/templates/xbox/third_party/LICENSES.md +15 -0
  52. package/templates/xbox/third_party/stb_image.h +15 -10
  53. package/templates/xbox/third_party/stb_truetype.h +4 -4
  54. package/templates/xbox/third_party/stb_vorbis.c +1 -1
package/README.md CHANGED
@@ -1,77 +1,110 @@
1
- Multi-platform Builder
2
- =============
1
+ # Multi-platform Builder
3
2
 
4
3
  [![CodeQL](https://github.com/Next2D/builder/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/Next2D/builder/actions/workflows/github-code-scanning/codeql)
5
4
  [![Lint](https://github.com/Next2D/builder/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/Next2D/builder/actions/workflows/lint.yml)
6
5
  [![release](https://img.shields.io/github/v/release/Next2D/builder)](https://github.com/Next2D/builder/releases)
7
6
  [![license](https://img.shields.io/github/license/Next2D/builder)](https://github.com/Next2D/builder/blob/main/LICENSE)
8
7
 
9
- [日本語]\
10
- 1つのプロジェクトから多種多様なプラットフォーム向けのアプリケーションを作成するマルチプラットフォームビルダー。
11
-
12
- * Steam:Steamに対応したWindows版、macOS版、Linux版のアプリケーションの書き出しが可能。
13
- * デスクトップ:デスクトップ環境向けに、Windows、macOS、Linux向けのアプリケーションを出力できます。
14
- * スマートフォン:iOSおよびAndroidに対応したスマートフォン用アプリケーションとしての書き出しもサポート。
15
- * Web(HTML):ブラウザ上で動作するHTML形式のコンテンツも簡単に生成可能。
16
-
17
- Next2D Frameworkを使えば、1つのソースコードから多様なデバイスやOSに最適化されたアプリケーションを効率的に開発・展開することができます。
18
-
19
- [English]\
20
- Multi-platform builder to create applications for a wide variety of platforms from a single project.
21
-
22
- * Steam: Allows export of applications for Windows, macOS, and Linux versions that are compatible with Steam.
23
- * Desktop: Export of applications for Windows, macOS, and Linux for desktop environments is supported.
24
- * Smartphone: Export as smartphone applications compatible with iOS and Android is also supported.
25
- * Web (HTML): Easily generate HTML-format content that runs in a browser.
26
-
27
- With Next2D Framework, you can efficiently develop and deploy applications optimized for diverse devices and operating systems from a single source code.
28
-
29
- ## Supported
30
-
31
- | platform | detail |
32
- |-----------------|-------------------------------------|
33
- | Windows | Export an exe file |
34
- | macOS | Export dmg, app file |
35
- | Linux | Export deb file |
36
- | Steam:Windows | Export an exe file |
37
- | Steam:macOS | Export dmg, app file |
38
- | Steam:Linux | Export deb file |
39
- | web | Export minfy'd JS files |
40
- | iOS | open Xcode, and Export ipa |
41
- | iOS | open Android Studio, and Export apk |
42
- | Xbox | GDK native title (V8 + Dawn/WebGPU) |
43
-
44
- Xbox exports a GDK native title that runs the Next2D JavaScript on the V8 engine and
45
- renders with Dawn (WebGPU → D3D12), packaged as a C++ executable (no Electron/WebView).
46
- Building the GDK package requires Windows + Visual Studio 2022 + Microsoft GDK + a devkit.
47
- On other platforms the builder scaffolds the host project and stages assets only.
48
- See the generated `xbox/README.md` for full build steps.
49
-
50
- ```bat
51
- npx @next2d/builder --platform xbox --env prd
8
+ [日本語](#日本語) | [English](#english)
9
+
10
+ ## 日本語
11
+
12
+ Next2D Frameworkの1つのプロジェクトから、複数のプラットフォーム向けにアプリケーションを書き出すビルダーです。
13
+ Steamおよびデスクトップ向けのWindows / macOS / Linux、スマートフォン向けのiOS / Android、Web(HTML)に対応しています。
14
+ Xbox対応は試作・開発段階です。
15
+
16
+ ### 対応プラットフォーム
17
+
18
+ | プラットフォーム | 出力・対応状況 |
19
+ |---|---|
20
+ | Steam:Windows | 実行ファイル(.exe)と必要なリソース |
21
+ | Steam:macOS | アプリケーションバンドル(.app) |
22
+ | Steam:Linux | 実行ファイルと必要なリソース |
23
+ | Web | 最適化されたJavaScriptとWebアセット |
24
+ | iOS | Xcodeプロジェクトを開く、IPAを書き出す |
25
+ | Android | Android Studioプロジェクトを開く、APKを書き出す |
26
+ | Xbox | GDKネイティブホストの試作・開発段階 |
27
+
28
+ ### プラットフォーム別ガイド
29
+
30
+ 設定、依存ツール、書き出し・配布手順の詳細は、以下のドキュメントにまとめています。
31
+
32
+ | ガイド | 内容 |
33
+ |---|---|
34
+ | [Electron / Steam](docs/steam.md#日本語) | デスクトップ書き出し、アイコン、CPU、署名、Steam Depot、アップロード、ベータテスト |
35
+ | [ネイティブ機能の拡張](docs/native-bridge.md) | OS API・任意SDKとの連携、設定、プロトコル、実装例(OS情報取得・EOS) |
36
+ | [iOS / Android(Capacitor)](docs/capacitor.md#日本語) | ネイティブ設定、SDK、プラグイン、ビルドコマンド、移行、Xcodeの設定 |
37
+ | [Xbox(試作)](docs/xbox.md#日本語) | 開発状況、ホスト生成、ビルド環境、検証範囲 |
38
+
39
+ ### 今後の対応予定
40
+
41
+ | プラットフォーム |
42
+ |---|
43
+ | Nintendo Switch |
44
+
45
+ ### ビルド例
46
+
47
+ ```sh
48
+ npx @next2d/builder --platform web --env prd
52
49
  ```
53
50
 
54
- The prebuilt V8 engine is downloaded automatically on the first Xbox build
55
- (published from the `build-v8` workflow). To use your own build, pass
56
- `--v8-root C:\path\to\v8`.
51
+ ### プレビュー例
57
52
 
58
- ### Scheduled introduction
53
+ ```sh
54
+ npx @next2d/builder --preview --platform web --env prd
55
+ ```
56
+
57
+ ### ライセンス
58
+
59
+ このプロジェクトは[MIT License](https://opensource.org/licenses/MIT)で公開しています。詳細は[LICENSE](LICENSE)を参照してください。
60
+
61
+ ## English
62
+
63
+ Build applications for multiple platforms from a single Next2D Framework project.
64
+ The builder supports Windows / macOS / Linux for Steam and desktop distribution, iOS / Android for mobile devices, and Web (HTML).
65
+ Xbox support is a prototype under development.
66
+
67
+ ### Supported platforms
59
68
 
60
- | platform |
61
- |-----------------|
69
+ | Platform | Output / status |
70
+ |---|---|
71
+ | Steam:Windows | Executable (.exe) and required resources |
72
+ | Steam:macOS | Application bundle (.app) |
73
+ | Steam:Linux | Executable and required resources |
74
+ | Web | Optimized JavaScript and web assets |
75
+ | iOS | Open the Xcode project and export an IPA |
76
+ | Android | Open the Android Studio project and export an APK |
77
+ | Xbox | Prototype GDK native host; under development |
78
+
79
+ ### Platform guides
80
+
81
+ These guides cover platform-specific configuration, dependencies, builds and distribution.
82
+
83
+ | Guide | Contents |
84
+ |---|---|
85
+ | [Electron / Steam](docs/steam.md#english) | Desktop exports, icons, architectures, signing, Steam depots, uploads and beta testing |
86
+ | [Native extensions (Japanese)](docs/native-bridge.md) | OS APIs and SDK integration, configuration, protocol, and examples (system information / EOS) |
87
+ | [iOS / Android (Capacitor)](docs/capacitor.md#english) | Native configuration, SDKs, plugins, build commands, migration and Xcode setup |
88
+ | [Xbox (prototype)](docs/xbox.md#english) | Development status, host generation, build environment and validation scope |
89
+
90
+ ### Planned support
91
+
92
+ | Platform |
93
+ |---|
62
94
  | Nintendo Switch |
63
95
 
64
- ### build example.
96
+ ### Build example
65
97
 
66
- ```linux
98
+ ```sh
67
99
  npx @next2d/builder --platform web --env prd
68
100
  ```
69
101
 
70
- ### preview example.
102
+ ### Preview example
71
103
 
72
- ```linux
104
+ ```sh
73
105
  npx @next2d/builder --preview --platform web --env prd
74
106
  ```
75
107
 
76
- ## License
77
- This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details.
108
+ ### License
109
+
110
+ This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). See [LICENSE](LICENSE) for details.
package/dist/cli.d.ts CHANGED
@@ -17,6 +17,13 @@ export interface ParsedArgs {
17
17
  open: boolean;
18
18
  build: boolean;
19
19
  v8Root: string;
20
+ arch: string;
21
+ steamManifest: boolean;
22
+ steamUpload: boolean;
23
+ steamBranch: string;
24
+ steamRoot: string;
25
+ steamComment: string;
26
+ dryRun: boolean;
20
27
  hasHelp: boolean;
21
28
  }
22
29
  /**
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // CLI 引数の解析とヘルプ表示。process.argv を読み取り ctx を初期化する。
2
- import pc from "picocolors";
2
+ import pc from "./colors.js";
3
3
  import { ctx } from "./context.js";
4
4
  import { RECOMMENDED_NODE_VERSION } from "./constants.js";
5
5
  /**
@@ -24,6 +24,11 @@ export const echoHelp = () => {
24
24
  console.log("For Xbox build example (prebuilt V8 is downloaded automatically):");
25
25
  console.log("npx @next2d/builder --platform xbox --env prd");
26
26
  console.log("To use your own V8 build: --v8-root C:\\path\\to\\v8");
27
+ console.log("Steam: --platform steam:windows|steam:macos|steam:linux --env prd");
28
+ console.log("Electron: --arch x64|arm64|universal (default: macOS universal, others x64)");
29
+ console.log("Combine existing Steam packages only: --platform steam:macos --env prd --steam-manifest");
30
+ console.log("Upload existing packages: --steam-upload --env prd [--steam-branch internal] [--steam-root dist/steam] [--steam-comment \"Build comment\"] [--dry-run]");
31
+ console.log("Steam upload uses STEAM_USERNAME and optional STEAMCMD (executable path); no --platform is needed.");
27
32
  console.log();
28
33
  process.exit(1);
29
34
  };
@@ -54,10 +59,45 @@ export const parseArgv = (argv) => {
54
59
  "open": false,
55
60
  "build": false,
56
61
  "v8Root": "",
62
+ "arch": "",
63
+ "steamManifest": false,
64
+ "steamUpload": false,
65
+ "steamBranch": "",
66
+ "steamRoot": "",
67
+ "steamComment": "",
68
+ "dryRun": false,
57
69
  "hasHelp": false
58
70
  };
59
71
  for (let idx = 0; idx < argv.length; ++idx) {
60
72
  switch (argv[idx]) {
73
+ case "--steam-upload":
74
+ result.steamUpload = true;
75
+ break;
76
+ case "--dry-run":
77
+ result.dryRun = true;
78
+ break;
79
+ case "--steam-branch":
80
+ case "--steam-root":
81
+ case "--steam-comment": {
82
+ const flag = argv[idx];
83
+ const value = argv[++idx] || "";
84
+ if (!value || value.startsWith("--")) {
85
+ result.hasHelp = true;
86
+ }
87
+ if (flag === "--steam-branch") {
88
+ result.steamBranch = value;
89
+ }
90
+ else if (flag === "--steam-root") {
91
+ result.steamRoot = value;
92
+ }
93
+ else {
94
+ result.steamComment = value;
95
+ }
96
+ break;
97
+ }
98
+ case "--steam-manifest":
99
+ result.steamManifest = true;
100
+ break;
61
101
  case "--preview":
62
102
  result.preview = true;
63
103
  break;
@@ -81,14 +121,32 @@ export const parseArgv = (argv) => {
81
121
  // Xbox ビルドで使う prebuilt V8 monolith のパス (環境変数 V8_ROOT より優先)
82
122
  result.v8Root = argv[++idx] || "";
83
123
  break;
124
+ case "--arch":
125
+ result.arch = argv[++idx] || "";
126
+ if (!result.arch || result.arch.startsWith("--")) {
127
+ result.hasHelp = true;
128
+ }
129
+ break;
84
130
  default:
131
+ if (argv.includes("--steam-upload") && argv[idx].startsWith("-")) {
132
+ result.hasHelp = true;
133
+ }
85
134
  break;
86
135
  }
87
136
  if (result.hasHelp) {
88
137
  break;
89
138
  }
90
139
  }
91
- if (!result.platform || !result.environment) {
140
+ if (!result.platform && !result.steamUpload || !result.environment || result.environment.startsWith("--")) {
141
+ result.hasHelp = true;
142
+ }
143
+ if (result.steamManifest && (!result.platform.startsWith("steam:") || result.preview || result.open || result.build || result.arch)) {
144
+ result.hasHelp = true;
145
+ }
146
+ if (result.steamUpload && (result.platform || result.steamManifest || result.preview || result.open || result.build || result.arch || result.v8Root)) {
147
+ result.hasHelp = true;
148
+ }
149
+ if (!result.steamUpload && (result.steamBranch || result.steamRoot || result.steamComment || result.dryRun)) {
92
150
  result.hasHelp = true;
93
151
  }
94
152
  return result;
@@ -131,7 +189,14 @@ Please update your version of Node.`);
131
189
  ctx.open = parsed.open;
132
190
  ctx.build = parsed.build;
133
191
  ctx.v8Root = parsed.v8Root;
134
- if (parsed.hasHelp || !SUPPORTED_PLATFORMS.has(ctx.platform)) {
192
+ ctx.arch = parsed.arch;
193
+ ctx.steamManifest = parsed.steamManifest;
194
+ ctx.steamUpload = parsed.steamUpload;
195
+ ctx.steamBranch = parsed.steamBranch;
196
+ ctx.steamRoot = parsed.steamRoot;
197
+ ctx.steamComment = parsed.steamComment;
198
+ ctx.dryRun = parsed.dryRun;
199
+ if (parsed.hasHelp || !ctx.steamUpload && !SUPPORTED_PLATFORMS.has(ctx.platform)) {
135
200
  echoHelp();
136
201
  }
137
202
  ctx.platformDir = derivePlatformDir(ctx.platform);
@@ -0,0 +1,6 @@
1
+ declare const _default: {
2
+ red: (value: unknown) => string;
3
+ green: (value: unknown) => string;
4
+ yellow: (value: unknown) => string;
5
+ };
6
+ export default _default;
package/dist/colors.js ADDED
@@ -0,0 +1,7 @@
1
+ // Node handles TTY detection, NO_COLOR and FORCE_COLOR without a logging dependency.
2
+ import { styleText } from "node:util";
3
+ export default {
4
+ "red": (value) => styleText("red", String(value)),
5
+ "green": (value) => styleText("green", String(value)),
6
+ "yellow": (value) => styleText("yellow", String(value))
7
+ };
package/dist/context.d.ts CHANGED
@@ -14,8 +14,17 @@ export interface BuildContext {
14
14
  build: boolean;
15
15
  /** --v8-root (Xbox) */
16
16
  v8Root: string;
17
+ /** Electron CPU architecture (--arch); empty selects a platform default. */
18
+ arch: string;
19
+ /** Generate shared Steam depot manifests from existing packages only. */
20
+ steamManifest: boolean;
21
+ steamUpload: boolean;
22
+ steamBranch: string;
23
+ steamRoot: string;
24
+ steamComment: string;
25
+ dryRun: boolean;
17
26
  /** vite の loadConfigFromFile 結果 */
18
- configObject: any | null;
27
+ configObject: Awaited<ReturnType<typeof import("vite").loadConfigFromFile>>;
19
28
  /** ビルド出力ディレクトリ (dist/<platformDir>/<env>) */
20
29
  buildDir: string;
21
30
  /** vite build.outDir (既定 "dist") */
package/dist/context.js CHANGED
@@ -12,6 +12,13 @@ export const ctx = {
12
12
  "open": false,
13
13
  "build": false,
14
14
  "v8Root": "",
15
+ "arch": "",
16
+ "steamManifest": false,
17
+ "steamUpload": false,
18
+ "steamBranch": "",
19
+ "steamRoot": "",
20
+ "steamComment": "",
21
+ "dryRun": false,
15
22
  "configObject": null,
16
23
  "buildDir": "",
17
24
  "outDir": "dist",
@@ -0,0 +1,2 @@
1
+ /** An explicit empty string is intentional; null/undefined use package metadata. */
2
+ export declare const resolveDescription: (description: unknown, fallback: unknown) => string;
@@ -0,0 +1,8 @@
1
+ /** An explicit empty string is intentional; null/undefined use package metadata. */
2
+ export const resolveDescription = (description, fallback) => {
3
+ const value = description ?? fallback ?? "";
4
+ if (typeof value !== "string") {
5
+ throw new Error("App description must be a string.");
6
+ }
7
+ return value;
8
+ };
@@ -0,0 +1,62 @@
1
+ import type { INativeBridgeConfig } from "./electron-native.js";
2
+ export type ElectronOS = "windows" | "macos" | "linux";
3
+ export interface IElectronConfig {
4
+ nativeBridge?: INativeBridgeConfig;
5
+ appId: string;
6
+ appName: string;
7
+ description: string;
8
+ executableName: string;
9
+ companyName: string;
10
+ icons: Partial<Record<ElectronOS, string>>;
11
+ architectures: Partial<Record<ElectronOS, string>>;
12
+ window: {
13
+ width: number;
14
+ height: number;
15
+ fullscreen: boolean;
16
+ };
17
+ steam?: {
18
+ appId: string | null;
19
+ depots: Partial<Record<ElectronOS, string | null>>;
20
+ branch?: string;
21
+ };
22
+ macos: {
23
+ sign: boolean;
24
+ notarize: boolean;
25
+ localNetworkUsageDescription?: string;
26
+ };
27
+ }
28
+ export declare const validateSteamId: (value: unknown) => string;
29
+ export declare const readElectronConfig: (root: string) => IElectronConfig;
30
+ /** JSON is the source of game metadata; secrets stay in the environment/keychain. */
31
+ export declare const createElectronPackagerOptions: (root: string, config: IElectronConfig, os: ElectronOS, resources: string) => {
32
+ osxNotarize?: {
33
+ keychainProfile: string;
34
+ } | undefined;
35
+ osxSign?: {
36
+ identity: string | undefined;
37
+ continueOnError: boolean;
38
+ ignore: string[];
39
+ optionsForFile: () => {
40
+ hardenedRuntime: boolean;
41
+ entitlements: string;
42
+ };
43
+ } | undefined;
44
+ extendInfo?: {
45
+ NSLocalNetworkUsageDescription: string;
46
+ } | undefined;
47
+ extendHelperInfo?: {
48
+ NSLocalNetworkUsageDescription: string;
49
+ } | undefined;
50
+ icon?: string | undefined;
51
+ name: string;
52
+ executableName: string;
53
+ appBundleId: string;
54
+ appVersion: any;
55
+ win32metadata: {
56
+ CompanyName: string;
57
+ FileDescription: string;
58
+ };
59
+ asar: boolean;
60
+ extraResource: string[];
61
+ ignore: RegExp[];
62
+ };
@@ -0,0 +1,134 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getTemplateDir } from "./utils.js";
4
+ import { resolveDescription } from "./description.js";
5
+ import { validateNativeBridge } from "./electron-native.js";
6
+ export const validateSteamId = (value) => {
7
+ const id = String(value);
8
+ if (!/^[1-9][0-9]*$/.test(id) || !Number.isSafeInteger(Number(id)) || Number(id) > 4294967295) {
9
+ throw new Error("Steam App ID and Depot IDs must be positive 32-bit integers.");
10
+ }
11
+ return id;
12
+ };
13
+ export const readElectronConfig = (root) => {
14
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
15
+ const file = path.resolve(root, process.env.NEXT2D_ELECTRON_CONFIG_FILE || "electron.config.json");
16
+ if (process.env.NEXT2D_ELECTRON_CONFIG_FILE && !fs.existsSync(file)) {
17
+ throw new Error("NEXT2D_ELECTRON_CONFIG_FILE does not exist.");
18
+ }
19
+ const input = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {};
20
+ const config = {
21
+ "appId": input.appId ?? `app.next2d.${pkg.name}`,
22
+ "appName": input.appName ?? pkg.name,
23
+ "description": resolveDescription(input.description, pkg.description),
24
+ "executableName": input.executableName ?? pkg.name,
25
+ "companyName": input.companyName ?? pkg.author?.name ?? input.appName ?? pkg.name,
26
+ "icons": {
27
+ "windows": path.join(getTemplateDir("electron"), "icons/icon.ico"),
28
+ "macos": path.join(getTemplateDir("electron"), "icons/icon.icns"),
29
+ "linux": path.join(getTemplateDir("electron"), "icons/icon.png"),
30
+ ...input.icons
31
+ },
32
+ "architectures": input.architectures ?? {},
33
+ "window": { "width": 1280, "height": 720, "fullscreen": false, ...input.window },
34
+ "macos": { "sign": false, "notarize": false, ...input.macos },
35
+ "steam": input.steam,
36
+ "nativeBridge": input.nativeBridge
37
+ };
38
+ if (typeof config.appId !== "string" || !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.appId)) {
39
+ throw new Error("electron.config.json appId must be a reverse-domain bundle identifier.");
40
+ }
41
+ for (const [key, value] of [["appName", config.appName], ["executableName", config.executableName]]) {
42
+ if (typeof value !== "string" || !value.trim() || /[<>:"/\\|?*\x00-\x1f]/.test(value) || /[. ]$/.test(value) || value === "..") {
43
+ throw new Error(`electron.config.json ${key} must be a valid portable file name.`);
44
+ }
45
+ }
46
+ if (typeof config.companyName !== "string" || !config.companyName.trim()) {
47
+ throw new Error("electron.config.json companyName must be a nonempty string.");
48
+ }
49
+ for (const dimension of [config.window.width, config.window.height]) {
50
+ if (!Number.isInteger(dimension) || dimension < 1 || dimension > 16384) {
51
+ throw new Error("Electron window width/height must be integers between 1 and 16384.");
52
+ }
53
+ }
54
+ for (const value of [config.window.fullscreen, config.macos.sign, config.macos.notarize]) {
55
+ if (typeof value !== "boolean") {
56
+ throw new Error("Electron fullscreen/sign/notarize options must be booleans.");
57
+ }
58
+ }
59
+ if (config.macos.notarize && !config.macos.sign) {
60
+ throw new Error("macos.notarize requires macos.sign.");
61
+ }
62
+ const localNetwork = config.macos.localNetworkUsageDescription;
63
+ if (localNetwork !== undefined && (typeof localNetwork !== "string" || !localNetwork.trim()
64
+ || localNetwork.length > 1000 || localNetwork.includes("\0"))) {
65
+ throw new Error("macos.localNetworkUsageDescription must be a nonempty string of at most 1000 characters.");
66
+ }
67
+ for (const os of ["windows", "macos", "linux"]) {
68
+ const icon = config.icons[os];
69
+ if (icon !== undefined) {
70
+ const extension = { "windows": ".ico", "macos": ".icns", "linux": ".png" }[os];
71
+ if (typeof icon !== "string" || path.extname(icon).toLowerCase() !== extension || !fs.statSync(path.resolve(root, icon), { "throwIfNoEntry": false })?.isFile()) {
72
+ throw new Error(`electron.config.json icons.${os} must point to an existing ${extension} file (relative to the project root).`);
73
+ }
74
+ }
75
+ const arch = config.architectures[os];
76
+ if (arch !== undefined && !(os === "macos" ? ["x64", "arm64", "universal"] : ["x64", "arm64"]).includes(arch)) {
77
+ throw new Error(`Invalid electron.config.json architectures.${os}.`);
78
+ }
79
+ }
80
+ if (config.steam) {
81
+ config.steam.appId = config.steam.appId === null || config.steam.appId === undefined ? null : validateSteamId(config.steam.appId);
82
+ const depots = config.steam.depots ?? {};
83
+ const ids = Object.values(depots).filter((value) => value !== null).map(validateSteamId);
84
+ if (ids.length && !config.steam.appId) {
85
+ throw new Error("Set steam.appId before configuring Steam Depot IDs.");
86
+ }
87
+ config.steam.depots = depots;
88
+ }
89
+ validateNativeBridge(config.nativeBridge);
90
+ return config;
91
+ };
92
+ /** JSON is the source of game metadata; secrets stay in the environment/keychain. */
93
+ export const createElectronPackagerOptions = (root, config, os, resources) => {
94
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
95
+ const release = process.env.NEXT2D_STEAM_RELEASE === "1";
96
+ const sign = config.macos.sign || release;
97
+ const notarize = config.macos.notarize || release;
98
+ if (os === "macos" && sign && !process.env.APPLE_SIGNING_IDENTITY) {
99
+ throw new Error("macOS signing requires APPLE_SIGNING_IDENTITY.");
100
+ }
101
+ if (os === "macos" && notarize && !process.env.APPLE_NOTARY_PROFILE) {
102
+ throw new Error("macOS notarization requires APPLE_NOTARY_PROFILE (notarytool keychain profile).");
103
+ }
104
+ return {
105
+ "name": config.appName,
106
+ "executableName": config.executableName,
107
+ "appBundleId": config.appId,
108
+ "appVersion": pkg.version,
109
+ "win32metadata": { "CompanyName": config.companyName, "FileDescription": config.description },
110
+ "asar": true,
111
+ "extraResource": [resources, ...config.nativeBridge ? [path.join(path.dirname(resources), "native")] : []],
112
+ "ignore": [/^\/native(?:\/|$)/, /^\/resources(?:\/|$)/, /^\/icons(?:\/|$)/, /^\/forge\.config\.[cm]?js$/, /^\/entitlements\.plist$/, /(?:^|\/)steam_appid\.txt$/],
113
+ ...os !== "linux" && config.icons[os] ? { "icon": path.resolve(root, config.icons[os]) } : {},
114
+ ...os === "macos" && config.macos.localNetworkUsageDescription ? {
115
+ "extendInfo": { "NSLocalNetworkUsageDescription": config.macos.localNetworkUsageDescription },
116
+ "extendHelperInfo": { "NSLocalNetworkUsageDescription": config.macos.localNetworkUsageDescription }
117
+ } : {},
118
+ ...os === "macos" && sign ? {
119
+ "osxSign": {
120
+ "identity": process.env.APPLE_SIGNING_IDENTITY,
121
+ // Packager otherwise swallows signing failures and attempts notarization.
122
+ "continueOnError": false,
123
+ // Static web/localization data is sealed by its enclosing bundle.
124
+ // Keep native sidecars and libraries outside these exclusions.
125
+ "ignore": ["/Contents/Resources/resources/", "\\.lproj/locale\\.pak$"],
126
+ "optionsForFile": () => ({
127
+ "hardenedRuntime": true,
128
+ "entitlements": path.join(getTemplateDir("electron"), "entitlements.plist")
129
+ })
130
+ }
131
+ } : {},
132
+ ...os === "macos" && notarize ? { "osxNotarize": { "keychainProfile": process.env.APPLE_NOTARY_PROFILE } } : {}
133
+ };
134
+ };
@@ -0,0 +1,7 @@
1
+ import type { IElectronConfig } from "./electron-config.js";
2
+ /** Packager cleans its temporary root; never share that root with concurrent builds. */
3
+ export declare const withElectronPackagerTemp: <T>(use: (directory: string) => Promise<T>) => Promise<T>;
4
+ /** The builder pins the runtime; games need neither Electron nor its npm dependency tree. */
5
+ export declare const getElectronVersion: () => string;
6
+ /** No user files are written or removed. Each build gets an isolated, disposable host. */
7
+ export declare const withElectronHost: <T>(root: string, resources: string, config: IElectronConfig, use: (directory: string) => Promise<T>, target?: string) => Promise<T>;
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { getTemplateDir } from "./utils.js";
5
+ import { stageNativeBridge } from "./electron-native.js";
6
+ /** Packager cleans its temporary root; never share that root with concurrent builds. */
7
+ export const withElectronPackagerTemp = async (use) => {
8
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-packager-"));
9
+ try {
10
+ return await use(dir);
11
+ }
12
+ finally {
13
+ fs.rmSync(dir, { "recursive": true, "force": true });
14
+ }
15
+ };
16
+ /** The builder pins the runtime; games need neither Electron nor its npm dependency tree. */
17
+ export const getElectronVersion = () => {
18
+ const pkg = JSON.parse(fs.readFileSync(path.join(getTemplateDir("electron"), "package.json"), "utf8"));
19
+ const version = pkg.devDependencies.electron;
20
+ if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version)) {
21
+ throw new Error("The builder's Electron template must pin an exact runtime version.");
22
+ }
23
+ return version;
24
+ };
25
+ /** No user files are written or removed. Each build gets an isolated, disposable host. */
26
+ export const withElectronHost = async (root, resources, config, use, target) => {
27
+ if (!fs.statSync(path.join(resources, "index.html"), { "throwIfNoEntry": false })?.isFile()) {
28
+ throw new Error(`Electron web entry point is missing: ${resources}/index.html`);
29
+ }
30
+ const game = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
31
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-electron-"));
32
+ try {
33
+ for (const file of ["index.js", "local-assets.cjs", "native-bridge.cjs", "preload.cjs"]) {
34
+ fs.copyFileSync(path.join(getTemplateDir("electron"), file), path.join(dir, file));
35
+ }
36
+ const pkg = {
37
+ "name": game.name, "version": game.version, "description": config.description,
38
+ "private": true, "type": "commonjs", "main": "index.js"
39
+ };
40
+ fs.writeFileSync(path.join(dir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
41
+ fs.cpSync(resources, path.join(dir, "resources"), { "recursive": true });
42
+ const nativeBridge = config.nativeBridge ? stageNativeBridge(root, dir, config.nativeBridge, target ?? "") : undefined;
43
+ const runtime = { "appId": config.appId, "appName": config.appName, "window": config.window, "icon": "", nativeBridge };
44
+ if (config.icons.linux) {
45
+ fs.copyFileSync(path.resolve(root, config.icons.linux), path.join(dir, "resources/desktop-icon.png"));
46
+ runtime.icon = "desktop-icon.png";
47
+ }
48
+ fs.writeFileSync(path.join(dir, "runtime-config.json"), `${JSON.stringify(runtime, null, 2)}\n`);
49
+ return await use(dir);
50
+ }
51
+ finally {
52
+ fs.rmSync(dir, { "recursive": true, "force": true });
53
+ }
54
+ };
@@ -0,0 +1,15 @@
1
+ export interface INativeBridgeConfig {
2
+ methods: string[];
3
+ targets: Record<string, {
4
+ directory: string;
5
+ executable: string;
6
+ }>;
7
+ backgroundThrottling?: boolean;
8
+ prepare?: string;
9
+ }
10
+ export declare const validateNativeBridge: (value: INativeBridgeConfig | undefined) => void;
11
+ export declare const stageNativeBridge: (root: string, host: string, config: INativeBridgeConfig, target: string) => {
12
+ backgroundThrottling?: boolean | undefined;
13
+ executable: string;
14
+ methods: string[];
15
+ };