@next2d/builder 0.1.11 → 0.2.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 +88 -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 +52 -0
- package/dist/electron-config.js +116 -0
- package/dist/electron-host.d.ts +5 -0
- package/dist/electron-host.js +42 -0
- package/dist/electron.d.ts +4 -9
- package/dist/electron.js +83 -132
- 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/steam.md +714 -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 +95 -0
- package/templates/electron/local-assets.cjs +24 -0
- package/templates/electron/package.json +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
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { readElectronConfig, validateSteamId } from "./electron-config.js";
|
|
5
|
+
import { quoteSteamValue as quote, readSteamPackage } from "./steam.js";
|
|
6
|
+
export const validateSteamBranch = (branch) => {
|
|
7
|
+
if (typeof branch !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(branch)
|
|
8
|
+
|| ["default", "public", "none"].includes(branch.toLowerCase())) {
|
|
9
|
+
throw new Error("Steam upload requires a named beta branch such as internal (letters, numbers, - or _). The default branch is not supported.");
|
|
10
|
+
}
|
|
11
|
+
return branch;
|
|
12
|
+
};
|
|
13
|
+
/** Recreate manifests from validated package records, never from stale or edited VDF files. */
|
|
14
|
+
export const prepareSteamUpload = (options) => {
|
|
15
|
+
const root = path.resolve(options.root);
|
|
16
|
+
const steamRoot = path.resolve(root, options.steamRoot);
|
|
17
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(options.environment)) {
|
|
18
|
+
throw new Error("Steam upload --env must contain only letters, numbers, - or _.");
|
|
19
|
+
}
|
|
20
|
+
const config = readElectronConfig(root);
|
|
21
|
+
const branch = validateSteamBranch(options.branch ?? config.steam?.branch ?? "internal");
|
|
22
|
+
if (!config.steam?.appId) {
|
|
23
|
+
throw new Error("Set steam.appId and steam.depots in electron.config.json before uploading.");
|
|
24
|
+
}
|
|
25
|
+
const version = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
26
|
+
if (typeof version !== "string" || !version) {
|
|
27
|
+
throw new Error("Set package.json version before uploading.");
|
|
28
|
+
}
|
|
29
|
+
if (options.comment !== undefined && (typeof options.comment !== "string" || !options.comment.trim()
|
|
30
|
+
|| /["\\\u0000-\u001f\u007f]/.test(options.comment))) {
|
|
31
|
+
throw new Error("Steam upload --steam-comment must be nonempty single-line text without double quotes, backslashes or control characters.");
|
|
32
|
+
}
|
|
33
|
+
const comment = options.comment ?? `${config.appName} ${version} (${options.environment}, ${branch})`;
|
|
34
|
+
const quotedComment = quote(comment);
|
|
35
|
+
const groups = new Map();
|
|
36
|
+
for (const os of ["windows", "macos", "linux"]) {
|
|
37
|
+
const id = config.steam.depots[os];
|
|
38
|
+
if (id === null || id === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const depot = validateSteamId(id);
|
|
42
|
+
try {
|
|
43
|
+
const record = readSteamPackage(steamRoot, options.environment, config, version, os);
|
|
44
|
+
groups.set(depot, [...groups.get(depot) ?? [], { os, ...record }]);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error(`Steam upload: ${os} package is missing or outdated. Export/collect every configured OS first. ${error instanceof Error ? error.message : error}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!groups.size) {
|
|
51
|
+
throw new Error("Set at least one Steam Depot ID in electron.config.json before uploading.");
|
|
52
|
+
}
|
|
53
|
+
// Unique scripts prevent a dry-run or another invocation from overwriting an active upload.
|
|
54
|
+
const uploads = path.join(steamRoot, "uploads", options.environment);
|
|
55
|
+
fs.mkdirSync(uploads, { "recursive": true });
|
|
56
|
+
const scripts = fs.mkdtempSync(path.join(uploads, `${branch}-`));
|
|
57
|
+
const depotFiles = [];
|
|
58
|
+
const launches = [];
|
|
59
|
+
for (const [depot, members] of groups) {
|
|
60
|
+
const mappings = members.map(({ os, content, arch, localExecutable }) => {
|
|
61
|
+
const prefix = members.length > 1 ? `${os}/` : "";
|
|
62
|
+
launches.push({ "platform": `steam:${os}`, arch, "executable": `${prefix}${localExecutable}`, "depotId": depot });
|
|
63
|
+
return ` "FileMapping"\n {\n "LocalPath" ${quote(`${path.relative(steamRoot, content)}/*`)}\n "DepotPath" ${quote(prefix || ".")}\n "Recursive" "1"\n }`;
|
|
64
|
+
});
|
|
65
|
+
const name = `depot_${depot}.vdf`;
|
|
66
|
+
fs.writeFileSync(path.join(scripts, name), `"DepotBuild"\n{\n "DepotID" "${depot}"\n${mappings.join("\n")}\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`);
|
|
67
|
+
depotFiles.push(` "${depot}" ${quote(name)}`);
|
|
68
|
+
}
|
|
69
|
+
const manifest = path.join(scripts, options.dryRun ? "app_preview.vdf" : "app_upload.vdf");
|
|
70
|
+
// A stable cache directory allows SteamPipe to reuse chunks across uploads to a branch.
|
|
71
|
+
const cache = path.join(uploads, "cache", branch);
|
|
72
|
+
fs.writeFileSync(manifest, `"AppBuild"\n{\n "AppID" "${config.steam.appId}"\n "Desc" ${quotedComment}\n`
|
|
73
|
+
+ ` "ContentRoot" ${quote(steamRoot)}\n "BuildOutput" ${quote(cache)}\n "Preview" "${options.dryRun ? "1" : "0"}"\n`
|
|
74
|
+
+ (options.dryRun ? "" : ` "SetLive" ${quote(branch)}\n`)
|
|
75
|
+
+ ` "Depots"\n {\n${depotFiles.join("\n")}\n }\n}\n`);
|
|
76
|
+
const plan = { "appId": config.steam.appId, branch, version, comment, "environment": options.environment, "dryRun": !!options.dryRun, manifest, launches };
|
|
77
|
+
fs.writeFileSync(path.join(scripts, "upload-plan.json"), `${JSON.stringify(plan, null, 2)}\n`);
|
|
78
|
+
return plan;
|
|
79
|
+
};
|
|
80
|
+
/** Use SteamCMD's existing login session; passwords/Steam Guard codes never enter project config. */
|
|
81
|
+
export const uploadSteam = async (options) => {
|
|
82
|
+
const plan = prepareSteamUpload(options);
|
|
83
|
+
console.log(`Steam App ${plan.appId} -> beta branch ${plan.branch}`);
|
|
84
|
+
console.log(`Comment: ${plan.comment}`);
|
|
85
|
+
console.log(`Manifest: ${plan.manifest}`);
|
|
86
|
+
for (const launch of plan.launches) {
|
|
87
|
+
console.log(` Depot ${launch.depotId}: ${launch.platform}/${launch.arch} -> ${launch.executable}`);
|
|
88
|
+
}
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
console.log("Dry run complete: SteamCMD was not started. Branch existence/password protection has not been checked on Steamworks.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const username = process.env.STEAM_USERNAME;
|
|
94
|
+
if (!username || !/^[a-zA-Z0-9_][a-zA-Z0-9_.@-]*$/.test(username)) {
|
|
95
|
+
throw new Error("Set STEAM_USERNAME to your Steam build account name. Log in with SteamCMD once before uploading; see docs/steam.md.");
|
|
96
|
+
}
|
|
97
|
+
const command = process.env.STEAMCMD || (process.platform === "win32" ? "steamcmd.exe" : "steamcmd");
|
|
98
|
+
console.log(`Uploading and requesting activation on ${plan.branch}. Configure the beta password in Steamworks to restrict access.`);
|
|
99
|
+
const buildId = await new Promise((resolve, reject) => {
|
|
100
|
+
let output = "";
|
|
101
|
+
let reportedError = false;
|
|
102
|
+
const child = spawn(command, [
|
|
103
|
+
"+@ShutdownOnFailedCommand", "1", "+@NoPromptForPassword", "1",
|
|
104
|
+
"+login", username, "+run_app_build", plan.manifest, "+quit"
|
|
105
|
+
], { "cwd": path.resolve(options.root), "stdio": ["ignore", "pipe", "pipe"], "shell": false });
|
|
106
|
+
const collect = (chunk, stream) => {
|
|
107
|
+
stream.write(chunk);
|
|
108
|
+
output = (output + chunk.toString()).slice(-1024 * 1024);
|
|
109
|
+
reportedError || (reportedError = /ERROR[!:]|Failed to set[^\r\n]*live/i.test(output));
|
|
110
|
+
};
|
|
111
|
+
child.stdout.on("data", (chunk) => collect(chunk, process.stdout));
|
|
112
|
+
child.stderr.on("data", (chunk) => collect(chunk, process.stderr));
|
|
113
|
+
child.once("error", (error) => reject(new Error(`Cannot start SteamCMD: ${error.message}. Set STEAMCMD to the executable path; see docs/steam.md.`)));
|
|
114
|
+
child.once("close", (code, signal) => {
|
|
115
|
+
if (code !== 0) {
|
|
116
|
+
reject(new Error(`SteamCMD failed (${signal || code}). Inspect its output and SteamPipe logs; check login, app permissions and beta branch settings.`));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const success = output.match(new RegExp(`(?:Success! App '${plan.appId}' fully built|Successfully finished AppID ${plan.appId} build) \\(BuildID ([0-9]+)\\)`, "i"));
|
|
120
|
+
if (reportedError || !success) {
|
|
121
|
+
reject(new Error("SteamCMD did not report a successful app build, or reported an error. Check SteamPipe logs and the branch's active BuildID in Steamworks before retrying."));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
resolve(success[1]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
fs.writeFileSync(path.join(path.dirname(plan.manifest), "upload-result.json"), `${JSON.stringify({
|
|
128
|
+
"appId": plan.appId, buildId, "requestedBranch": plan.branch, "completedAt": new Date().toISOString()
|
|
129
|
+
}, null, 2)}\n`);
|
|
130
|
+
console.log(`Uploaded BuildID ${buildId}. Verify the active ${plan.branch} build at https://partner.steamgames.com/apps/builds/${plan.appId}`);
|
|
131
|
+
};
|
package/dist/steam.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IElectronConfig, ElectronOS } from "./electron-config.js";
|
|
2
|
+
export interface ISteamConfig {
|
|
3
|
+
appId: string;
|
|
4
|
+
depotId: string | null;
|
|
5
|
+
sharedPlatforms?: ElectronOS[];
|
|
6
|
+
}
|
|
7
|
+
export declare const readSteamConfig: (config: IElectronConfig, platform: string) => ISteamConfig | null;
|
|
8
|
+
export declare const quoteSteamValue: (value: string) => string;
|
|
9
|
+
/** Discover the executable from the output, including customized executable names. */
|
|
10
|
+
export declare const findSteamLaunch: (content: string, platform: string, executableName?: string) => string;
|
|
11
|
+
/** Keep scripts outside the content directory so Steam never installs build metadata. */
|
|
12
|
+
export declare const writeSteamMetadata: (content: string, platform: string, arch: string, config: ISteamConfig | null, executableName?: string) => void;
|
|
13
|
+
/** A portable pointer to the most recently exported architecture for this OS/environment. */
|
|
14
|
+
export declare const recordSteamPackage: (content: string, platform: string, arch: string, config: IElectronConfig, version: string) => void;
|
|
15
|
+
/** Validate that an exported package still matches the current project and depot configuration. */
|
|
16
|
+
export declare const readSteamPackage: (steamRoot: string, environment: string, config: IElectronConfig, version: string, os: ElectronOS) => {
|
|
17
|
+
content: string;
|
|
18
|
+
arch: string;
|
|
19
|
+
localExecutable: string;
|
|
20
|
+
};
|
|
21
|
+
/** Combine existing packages with multiple FileMappings, preserving app symlinks and permissions. */
|
|
22
|
+
export declare const writeSharedSteamDepots: (steamRoot: string, environment: string, config: IElectronConfig, version: string) => string[];
|
package/dist/steam.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { validateSteamId } from "./electron-config.js";
|
|
4
|
+
const platforms = ["windows", "macos", "linux"];
|
|
5
|
+
export const readSteamConfig = (config, platform) => {
|
|
6
|
+
if (!config.steam?.appId) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const os = platform.replace(/^steam:/, "");
|
|
10
|
+
const depot = config.steam.depots[os];
|
|
11
|
+
const depotId = depot ? validateSteamId(depot) : null;
|
|
12
|
+
const sharedPlatforms = depotId ? platforms.filter((key) => String(config.steam?.depots[key]) === depotId) : [];
|
|
13
|
+
return { "appId": config.steam.appId, depotId, ...sharedPlatforms.length > 1 ? { sharedPlatforms } : {} };
|
|
14
|
+
};
|
|
15
|
+
export const quoteSteamValue = (value) => {
|
|
16
|
+
if (/["\r\n\0]/.test(value)) {
|
|
17
|
+
throw new Error("Unsupported quote or control character in SteamPipe path.");
|
|
18
|
+
}
|
|
19
|
+
return `"${value.replace(/\\/g, "/")}"`;
|
|
20
|
+
};
|
|
21
|
+
const quote = quoteSteamValue;
|
|
22
|
+
/** Discover the executable from the output, including customized executable names. */
|
|
23
|
+
export const findSteamLaunch = (content, platform, executableName) => {
|
|
24
|
+
const entries = fs.readdirSync(content, { "withFileTypes": true });
|
|
25
|
+
if (platform === "steam:macos") {
|
|
26
|
+
const apps = entries.filter((entry) => entry.isDirectory() && entry.name.endsWith(".app"));
|
|
27
|
+
if (apps.length !== 1) {
|
|
28
|
+
throw new Error("Expected exactly one macOS .app in the packaged folder.");
|
|
29
|
+
}
|
|
30
|
+
return apps[0].name;
|
|
31
|
+
}
|
|
32
|
+
if (executableName) {
|
|
33
|
+
const executable = `${executableName}${platform === "steam:windows" ? ".exe" : ""}`;
|
|
34
|
+
if (!fs.statSync(path.join(content, executable), { "throwIfNoEntry": false })?.isFile()) {
|
|
35
|
+
throw new Error(`Packaged Steam executable is missing: ${executable}`);
|
|
36
|
+
}
|
|
37
|
+
return executable;
|
|
38
|
+
}
|
|
39
|
+
const candidates = entries.filter((entry) => {
|
|
40
|
+
if (!entry.isFile()) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (platform === "steam:windows") {
|
|
44
|
+
return entry.name.endsWith(".exe") && entry.name !== "chrome_crashpad_handler.exe";
|
|
45
|
+
}
|
|
46
|
+
// Electron's other Linux binaries have extensions or known helper names.
|
|
47
|
+
return !entry.name.includes(".") && !["chrome-sandbox", "chrome_crashpad_handler", "LICENSE", "version"].includes(entry.name)
|
|
48
|
+
&& (fs.statSync(path.join(content, entry.name)).mode & 0o111) !== 0;
|
|
49
|
+
});
|
|
50
|
+
if (candidates.length !== 1) {
|
|
51
|
+
throw new Error(`Cannot determine the Steam executable in ${content}`);
|
|
52
|
+
}
|
|
53
|
+
return candidates[0].name;
|
|
54
|
+
};
|
|
55
|
+
/** Keep scripts outside the content directory so Steam never installs build metadata. */
|
|
56
|
+
export const writeSteamMetadata = (content, platform, arch, config, executableName) => {
|
|
57
|
+
const localExecutable = findSteamLaunch(content, platform, executableName);
|
|
58
|
+
const depotPath = config?.sharedPlatforms ? platform.replace("steam:", "") : "";
|
|
59
|
+
const executable = depotPath ? `${depotPath}/${localExecutable}` : localExecutable;
|
|
60
|
+
const scripts = `${content}-steampipe`;
|
|
61
|
+
fs.mkdirSync(scripts, { "recursive": true });
|
|
62
|
+
const metadata = {
|
|
63
|
+
platform, arch, executable,
|
|
64
|
+
localExecutable, depotPath,
|
|
65
|
+
"contentRoot": `../${path.basename(content)}`,
|
|
66
|
+
"workingDirectory": "",
|
|
67
|
+
"arguments": "",
|
|
68
|
+
"appId": config?.appId || null,
|
|
69
|
+
"depotId": config?.depotId || null
|
|
70
|
+
};
|
|
71
|
+
fs.writeFileSync(path.join(scripts, "launch.json"), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
72
|
+
// Remove scripts from an earlier configured run if IDs are no longer configured.
|
|
73
|
+
for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf"]) {
|
|
74
|
+
fs.rmSync(path.join(scripts, name), { "force": true });
|
|
75
|
+
}
|
|
76
|
+
if (!config?.depotId) {
|
|
77
|
+
console.log("Steam package ready. Set steam.appId and steam.depots in electron.config.json to generate SteamPipe VDFs.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Upload a shared depot only through the combined manifest, never one OS at a time.
|
|
81
|
+
if (config.sharedPlatforms) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
fs.writeFileSync(path.join(scripts, "depot_build.vdf"), `"DepotBuild"\n{\n "DepotID" "${config.depotId}"\n "FileMapping"\n {\n "LocalPath" "*"\n "DepotPath" "."\n "Recursive" "1"\n }\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`);
|
|
85
|
+
for (const preview of [false, true]) {
|
|
86
|
+
fs.writeFileSync(path.join(scripts, preview ? "app_preview.vdf" : "app_build.vdf"), `"AppBuild"\n{\n "AppID" "${config.appId}"\n "Desc" ${quote(`${platform} ${arch}`)}\n "ContentRoot" ${quote(metadata.contentRoot)}\n "BuildOutput" "cache"\n "Preview" "${preview ? "1" : "0"}"\n "Depots"\n {\n "${config.depotId}" "depot_build.vdf"\n }\n}\n`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/** A portable pointer to the most recently exported architecture for this OS/environment. */
|
|
90
|
+
export const recordSteamPackage = (content, platform, arch, config, version) => {
|
|
91
|
+
const steam = readSteamConfig(config, platform);
|
|
92
|
+
writeSteamMetadata(content, platform, arch, steam, config.executableName);
|
|
93
|
+
const record = {
|
|
94
|
+
platform, arch, version,
|
|
95
|
+
"appId": steam?.appId ?? null, "depotId": steam?.depotId ?? null,
|
|
96
|
+
"appName": config.appName, "executableName": config.executableName,
|
|
97
|
+
"contentRoot": path.basename(content)
|
|
98
|
+
};
|
|
99
|
+
fs.writeFileSync(path.join(path.dirname(content), "steam-package.json"), `${JSON.stringify(record, null, 2)}\n`);
|
|
100
|
+
};
|
|
101
|
+
/** Validate that an exported package still matches the current project and depot configuration. */
|
|
102
|
+
export const readSteamPackage = (steamRoot, environment, config, version, os) => {
|
|
103
|
+
const output = path.join(steamRoot, os, "build", environment);
|
|
104
|
+
const record = JSON.parse(fs.readFileSync(path.join(output, "steam-package.json"), "utf8"));
|
|
105
|
+
if (typeof record.contentRoot !== "string" || path.basename(record.contentRoot) !== record.contentRoot
|
|
106
|
+
|| /[\\/*?"\r\n\0]/.test(record.contentRoot) || record.contentRoot === "." || record.contentRoot === "..") {
|
|
107
|
+
throw new Error("Invalid package path");
|
|
108
|
+
}
|
|
109
|
+
const content = path.join(output, record.contentRoot);
|
|
110
|
+
if (record.platform !== `steam:${os}` || record.appId !== config.steam?.appId
|
|
111
|
+
|| record.depotId !== validateSteamId(config.steam?.depots[os])
|
|
112
|
+
|| record.version !== version || record.appName !== config.appName || record.executableName !== config.executableName
|
|
113
|
+
|| !(os === "macos" ? ["x64", "arm64", "universal"] : ["x64", "arm64"]).includes(record.arch)) {
|
|
114
|
+
throw new Error("Package metadata no longer matches the project configuration");
|
|
115
|
+
}
|
|
116
|
+
const relativeRealPath = path.relative(fs.realpathSync(steamRoot), fs.realpathSync(content));
|
|
117
|
+
if (path.isAbsolute(relativeRealPath) || relativeRealPath === ".." || relativeRealPath.startsWith(`..${path.sep}`)) {
|
|
118
|
+
throw new Error("Package directory must be inside the Steam output root");
|
|
119
|
+
}
|
|
120
|
+
return { content, "arch": record.arch, "localExecutable": findSteamLaunch(content, `steam:${os}`, config.executableName) };
|
|
121
|
+
};
|
|
122
|
+
/** Combine existing packages with multiple FileMappings, preserving app symlinks and permissions. */
|
|
123
|
+
export const writeSharedSteamDepots = (steamRoot, environment, config, version) => {
|
|
124
|
+
const pending = [];
|
|
125
|
+
const groups = new Map();
|
|
126
|
+
for (const os of platforms) {
|
|
127
|
+
const id = config.steam?.depots[os];
|
|
128
|
+
if (id) {
|
|
129
|
+
const key = validateSteamId(id);
|
|
130
|
+
groups.set(key, [...groups.get(key) ?? [], os]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const sharedRoot = path.join(steamRoot, "shared", environment);
|
|
134
|
+
// Remove obsolete generated VDFs after a depot's membership or IDs change.
|
|
135
|
+
if (fs.existsSync(sharedRoot)) {
|
|
136
|
+
for (const entry of fs.readdirSync(sharedRoot, { "withFileTypes": true })) {
|
|
137
|
+
if (entry.isDirectory() && /^depot-[0-9]+$/.test(entry.name)) {
|
|
138
|
+
for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf", "launch.json"]) {
|
|
139
|
+
fs.rmSync(path.join(sharedRoot, entry.name, name), { "force": true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const [depotId, members] of groups) {
|
|
145
|
+
if (members.length < 2) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const scripts = path.join(sharedRoot, `depot-${depotId}`);
|
|
149
|
+
fs.mkdirSync(scripts, { "recursive": true });
|
|
150
|
+
const mappings = [];
|
|
151
|
+
const launches = [];
|
|
152
|
+
const missing = [];
|
|
153
|
+
for (const os of members) {
|
|
154
|
+
const output = path.join(steamRoot, os, "build", environment);
|
|
155
|
+
// Also invalidate legacy exports created before steam-package.json existed.
|
|
156
|
+
if (fs.existsSync(output)) {
|
|
157
|
+
for (const entry of fs.readdirSync(output, { "withFileTypes": true })) {
|
|
158
|
+
if (entry.isDirectory() && entry.name.startsWith(`${config.appName}-`) && entry.name.endsWith("-steampipe")) {
|
|
159
|
+
for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf"]) {
|
|
160
|
+
fs.rmSync(path.join(output, entry.name, name), { "force": true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const { content, arch, localExecutable } = readSteamPackage(steamRoot, environment, config, version, os);
|
|
167
|
+
const relative = path.relative(steamRoot, content).replace(/\\/g, "/");
|
|
168
|
+
mappings.push(` "FileMapping"\n {\n "LocalPath" ${quote(`${relative}/*`)}\n "DepotPath" "${os}/"\n "Recursive" "1"\n }`);
|
|
169
|
+
launches.push({ "platform": `steam:${os}`, arch, "executable": `${os}/${localExecutable}`, "workingDirectory": "", "arguments": "" });
|
|
170
|
+
writeSteamMetadata(content, `steam:${os}`, arch, readSteamConfig(config, `steam:${os}`), config.executableName);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
missing.push(`${os}: ${error instanceof Error ? error.message : String(error)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
fs.writeFileSync(path.join(scripts, "launch.json"), `${JSON.stringify({
|
|
177
|
+
"appId": config.steam?.appId, depotId, "ready": !missing.length,
|
|
178
|
+
"requiredPlatforms": members, missing, "launches": launches
|
|
179
|
+
}, null, 2)}\n`);
|
|
180
|
+
if (missing.length) {
|
|
181
|
+
pending.push(`Depot ${depotId}: ${missing.join("; ")}`);
|
|
182
|
+
console.log(`Shared Steam depot ${depotId}: waiting for all OS packages. See ${path.join(scripts, "launch.json")}`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
fs.writeFileSync(path.join(scripts, "depot_build.vdf"), `"DepotBuild"\n{\n "DepotID" "${depotId}"\n${mappings.join("\n")}\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`);
|
|
186
|
+
const contentRoot = path.relative(scripts, steamRoot);
|
|
187
|
+
for (const preview of [false, true]) {
|
|
188
|
+
fs.writeFileSync(path.join(scripts, preview ? "app_preview.vdf" : "app_build.vdf"), `"AppBuild"\n{\n "AppID" "${config.steam?.appId}"\n "Desc" ${quote(`Shared depot ${depotId}: ${members.join(", ")} ${version}`)}\n "ContentRoot" ${quote(contentRoot)}\n "BuildOutput" "cache"\n "Preview" "${preview ? "1" : "0"}"\n "Depots"\n {\n "${depotId}" "depot_build.vdf"\n }\n}\n`);
|
|
189
|
+
}
|
|
190
|
+
console.log(`Shared Steam depot ready: ${scripts}`);
|
|
191
|
+
}
|
|
192
|
+
return pending;
|
|
193
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const packages: {
|
|
2
|
+
electron: {
|
|
3
|
+
"@electron/packager": string;
|
|
4
|
+
};
|
|
5
|
+
capacitor: {
|
|
6
|
+
"@capacitor/cli": string;
|
|
7
|
+
"@capacitor/core": string;
|
|
8
|
+
"@capacitor/ios": string;
|
|
9
|
+
"@capacitor/android": string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
type Tool = keyof typeof packages;
|
|
13
|
+
/** Download only on first use; npm retains the packages after the temporary request is removed. */
|
|
14
|
+
export declare const resolveToolPackages: (tool: Tool) => Promise<Record<string, string>>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
// Keep native SDKs together and pin tools independently of the game's dependencies.
|
|
6
|
+
const packages = {
|
|
7
|
+
"electron": { "@electron/packager": "20.3.0" },
|
|
8
|
+
"capacitor": {
|
|
9
|
+
"@capacitor/cli": "8.5.2", "@capacitor/core": "8.5.2",
|
|
10
|
+
"@capacitor/ios": "8.5.2", "@capacitor/android": "8.5.2"
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const pending = new Map();
|
|
14
|
+
/** Launch npm's npx script with Node, avoiding cmd.exe quoting on Windows. */
|
|
15
|
+
const resolveNpxCli = () => {
|
|
16
|
+
const candidates = [
|
|
17
|
+
...process.env.npm_execpath ? [path.join(path.dirname(process.env.npm_execpath), "npx-cli.js")] : [],
|
|
18
|
+
path.join(path.dirname(process.execPath), "node_modules/npm/bin/npx-cli.js"),
|
|
19
|
+
...(process.env.PATH ?? "").split(path.delimiter).flatMap((dir) => [
|
|
20
|
+
path.join(dir, "npx"), path.join(dir, "node_modules/npm/bin/npx-cli.js")
|
|
21
|
+
])
|
|
22
|
+
];
|
|
23
|
+
for (const file of candidates) {
|
|
24
|
+
if (fs.statSync(file, { "throwIfNoEntry": false })?.isFile()) {
|
|
25
|
+
const resolved = fs.realpathSync(file);
|
|
26
|
+
if (path.basename(resolved) === "npx-cli.js") {
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error("npm/npx is required to download build tools. Install Node.js with npm and add it to PATH.");
|
|
32
|
+
};
|
|
33
|
+
const acquire = async (tool) => {
|
|
34
|
+
const cli = resolveNpxCli();
|
|
35
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-tools-"));
|
|
36
|
+
const result = path.join(dir, "packages.json");
|
|
37
|
+
const specs = Object.entries(packages[tool]).map(([name, version]) => `--package=${name}@${version}`);
|
|
38
|
+
try {
|
|
39
|
+
console.log(`Preparing ${tool} tools via npx (${specs.map((spec) => spec.slice(10)).join(", ")})`);
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
const child = spawn(process.execPath, [cli, "--yes", ...specs, "--call",
|
|
42
|
+
"node --input-type=module -e \"import(process.env.NEXT2D_TOOL_RESOLVER)\""
|
|
43
|
+
], {
|
|
44
|
+
// A neutral cwd prevents project dependencies and npm hooks influencing tool acquisition.
|
|
45
|
+
"cwd": dir, "stdio": "inherit",
|
|
46
|
+
"env": {
|
|
47
|
+
...process.env,
|
|
48
|
+
"NEXT2D_TOOL_RESOLVER": new URL("./resolve-tool-packages.js", import.meta.url).href,
|
|
49
|
+
"NEXT2D_TOOL_PACKAGES": JSON.stringify(packages[tool]),
|
|
50
|
+
"NEXT2D_TOOL_RESULT": result
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
child.once("error", reject);
|
|
54
|
+
child.once("close", (code, signal) => code === 0 ? resolve()
|
|
55
|
+
: reject(new Error(`npx could not prepare ${tool} tools (${signal || code}). Check npm connectivity/cache and retry.`)));
|
|
56
|
+
});
|
|
57
|
+
return JSON.parse(fs.readFileSync(result, "utf8"));
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
fs.rmSync(dir, { "recursive": true, "force": true });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/** Download only on first use; npm retains the packages after the temporary request is removed. */
|
|
64
|
+
export const resolveToolPackages = (tool) => {
|
|
65
|
+
let request = pending.get(tool);
|
|
66
|
+
if (!request) {
|
|
67
|
+
request = acquire(tool).catch((error) => {
|
|
68
|
+
pending.delete(tool);
|
|
69
|
+
throw error;
|
|
70
|
+
});
|
|
71
|
+
pending.set(tool, request);
|
|
72
|
+
}
|
|
73
|
+
return request;
|
|
74
|
+
};
|
package/dist/web.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Web ビルド: vite 設定の読み込みと HTML/JavaScript の書き出し。
|
|
2
|
-
import pc from "
|
|
2
|
+
import pc from "./colors.js";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
import
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { loadProjectVite } from "./project-vite.js";
|
|
5
6
|
import { ctx } from "./context.js";
|
|
6
7
|
import { $spawn } from "./utils.js";
|
|
7
8
|
/**
|
|
@@ -20,14 +21,15 @@ export const loadConfig = async () => {
|
|
|
20
21
|
fs.writeFileSync(`${process.cwd()}/package.json`, JSON.stringify(packageJson, null, 2));
|
|
21
22
|
}
|
|
22
23
|
const ext = fs.existsSync(`${process.cwd()}/vite.config.ts`) ? "ts" : "js";
|
|
24
|
+
const { loadConfigFromFile } = await loadProjectVite();
|
|
23
25
|
const config = await loadConfigFromFile({
|
|
24
26
|
"command": "build",
|
|
25
27
|
"mode": "build"
|
|
26
28
|
}, `${process.cwd()}/vite.config.${ext}`);
|
|
27
29
|
// update config
|
|
28
30
|
ctx.configObject = config;
|
|
29
|
-
ctx.outDir = ctx.configObject.
|
|
30
|
-
ctx.buildDir =
|
|
31
|
+
ctx.outDir = ctx.configObject?.config.build?.outDir || "dist";
|
|
32
|
+
ctx.buildDir = path.resolve(ctx.outDir, ctx.platformDir, ctx.environment);
|
|
31
33
|
if (!fs.existsSync(`${ctx.buildDir}`)) {
|
|
32
34
|
fs.mkdirSync(`${ctx.buildDir}`, { "recursive": true });
|
|
33
35
|
console.log(pc.green(`Create build directory: ${ctx.buildDir}`));
|
|
@@ -47,9 +49,11 @@ export const buildWeb = () => {
|
|
|
47
49
|
const stream = $spawn("npx", [
|
|
48
50
|
"@next2d/vite-plugin-next2d-auto-loader"
|
|
49
51
|
], { "stdio": "inherit" });
|
|
52
|
+
stream.once("error", reject);
|
|
50
53
|
stream.on("close", (code) => {
|
|
51
54
|
if (code !== 0) {
|
|
52
55
|
reject("vite plugin command failed.");
|
|
56
|
+
return;
|
|
53
57
|
}
|
|
54
58
|
const stream = $spawn("npx", [
|
|
55
59
|
"vite",
|
|
@@ -57,9 +61,11 @@ export const buildWeb = () => {
|
|
|
57
61
|
ctx.buildDir,
|
|
58
62
|
"build"
|
|
59
63
|
], { "stdio": "inherit" });
|
|
64
|
+
stream.once("error", reject);
|
|
60
65
|
stream.on("close", (code) => {
|
|
61
66
|
if (code !== 0) {
|
|
62
67
|
reject("Export of `HTML` and `JavaScript` failed.");
|
|
68
|
+
return;
|
|
63
69
|
}
|
|
64
70
|
console.log();
|
|
65
71
|
console.log(pc.green("`HTML` and `JavaScript` files are written out."));
|
package/dist/xbox.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export declare const renderAssetsRc: (pakAbsPath: string) => string;
|
|
|
49
49
|
* @method
|
|
50
50
|
* @public
|
|
51
51
|
*/
|
|
52
|
-
export declare const minifyJs: (name: string, code: string) => string
|
|
52
|
+
export declare const minifyJs: (name: string, code: string) => Promise<string>;
|
|
53
53
|
/**
|
|
54
54
|
* @description Xbox(GDKネイティブ)用アプリの書き出し関数。
|
|
55
55
|
* V8にNext2DのJSを載せ、Dawn(WebGPU/D3D12)で描画するC++ホストをビルドする。
|