@mirinjs/cli 0.1.0-alpha.1 → 0.1.0-alpha.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirinjs/cli",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "CLI for mirin apps: dev, build, init.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,12 +27,12 @@
27
27
  "bun": ">=1.2.0"
28
28
  },
29
29
  "dependencies": {
30
- "create-mirinjs": "0.1.0-alpha.1",
31
- "mirinjs": "0.1.0-alpha.1"
30
+ "create-mirinjs": "0.1.0-alpha.2",
31
+ "mirinjs": "0.1.0-alpha.2"
32
32
  },
33
33
  "optionalDependencies": {
34
- "@mirinjs/darwin-arm64": "0.1.0-alpha.1",
35
- "@mirinjs/win32-x64": "0.1.0-alpha.1"
34
+ "@mirinjs/darwin-arm64": "0.1.0-alpha.2",
35
+ "@mirinjs/win32-x64": "0.1.0-alpha.2"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
package/src/dmg.ts CHANGED
@@ -93,6 +93,34 @@ export async function notarizeAndStaple(target: string): Promise<boolean> {
93
93
  return true;
94
94
  }
95
95
 
96
+ /**
97
+ * `hdiutil create` with retries. "Resource busy" is a transient CI flake — a
98
+ * Spotlight/antivirus/`fseventsd` process touches the staging folder (or a prior
99
+ * image hasn't fully detached) while hdiutil tries to read it. A short backoff
100
+ * clears it; only a non-busy error fails immediately.
101
+ */
102
+ async function hdiutilCreate(
103
+ vol: string,
104
+ staging: string,
105
+ format: string,
106
+ dmgPath: string,
107
+ ): Promise<void> {
108
+ let stderr = "";
109
+ for (let attempt = 1; attempt <= 4; attempt++) {
110
+ const res =
111
+ await $`hdiutil create -volname ${vol} -srcfolder ${staging} -ov -format ${format} ${dmgPath}`
112
+ .quiet()
113
+ .nothrow();
114
+ if (res.exitCode === 0) return;
115
+ stderr = res.stderr.toString().trim();
116
+ if (!/busy/i.test(stderr)) break; // not the transient flake — fail now
117
+ console.warn(`[mirin release] hdiutil create busy (attempt ${attempt}/4); retrying…`);
118
+ rmSync(dmgPath, { force: true });
119
+ await Bun.sleep(3000 * attempt);
120
+ }
121
+ throw new Error(`hdiutil create failed: ${stderr}`);
122
+ }
123
+
96
124
  /** Does this config ask for a styled Finder window (vs. a plain DMG)? */
97
125
  function wantsLayout(o: DmgOptions): boolean {
98
126
  return !!(o.background || o.appPosition || o.applicationsPosition || o.windowSize || o.iconSize);
@@ -131,7 +159,7 @@ export async function buildDmg(input: BuildDmgInput): Promise<string> {
131
159
  }
132
160
  }
133
161
  if (!built) {
134
- await $`hdiutil create -volname ${vol} -srcfolder ${staging} -ov -format ${format} ${dmgPath}`.quiet();
162
+ await hdiutilCreate(vol, staging, format, dmgPath);
135
163
  }
136
164
  } finally {
137
165
  rmSync(staging, { recursive: true, force: true });
package/src/release.ts CHANGED
@@ -164,20 +164,32 @@ export async function release(projectDir = process.cwd()): Promise<number> {
164
164
  installerSize = readFileSync(zipPath).byteLength;
165
165
  }
166
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;
167
+ // The DMG is the first-install convenience; the updater bundle + manifest (the
168
+ // critical artifacts) are already built. Never fail the whole release over a
169
+ // flaky installer step — warn loudly and ship the rest.
170
+ const dmgName = `${prefix}-${safeName}.dmg`;
171
+ console.log(`[mirin release] building installer → ${dmgName}`);
172
+ try {
173
+ const options: DmgOptions = typeof result.dmg === "object" ? result.dmg : {};
174
+ const dmgPath = await buildDmg({
175
+ app: result.app,
176
+ appName: result.appName,
177
+ outDir,
178
+ fileName: dmgName,
179
+ options,
180
+ projectDir: result.projectDir,
181
+ signIdentity: result.signIdentity,
182
+ });
183
+ await notarizeAndStaple(dmgPath); // no-op without notary credentials
184
+ installerName = dmgName;
185
+ installerSize = readFileSync(dmgPath).byteLength;
186
+ } catch (e) {
187
+ console.warn(
188
+ `\n[mirin release] ⚠️ DMG build failed: ${e instanceof Error ? e.message : e}\n` +
189
+ `[mirin release] shipping the updater bundle + manifest WITHOUT a .dmg installer.\n`,
190
+ );
191
+ rmSync(join(outDir, dmgName), { force: true });
192
+ }
181
193
  }
182
194
 
183
195
  const mb = (n: number) => (n / 1e6).toFixed(1);