@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.
- package/README.md +90 -57
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +68 -3
- package/dist/colors.d.ts +6 -0
- package/dist/colors.js +7 -0
- package/dist/context.d.ts +10 -1
- package/dist/context.js +7 -0
- package/dist/description.d.ts +2 -0
- package/dist/description.js +8 -0
- package/dist/electron-config.d.ts +62 -0
- package/dist/electron-config.js +134 -0
- package/dist/electron-host.d.ts +7 -0
- package/dist/electron-host.js +54 -0
- package/dist/electron-native.d.ts +15 -0
- package/dist/electron-native.js +73 -0
- package/dist/electron.d.ts +4 -9
- package/dist/electron.js +85 -133
- package/dist/index.js +21 -0
- package/dist/native.d.ts +9 -24
- package/dist/native.js +48 -106
- package/dist/project-vite.d.ts +3 -0
- package/dist/project-vite.js +21 -0
- package/dist/resolve-tool-packages.d.ts +1 -0
- package/dist/resolve-tool-packages.js +29 -0
- package/dist/steam-upload.d.ts +27 -0
- package/dist/steam-upload.js +131 -0
- package/dist/steam.d.ts +22 -0
- package/dist/steam.js +193 -0
- package/dist/tool-packages.d.ts +15 -0
- package/dist/tool-packages.js +74 -0
- package/dist/web.js +10 -4
- package/dist/xbox.d.ts +1 -1
- package/dist/xbox.js +60 -57
- package/docs/capacitor.md +201 -0
- package/docs/native-bridge.md +147 -0
- package/docs/steam.md +384 -0
- package/docs/xbox.md +145 -0
- package/package.json +9 -12
- package/templates/electron/entitlements.plist +12 -0
- package/templates/electron/icons/icon.icns +0 -0
- package/templates/electron/icons/icon.ico +0 -0
- package/templates/electron/icons/icon.png +0 -0
- package/templates/electron/index.js +119 -0
- package/templates/electron/local-assets.cjs +24 -0
- package/templates/electron/native-bridge.cjs +98 -0
- package/templates/electron/package.json +11 -0
- package/templates/electron/preload.cjs +11 -0
- package/templates/xbox/src/platform/StbTextRasterizer.cpp +14 -10
- package/templates/xbox/tests/check_local.sh +12 -4
- package/templates/xbox/tests/stb_safety_test.cpp +126 -0
- package/templates/xbox/third_party/LICENSES.md +15 -0
- package/templates/xbox/third_party/stb_image.h +15 -10
- package/templates/xbox/third_party/stb_truetype.h +4 -4
- 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
|
[](https://github.com/Next2D/builder/actions/workflows/github-code-scanning/codeql)
|
|
5
4
|
[](https://github.com/Next2D/builder/actions/workflows/lint.yml)
|
|
6
5
|
[](https://github.com/Next2D/builder/releases)
|
|
7
6
|
[](https://github.com/Next2D/builder/blob/main/LICENSE)
|
|
8
7
|
|
|
9
|
-
[日本語]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
55
|
-
(published from the `build-v8` workflow). To use your own build, pass
|
|
56
|
-
`--v8-root C:\path\to\v8`.
|
|
51
|
+
### プレビュー例
|
|
57
52
|
|
|
58
|
-
|
|
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
|
-
|
|
|
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
|
-
###
|
|
96
|
+
### Build example
|
|
65
97
|
|
|
66
|
-
```
|
|
98
|
+
```sh
|
|
67
99
|
npx @next2d/builder --platform web --env prd
|
|
68
100
|
```
|
|
69
101
|
|
|
70
|
-
###
|
|
102
|
+
### Preview example
|
|
71
103
|
|
|
72
|
-
```
|
|
104
|
+
```sh
|
|
73
105
|
npx @next2d/builder --preview --platform web --env prd
|
|
74
106
|
```
|
|
75
107
|
|
|
76
|
-
|
|
77
|
-
|
|
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 "
|
|
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
|
-
|
|
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);
|
package/dist/colors.d.ts
ADDED
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:
|
|
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,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
|
+
};
|