@mirinjs/cli 0.0.1-alpha.9 → 0.1.0-alpha.1
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/package.json +5 -3
- package/src/artifacts.ts +27 -8
- package/src/build.ts +105 -24
- package/src/bundle-win.ts +118 -0
- package/src/bundle.ts +159 -14
- package/src/dev.ts +100 -24
- package/src/dmg.ts +236 -0
- package/src/extras.ts +61 -0
- package/src/icon-win.ts +81 -0
- package/src/index.ts +8 -1
- package/src/installer-win.ts +166 -0
- package/src/release.ts +192 -0
- package/src/temps.ts +24 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows installer (NSIS) for `mirin release` — the Windows analogue of `dmg.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Generates an NSIS script from the app folder + config and compiles it with
|
|
5
|
+
* `makensis` into a single `…-setup.exe`. The installer copies the app into
|
|
6
|
+
* Program Files (per-machine) or `%LOCALAPPDATA%\Programs` (per-user, the
|
|
7
|
+
* default — no elevation, and the in-app updater can swap the folder), creates
|
|
8
|
+
* Start Menu + Desktop shortcuts, writes an uninstaller, and registers an
|
|
9
|
+
* Add/Remove Programs entry. Customizable via {@link NsisConfig}.
|
|
10
|
+
*
|
|
11
|
+
* Requires `makensis` (NSIS 3+) on PATH; the caller checks `hasMakensis()` and
|
|
12
|
+
* falls back to the portable `.zip` when it's absent.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { $ } from "bun";
|
|
16
|
+
import { existsSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { join, isAbsolute } from "node:path";
|
|
18
|
+
import type { NsisConfig } from "mirinjs";
|
|
19
|
+
import { makeWindowsIcon } from "./icon-win.ts";
|
|
20
|
+
|
|
21
|
+
export interface BuildNsisInput {
|
|
22
|
+
/** The assembled app folder (build/<App>). */
|
|
23
|
+
appDir: string;
|
|
24
|
+
appName: string;
|
|
25
|
+
/** Host exe name inside the folder (<App>.exe). */
|
|
26
|
+
exeName: string;
|
|
27
|
+
version: string;
|
|
28
|
+
/** Reverse-DNS app id → the Add/Remove Programs registry key. */
|
|
29
|
+
bundleId: string;
|
|
30
|
+
/** Output directory (build/release). */
|
|
31
|
+
outDir: string;
|
|
32
|
+
/** Output file name (e.g. stable-win32-x64-Anko-setup.exe). */
|
|
33
|
+
fileName: string;
|
|
34
|
+
options: NsisConfig;
|
|
35
|
+
/** Project root, for resolving a relative license / installerIcon. */
|
|
36
|
+
projectDir: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Whether `makensis` is on PATH. */
|
|
40
|
+
export async function hasMakensis(): Promise<boolean> {
|
|
41
|
+
return (await $`makensis -VERSION`.quiet().nothrow()).exitCode === 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** NSIS double-quoted string escaping (`"` and `$` are special). */
|
|
45
|
+
function nsis(s: string): string {
|
|
46
|
+
return s.replace(/\$/g, "$$$$").replace(/"/g, '$\\"');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const resolve = (projectDir: string, p: string) => (isAbsolute(p) ? p : join(projectDir, p));
|
|
50
|
+
|
|
51
|
+
/** Build the installer with `makensis`, returning the setup.exe path. */
|
|
52
|
+
export async function buildNsisInstaller(input: BuildNsisInput): Promise<string> {
|
|
53
|
+
const { appDir, appName, exeName, version, bundleId, outDir, fileName, options, projectDir } = input;
|
|
54
|
+
const out = join(outDir, fileName);
|
|
55
|
+
rmSync(out, { force: true });
|
|
56
|
+
|
|
57
|
+
const perMachine = options.perMachine === true;
|
|
58
|
+
const assisted = options.oneClick !== true;
|
|
59
|
+
const desktop = options.desktopShortcut !== false;
|
|
60
|
+
const startMenu = options.startMenuShortcut !== false;
|
|
61
|
+
const runAfter = options.runAfterFinish !== false;
|
|
62
|
+
const changeDir = options.allowChangeInstallDir !== false;
|
|
63
|
+
const publisher = options.publisher ?? appName;
|
|
64
|
+
const root = perMachine ? "HKLM" : "HKCU";
|
|
65
|
+
const installDir =
|
|
66
|
+
options.installDir ?? (perMachine ? `$PROGRAMFILES64\\${appName}` : `$LOCALAPPDATA\\Programs\\${appName}`);
|
|
67
|
+
|
|
68
|
+
// Installer/uninstaller icon: an explicit .ico, else the app's bundled icon.ico.
|
|
69
|
+
let icon: string | undefined;
|
|
70
|
+
if (options.installerIcon) {
|
|
71
|
+
icon = makeWindowsIcon(resolve(projectDir, options.installerIcon), join(outDir, "_installer.ico"));
|
|
72
|
+
} else if (existsSync(join(appDir, "icon.ico"))) {
|
|
73
|
+
icon = join(appDir, "icon.ico");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const license = options.license ? resolve(projectDir, options.license) : undefined;
|
|
77
|
+
if (license && !existsSync(license)) throw new Error(`nsis: license not found: ${license}`);
|
|
78
|
+
|
|
79
|
+
const L: string[] = [];
|
|
80
|
+
L.push("Unicode True");
|
|
81
|
+
L.push("SetCompressor /SOLID lzma");
|
|
82
|
+
L.push('!include "MUI2.nsh"');
|
|
83
|
+
L.push('!include "FileFunc.nsh"');
|
|
84
|
+
L.push(`Name "${nsis(appName)}"`);
|
|
85
|
+
L.push(`OutFile "${nsis(out)}"`);
|
|
86
|
+
L.push(`InstallDir "${installDir}"`);
|
|
87
|
+
L.push(`RequestExecutionLevel ${perMachine ? "admin" : "user"}`);
|
|
88
|
+
L.push(`!define MUI_ABORTWARNING`);
|
|
89
|
+
if (icon) {
|
|
90
|
+
L.push(`!define MUI_ICON "${nsis(icon)}"`);
|
|
91
|
+
L.push(`!define MUI_UNICON "${nsis(icon)}"`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Pages. Assisted shows the full wizard; one-click is just the progress page.
|
|
95
|
+
if (assisted) L.push("!insertmacro MUI_PAGE_WELCOME");
|
|
96
|
+
if (license) L.push(`!insertmacro MUI_PAGE_LICENSE "${nsis(license)}"`);
|
|
97
|
+
if (assisted && changeDir) L.push("!insertmacro MUI_PAGE_DIRECTORY");
|
|
98
|
+
if (assisted && runAfter) {
|
|
99
|
+
L.push(`!define MUI_FINISHPAGE_RUN "$INSTDIR\\${nsis(exeName)}"`);
|
|
100
|
+
}
|
|
101
|
+
L.push("!insertmacro MUI_PAGE_INSTFILES");
|
|
102
|
+
if (assisted) L.push("!insertmacro MUI_PAGE_FINISH");
|
|
103
|
+
L.push("!insertmacro MUI_UNPAGE_CONFIRM");
|
|
104
|
+
L.push("!insertmacro MUI_UNPAGE_INSTFILES");
|
|
105
|
+
L.push('!insertmacro MUI_LANGUAGE "English"');
|
|
106
|
+
|
|
107
|
+
if (options.include) L.push("", "; --- user include ---", options.include, "");
|
|
108
|
+
|
|
109
|
+
const key = `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${bundleId}`;
|
|
110
|
+
const ctx = perMachine ? "all" : "current";
|
|
111
|
+
|
|
112
|
+
// Install section.
|
|
113
|
+
L.push("Section");
|
|
114
|
+
L.push(` SetShellVarContext ${ctx}`);
|
|
115
|
+
L.push(` SetOutPath "$INSTDIR"`);
|
|
116
|
+
L.push(` File /r "${nsis(appDir)}\\*"`);
|
|
117
|
+
L.push(` WriteUninstaller "$INSTDIR\\Uninstall.exe"`);
|
|
118
|
+
if (desktop) {
|
|
119
|
+
L.push(` CreateShortcut "$DESKTOP\\${nsis(appName)}.lnk" "$INSTDIR\\${nsis(exeName)}"`);
|
|
120
|
+
}
|
|
121
|
+
if (startMenu) {
|
|
122
|
+
L.push(` CreateDirectory "$SMPROGRAMS\\${nsis(appName)}"`);
|
|
123
|
+
L.push(
|
|
124
|
+
` CreateShortcut "$SMPROGRAMS\\${nsis(appName)}\\${nsis(appName)}.lnk" "$INSTDIR\\${nsis(exeName)}"`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
// Estimated size for Add/Remove Programs.
|
|
128
|
+
L.push(` \${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2`);
|
|
129
|
+
L.push(` IntFmt $0 "0x%08X" $0`);
|
|
130
|
+
const reg = (name: string, val: string) => L.push(` WriteRegStr ${root} "${key}" "${name}" "${val}"`);
|
|
131
|
+
reg("DisplayName", nsis(appName));
|
|
132
|
+
reg("DisplayVersion", nsis(version));
|
|
133
|
+
reg("Publisher", nsis(publisher));
|
|
134
|
+
reg("DisplayIcon", `$INSTDIR\\${nsis(exeName)}`);
|
|
135
|
+
reg("InstallLocation", "$INSTDIR");
|
|
136
|
+
reg("UninstallString", '$INSTDIR\\Uninstall.exe');
|
|
137
|
+
reg("QuietUninstallString", '$INSTDIR\\Uninstall.exe /S');
|
|
138
|
+
L.push(` WriteRegDWORD ${root} "${key}" "NoModify" 1`);
|
|
139
|
+
L.push(` WriteRegDWORD ${root} "${key}" "NoRepair" 1`);
|
|
140
|
+
L.push(` WriteRegDWORD ${root} "${key}" "EstimatedSize" $0`);
|
|
141
|
+
if (!assisted && runAfter) L.push(` Exec '"$INSTDIR\\${nsis(exeName)}"'`);
|
|
142
|
+
L.push("SectionEnd");
|
|
143
|
+
|
|
144
|
+
// Uninstall section.
|
|
145
|
+
L.push("Section \"Uninstall\"");
|
|
146
|
+
L.push(` SetShellVarContext ${ctx}`);
|
|
147
|
+
if (desktop) L.push(` Delete "$DESKTOP\\${nsis(appName)}.lnk"`);
|
|
148
|
+
if (startMenu) {
|
|
149
|
+
L.push(` Delete "$SMPROGRAMS\\${nsis(appName)}\\${nsis(appName)}.lnk"`);
|
|
150
|
+
L.push(` RMDir "$SMPROGRAMS\\${nsis(appName)}"`);
|
|
151
|
+
}
|
|
152
|
+
L.push(` RMDir /r "$INSTDIR"`);
|
|
153
|
+
L.push(` DeleteRegKey ${root} "${key}"`);
|
|
154
|
+
L.push("SectionEnd");
|
|
155
|
+
|
|
156
|
+
const script = join(outDir, "_installer.nsi");
|
|
157
|
+
writeFileSync(script, L.join("\n") + "\n");
|
|
158
|
+
console.log(`[mirin release] compiling NSIS installer → ${fileName}`);
|
|
159
|
+
const res = await $`makensis -V2 ${script}`.nothrow();
|
|
160
|
+
rmSync(script, { force: true });
|
|
161
|
+
rmSync(join(outDir, "_installer.ico"), { force: true });
|
|
162
|
+
if (res.exitCode !== 0 || !existsSync(out)) {
|
|
163
|
+
throw new Error(`makensis failed (exit ${res.exitCode})`);
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
package/src/release.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mirin release` — build the app and emit flat-named update artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Produces, under `build/release/`:
|
|
5
|
+
* {channel}-{platform}-{arch}-update.json the manifest the app polls
|
|
6
|
+
* {channel}-{platform}-{arch}-{Name}.app.tar.zst the full bundle (fallback)
|
|
7
|
+
* {channel}-{platform}-{arch}-{prevVersion}.patch delta from the previous release
|
|
8
|
+
*
|
|
9
|
+
* The bundle is a zstd-compressed tar of the whole signed `.app`. Identity is the
|
|
10
|
+
* SHA-256 of the *uncompressed* tar (`tarHash`), so a delta patch can reconstruct
|
|
11
|
+
* it exactly. When the previous release is reachable at `baseUrl`, a bsdiff patch
|
|
12
|
+
* (prev → this) is generated; the app applies it instead of re-downloading the
|
|
13
|
+
* whole bundle, falling back to the full bundle whenever a patch isn't usable.
|
|
14
|
+
*
|
|
15
|
+
* Names are flat (no folders) so they upload as-is to GitHub Releases, S3/R2, or
|
|
16
|
+
* any static host. Channels coexist because the channel is part of every name.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { $ } from "bun";
|
|
20
|
+
import { mkdirSync, rmSync, readFileSync, existsSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { build } from "./build.ts";
|
|
24
|
+
import { buildDmg, notarizeAndStaple, type DmgOptions } from "./dmg.ts";
|
|
25
|
+
import { buildNsisInstaller, hasMakensis } from "./installer-win.ts";
|
|
26
|
+
import { loadCodec } from "mirinjs/codec";
|
|
27
|
+
|
|
28
|
+
const sha256File = (path: string) =>
|
|
29
|
+
new Bun.CryptoHasher("sha256").update(readFileSync(path)).digest("hex");
|
|
30
|
+
|
|
31
|
+
export async function release(projectDir = process.cwd()): Promise<number> {
|
|
32
|
+
const result = await build(projectDir);
|
|
33
|
+
if (!result.baseUrl) {
|
|
34
|
+
console.error("[mirin release] no `release.baseUrl` in mirin.config.ts — nothing to publish.");
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isWindows = process.platform === "win32";
|
|
39
|
+
const platform = isWindows ? "win32" : "darwin";
|
|
40
|
+
const arch = process.arch; // "arm64" | "x64"
|
|
41
|
+
const prefix = `${result.channel}-${platform}-${arch}`;
|
|
42
|
+
const safeName = result.appName.replace(/[^A-Za-z0-9._-]/g, "");
|
|
43
|
+
const base = result.baseUrl.replace(/\/$/, "");
|
|
44
|
+
// The packaged unit: a flat app folder on Windows, an `.app` bundle on macOS.
|
|
45
|
+
const appArtifact = isWindows ? result.appName : `${result.appName}.app`;
|
|
46
|
+
|
|
47
|
+
const buildDir = join(projectDir, "build");
|
|
48
|
+
const outDir = join(buildDir, "release");
|
|
49
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
50
|
+
mkdirSync(outDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// Notarize + staple the .app (Developer ID, macOS) before packing, when
|
|
53
|
+
// credentials are present. The .tar.zst / patch updater bundles are made from
|
|
54
|
+
// this signed, stapled .app, so the updater swaps in an already-notarized app.
|
|
55
|
+
if (!isWindows) await notarizeAndStaple(result.app);
|
|
56
|
+
|
|
57
|
+
// Load the codec (zstd/bsdiff) from the *bundled* core, not the raw build output:
|
|
58
|
+
// on Windows mirin_core.dll imports libcef.dll. The DLL loader resolves imports
|
|
59
|
+
// from PATH (and the loader exe's dir), not the dll's own dir, so prepend the
|
|
60
|
+
// bundle dir to PATH so libcef.dll is found. (On macOS the framework loads at
|
|
61
|
+
// runtime, so result.coreDylib works directly.)
|
|
62
|
+
const codecCore = isWindows ? join(result.app, "mirin_core.dll") : result.coreDylib;
|
|
63
|
+
if (isWindows) process.env.PATH = `${result.app};${process.env.PATH ?? ""}`;
|
|
64
|
+
const codec = loadCodec(codecCore);
|
|
65
|
+
|
|
66
|
+
// Uncompressed tar — the identity + diff/patch basis (BSD tar keeps symlinks).
|
|
67
|
+
const newTar = join(outDir, "_new.tar");
|
|
68
|
+
await $`tar -cf ${newTar} -C ${buildDir} ${appArtifact}`;
|
|
69
|
+
const tarHash = sha256File(newTar);
|
|
70
|
+
|
|
71
|
+
// Full bundle: zstd(newTar).
|
|
72
|
+
const bundleName = `${prefix}-${safeName}${isWindows ? "" : ".app"}.tar.zst`;
|
|
73
|
+
const bundlePath = join(outDir, bundleName);
|
|
74
|
+
console.log(`[mirin release] compressing → ${bundleName}`);
|
|
75
|
+
codec.compress(newTar, bundlePath, 19);
|
|
76
|
+
const bundleSha = sha256File(bundlePath);
|
|
77
|
+
const bundleSize = readFileSync(bundlePath).byteLength;
|
|
78
|
+
|
|
79
|
+
// Delta patch vs the previous release (if reachable). Best-effort.
|
|
80
|
+
const patches: Array<{ fromVersion: string; url: string; sha256: string; size: number }> = [];
|
|
81
|
+
try {
|
|
82
|
+
const prevRes = await fetch(`${base}/${prefix}-update.json?t=${Date.now()}`, { redirect: "follow" });
|
|
83
|
+
if (prevRes.ok) {
|
|
84
|
+
const prev = (await prevRes.json()) as { version: string; bundle?: { url: string } };
|
|
85
|
+
if (prev.version && prev.version !== result.version && prev.bundle?.url) {
|
|
86
|
+
console.log(`[mirin release] generating delta ${prev.version} → ${result.version}…`);
|
|
87
|
+
const tmp = join(tmpdir(), `mirin-release-${Date.now()}`);
|
|
88
|
+
mkdirSync(tmp, { recursive: true });
|
|
89
|
+
const prevZst = join(tmp, "prev.tar.zst");
|
|
90
|
+
const dl = await fetch(`${base}/${prev.bundle.url}`, { redirect: "follow" });
|
|
91
|
+
if (!dl.ok || !dl.body) throw new Error(`prev bundle ${dl.status}`);
|
|
92
|
+
await Bun.write(prevZst, await dl.arrayBuffer());
|
|
93
|
+
const prevTar = join(tmp, "prev.tar");
|
|
94
|
+
codec.decompress(prevZst, prevTar);
|
|
95
|
+
const rawPatch = join(tmp, "patch.bin");
|
|
96
|
+
codec.diff(prevTar, newTar, rawPatch); // bsdiff
|
|
97
|
+
const patchName = `${prefix}-${prev.version}.patch`;
|
|
98
|
+
const patchPath = join(outDir, patchName);
|
|
99
|
+
codec.compress(rawPatch, patchPath, 19);
|
|
100
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
101
|
+
patches.push({
|
|
102
|
+
fromVersion: prev.version,
|
|
103
|
+
url: patchName,
|
|
104
|
+
sha256: sha256File(patchPath),
|
|
105
|
+
size: readFileSync(patchPath).byteLength,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.warn(`[mirin release] skipping delta patch: ${e instanceof Error ? e.message : e}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
rmSync(newTar, { force: true });
|
|
114
|
+
|
|
115
|
+
const manifest = {
|
|
116
|
+
version: result.version,
|
|
117
|
+
channel: result.channel,
|
|
118
|
+
platform,
|
|
119
|
+
arch,
|
|
120
|
+
tarHash,
|
|
121
|
+
bundle: { url: bundleName, sha256: bundleSha, size: bundleSize },
|
|
122
|
+
patches,
|
|
123
|
+
};
|
|
124
|
+
const manifestName = `${prefix}-update.json`;
|
|
125
|
+
await Bun.write(join(outDir, manifestName), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
126
|
+
|
|
127
|
+
// Distributable installer for first-time installs (the updater uses the
|
|
128
|
+
// .tar.zst/patch): a drag-to-Applications .dmg on macOS, a .zip of the app
|
|
129
|
+
// folder on Windows.
|
|
130
|
+
let installerName: string | undefined;
|
|
131
|
+
let installerSize = 0;
|
|
132
|
+
if (isWindows) {
|
|
133
|
+
// Preferred: a real NSIS installer (Program Files / per-user install, Start
|
|
134
|
+
// Menu + Desktop shortcuts, uninstaller, Add/Remove Programs). Falls back to a
|
|
135
|
+
// portable .zip when nsis is disabled or makensis isn't installed.
|
|
136
|
+
const wantsInstaller = result.nsis !== false;
|
|
137
|
+
if (wantsInstaller && (await hasMakensis())) {
|
|
138
|
+
installerName = `${prefix}-${safeName}-setup.exe`;
|
|
139
|
+
const exePath = await buildNsisInstaller({
|
|
140
|
+
appDir: result.app,
|
|
141
|
+
appName: result.appName,
|
|
142
|
+
exeName: `${result.appName}.exe`,
|
|
143
|
+
version: result.version,
|
|
144
|
+
bundleId: result.bundleId,
|
|
145
|
+
outDir,
|
|
146
|
+
fileName: installerName,
|
|
147
|
+
options: typeof result.nsis === "object" ? result.nsis : {},
|
|
148
|
+
projectDir: result.projectDir,
|
|
149
|
+
});
|
|
150
|
+
installerSize = readFileSync(exePath).byteLength;
|
|
151
|
+
} else {
|
|
152
|
+
if (wantsInstaller) {
|
|
153
|
+
console.warn(
|
|
154
|
+
"[mirin release] makensis (NSIS) not found — shipping the portable .zip. " +
|
|
155
|
+
"Install NSIS (e.g. `scoop install nsis`) for an installer.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
installerName = `${prefix}-${safeName}.zip`;
|
|
159
|
+
console.log(`[mirin release] zipping → ${installerName}`);
|
|
160
|
+
const zipPath = join(outDir, installerName);
|
|
161
|
+
rmSync(zipPath, { force: true });
|
|
162
|
+
// bsdtar (Windows `tar`) creates a .zip from the extension with `-a`.
|
|
163
|
+
await $`tar -a -cf ${zipPath} -C ${buildDir} ${appArtifact}`;
|
|
164
|
+
installerSize = readFileSync(zipPath).byteLength;
|
|
165
|
+
}
|
|
166
|
+
} else if (result.dmg !== false) {
|
|
167
|
+
installerName = `${prefix}-${safeName}.dmg`;
|
|
168
|
+
console.log(`[mirin release] building installer → ${installerName}`);
|
|
169
|
+
const options: DmgOptions = typeof result.dmg === "object" ? result.dmg : {};
|
|
170
|
+
const dmgPath = await buildDmg({
|
|
171
|
+
app: result.app,
|
|
172
|
+
appName: result.appName,
|
|
173
|
+
outDir,
|
|
174
|
+
fileName: installerName,
|
|
175
|
+
options,
|
|
176
|
+
projectDir: result.projectDir,
|
|
177
|
+
signIdentity: result.signIdentity,
|
|
178
|
+
});
|
|
179
|
+
await notarizeAndStaple(dmgPath); // no-op without notary credentials
|
|
180
|
+
installerSize = readFileSync(dmgPath).byteLength;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const mb = (n: number) => (n / 1e6).toFixed(1);
|
|
184
|
+
console.log(`\n[mirin release] done → build/release/`);
|
|
185
|
+
console.log(` ${manifestName}`);
|
|
186
|
+
console.log(` ${bundleName} (${mb(bundleSize)} MB)`);
|
|
187
|
+
for (const p of patches) console.log(` ${p.url} (${mb(p.size)} MB delta from ${p.fromVersion})`);
|
|
188
|
+
if (installerName) console.log(` ${installerName} (${mb(installerSize)} MB installer)`);
|
|
189
|
+
console.log(`\nUpload all of build/release/ to: ${result.baseUrl}`);
|
|
190
|
+
if (existsSync(join(outDir, "_new.tar"))) rmSync(join(outDir, "_new.tar"), { force: true });
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
package/src/temps.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove `bun build --compile` scratch files (`.*.bun-build`) from a directory.
|
|
3
|
+
*
|
|
4
|
+
* `bun build --compile` writes these temp files into the working directory and
|
|
5
|
+
* normally cleans them up — but a hard-killed build leaves them behind, where
|
|
6
|
+
* they accumulate in the project root. The dev/build commands sweep them first.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdirSync, rmSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
export function sweepBuildTemps(dir: string): void {
|
|
13
|
+
let entries: string[];
|
|
14
|
+
try {
|
|
15
|
+
entries = readdirSync(dir);
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const name of entries) {
|
|
20
|
+
if (name.endsWith(".bun-build")) {
|
|
21
|
+
rmSync(join(dir, name), { force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|