@mutmutco/installer-launcher 0.1.0 → 0.1.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/README.md CHANGED
@@ -179,8 +179,9 @@ the fixture's stdout.
179
179
 
180
180
  The SEA binary must be stamped on a native runner for its target OS/architecture — the Node SEA
181
181
  blob embeds the host Node binary, so a macOS arm64 binary cannot be produced on Linux. The reusable
182
- workflow `.github/workflows/launcher-build.yml` implements exactly the steps above on the three
183
- supported lanes (`macos-14` arm64, `macos-13` x64, `windows-latest` x64): `npm ci`, install
182
+ workflow `.github/workflows/launcher-build.yml` implements exactly the steps above on the two
183
+ supported lanes (`macos-14` arm64, Apple Silicon, and `windows-latest` x64 Intel Macs are not
184
+ supported, so there is no `darwin-x64` leg): `npm ci`, install
184
185
  `postject`, `node build.mjs --product-config <cfg> --sea`, smoke `--version` then
185
186
  `--run test/fixtures/run-hello.mjs a b`, write the `.sha256`,
186
187
  and upload `launcher-<product>-<platform>-<arch>`. It is callable (`workflow_call`) and dispatchable,
package/dist/launcher.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
6
- import { join as join3, resolve } from "node:path";
6
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
7
7
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
8
8
 
9
9
  // src/config.ts
@@ -91,8 +91,8 @@ import { spawn } from "node:child_process";
91
91
  var realSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
92
92
  function openBrowser(url) {
93
93
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
94
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
95
- const args = process.platform === "win32" ? ["/c", "start", "", url] : process.platform === "darwin" ? [url] : [url];
94
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
95
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
96
96
  try {
97
97
  const child = spawn(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
98
98
  child.on("error", () => {
@@ -108,10 +108,24 @@ async function postJson(url, body, fetchImpl) {
108
108
  body: JSON.stringify(body)
109
109
  });
110
110
  }
111
+ async function readBody(response) {
112
+ try {
113
+ return await response.text();
114
+ } catch {
115
+ return "";
116
+ }
117
+ }
118
+ function gateRefusal(method, url, status, body) {
119
+ const head = body.replace(/\s+/g, " ").trim().slice(0, 200);
120
+ return `the sign-in server refused the request \u2014 ${method} ${url} \u2192 ${status}${head ? `: ${head}` : ""}`;
121
+ }
111
122
  async function requestDeviceCode(config, fetchImpl) {
112
123
  if (!config.githubClientId) throw new Error("this product has no githubClientId configured");
113
- const response = await postJson(`${config.host}/gate/device/code`, { client_id: config.githubClientId }, fetchImpl);
114
- if (!response.ok) throw new Error(`the sign-in server refused the request (${response.status})`);
124
+ const url = `${config.host}/gate/device/code`;
125
+ const response = await postJson(url, { client_id: config.githubClientId }, fetchImpl);
126
+ if (!response.ok) {
127
+ throw new Error(gateRefusal("POST", url, response.status, await readBody(response)));
128
+ }
115
129
  const data = await response.json();
116
130
  if (typeof data.device_code !== "string" || typeof data.user_code !== "string" || typeof data.verification_uri !== "string" || typeof data.expires_in !== "number" || typeof data.interval !== "number") {
117
131
  throw new Error("the sign-in server returned a malformed device code");
@@ -144,8 +158,9 @@ async function loginGithub(config, options = {}) {
144
158
  for (; ; ) {
145
159
  if (now() >= deadline) throw new Error("the sign-in code expired before you approved it \u2014 run login again.");
146
160
  await sleep(intervalMs);
161
+ const url = `${config.host}/gate/device/token`;
147
162
  const response = await postJson(
148
- `${config.host}/gate/device/token`,
163
+ url,
149
164
  { client_id: config.githubClientId, device_code: issued.device_code },
150
165
  fetchImpl
151
166
  );
@@ -156,9 +171,10 @@ async function loginGithub(config, options = {}) {
156
171
  }
157
172
  return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresIn: data.expires_in };
158
173
  }
174
+ const body = await readBody(response);
159
175
  let error = "";
160
176
  try {
161
- error = (await response.json()).error ?? "";
177
+ error = JSON.parse(body).error ?? "";
162
178
  } catch {
163
179
  error = "";
164
180
  }
@@ -173,15 +189,16 @@ async function loginGithub(config, options = {}) {
173
189
  if (error === "denied") {
174
190
  throw new Error("you declined the sign-in request \u2014 nothing was installed.");
175
191
  }
176
- throw new Error(
177
- `the sign-in server refused the request (${response.status}${error ? `: ${error}` : ""})`
178
- );
192
+ throw new Error(gateRefusal("POST", url, response.status, body));
179
193
  }
180
194
  }
181
195
  async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
182
- const response = await postJson(`${config.host}/gate/refresh`, { refresh_token: refreshToken }, fetchImpl);
196
+ const url = `${config.host}/gate/refresh`;
197
+ const response = await postJson(url, { refresh_token: refreshToken }, fetchImpl);
183
198
  if (response.status === 403) return null;
184
- if (!response.ok) throw new Error(`the sign-in server refused the refresh (${response.status})`);
199
+ if (!response.ok) {
200
+ throw new Error(gateRefusal("POST", url, response.status, await readBody(response)));
201
+ }
185
202
  const data = await response.json();
186
203
  if (typeof data.access_token !== "string" || typeof data.expires_in !== "number") {
187
204
  throw new Error("the sign-in server returned a malformed refresh");
@@ -245,6 +262,10 @@ async function loginGoogle(options) {
245
262
  clearTimeout(timer);
246
263
  resolve2(received);
247
264
  });
265
+ const print = options.print ?? ((line) => process.stderr.write(`${line}
266
+ `));
267
+ print(`Sign in at: ${authorize.toString()}`);
268
+ print("If the browser did not open, open that address yourself.");
248
269
  options.open(authorize.toString());
249
270
  });
250
271
  const exchange = await fetchImpl(`${server}/oauth/token`, {
@@ -294,7 +315,8 @@ async function refreshGoogleToken(host, clientId, refreshToken, fetchImpl = fetc
294
315
  };
295
316
  }
296
317
  function page(title, body) {
297
- return `<!doctype html><meta charset="utf-8"><title>${title}</title><style>body{font-family:system-ui;margin:0;padding:12vh 1.5rem}main{max-width:32rem;margin:auto}h1{font-size:1.4rem}</style><main><h1>${title}</h1><p>${body}</p></main>`;
318
+ const style = "html{color-scheme:dark}body{background:#0a0a0a;color:#ededed;font-family:system-ui,-apple-system,Segoe UI,sans-serif;margin:0;padding:12vh 1.5rem}main{max-width:32rem;margin:auto}h1{font-size:1.4rem;font-weight:700;color:#1fd18a;margin:0 0 .75rem}p{color:#c9c9c9;line-height:1.55;margin:0}";
319
+ return `<!doctype html><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>${title}</title><style>${style}</style><main><h1>${title}</h1><p>${body}</p></main>`;
298
320
  }
299
321
 
300
322
  // src/payload.ts
@@ -535,7 +557,7 @@ function wipeProductDir(dir) {
535
557
  }
536
558
 
537
559
  // src/index.ts
538
- var LAUNCHER_VERSION = "0.1.0";
560
+ var LAUNCHER_VERSION = true ? "0.1.2" : readVersionFromPackage();
539
561
  function defaultPrint(message) {
540
562
  process.stdout.write(`${message}
541
563
  `);
@@ -120,8 +120,8 @@ var import_node_child_process = require("node:child_process");
120
120
  var realSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
121
121
  function openBrowser(url) {
122
122
  if (process.env.LAUNCHER_NO_OPEN === "1") return;
123
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
124
- const args = process.platform === "win32" ? ["/c", "start", "", url] : process.platform === "darwin" ? [url] : [url];
123
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
124
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
125
125
  try {
126
126
  const child = (0, import_node_child_process.spawn)(opener, args, { detached: true, stdio: "ignore", windowsHide: true });
127
127
  child.on("error", () => {
@@ -137,10 +137,24 @@ async function postJson(url, body, fetchImpl) {
137
137
  body: JSON.stringify(body)
138
138
  });
139
139
  }
140
+ async function readBody(response) {
141
+ try {
142
+ return await response.text();
143
+ } catch {
144
+ return "";
145
+ }
146
+ }
147
+ function gateRefusal(method, url, status, body) {
148
+ const head = body.replace(/\s+/g, " ").trim().slice(0, 200);
149
+ return `the sign-in server refused the request \u2014 ${method} ${url} \u2192 ${status}${head ? `: ${head}` : ""}`;
150
+ }
140
151
  async function requestDeviceCode(config, fetchImpl) {
141
152
  if (!config.githubClientId) throw new Error("this product has no githubClientId configured");
142
- const response = await postJson(`${config.host}/gate/device/code`, { client_id: config.githubClientId }, fetchImpl);
143
- if (!response.ok) throw new Error(`the sign-in server refused the request (${response.status})`);
153
+ const url = `${config.host}/gate/device/code`;
154
+ const response = await postJson(url, { client_id: config.githubClientId }, fetchImpl);
155
+ if (!response.ok) {
156
+ throw new Error(gateRefusal("POST", url, response.status, await readBody(response)));
157
+ }
144
158
  const data = await response.json();
145
159
  if (typeof data.device_code !== "string" || typeof data.user_code !== "string" || typeof data.verification_uri !== "string" || typeof data.expires_in !== "number" || typeof data.interval !== "number") {
146
160
  throw new Error("the sign-in server returned a malformed device code");
@@ -173,8 +187,9 @@ async function loginGithub(config, options = {}) {
173
187
  for (; ; ) {
174
188
  if (now() >= deadline) throw new Error("the sign-in code expired before you approved it \u2014 run login again.");
175
189
  await sleep(intervalMs);
190
+ const url = `${config.host}/gate/device/token`;
176
191
  const response = await postJson(
177
- `${config.host}/gate/device/token`,
192
+ url,
178
193
  { client_id: config.githubClientId, device_code: issued.device_code },
179
194
  fetchImpl
180
195
  );
@@ -185,9 +200,10 @@ async function loginGithub(config, options = {}) {
185
200
  }
186
201
  return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresIn: data.expires_in };
187
202
  }
203
+ const body = await readBody(response);
188
204
  let error = "";
189
205
  try {
190
- error = (await response.json()).error ?? "";
206
+ error = JSON.parse(body).error ?? "";
191
207
  } catch {
192
208
  error = "";
193
209
  }
@@ -202,15 +218,16 @@ async function loginGithub(config, options = {}) {
202
218
  if (error === "denied") {
203
219
  throw new Error("you declined the sign-in request \u2014 nothing was installed.");
204
220
  }
205
- throw new Error(
206
- `the sign-in server refused the request (${response.status}${error ? `: ${error}` : ""})`
207
- );
221
+ throw new Error(gateRefusal("POST", url, response.status, body));
208
222
  }
209
223
  }
210
224
  async function refreshAccessToken(config, refreshToken, fetchImpl = fetch) {
211
- const response = await postJson(`${config.host}/gate/refresh`, { refresh_token: refreshToken }, fetchImpl);
225
+ const url = `${config.host}/gate/refresh`;
226
+ const response = await postJson(url, { refresh_token: refreshToken }, fetchImpl);
212
227
  if (response.status === 403) return null;
213
- if (!response.ok) throw new Error(`the sign-in server refused the refresh (${response.status})`);
228
+ if (!response.ok) {
229
+ throw new Error(gateRefusal("POST", url, response.status, await readBody(response)));
230
+ }
214
231
  const data = await response.json();
215
232
  if (typeof data.access_token !== "string" || typeof data.expires_in !== "number") {
216
233
  throw new Error("the sign-in server returned a malformed refresh");
@@ -274,6 +291,10 @@ async function loginGoogle(options) {
274
291
  clearTimeout(timer);
275
292
  resolve2(received);
276
293
  });
294
+ const print = options.print ?? ((line) => process.stderr.write(`${line}
295
+ `));
296
+ print(`Sign in at: ${authorize.toString()}`);
297
+ print("If the browser did not open, open that address yourself.");
277
298
  options.open(authorize.toString());
278
299
  });
279
300
  const exchange = await fetchImpl(`${server}/oauth/token`, {
@@ -323,7 +344,8 @@ async function refreshGoogleToken(host, clientId, refreshToken, fetchImpl = fetc
323
344
  };
324
345
  }
325
346
  function page(title, body) {
326
- return `<!doctype html><meta charset="utf-8"><title>${title}</title><style>body{font-family:system-ui;margin:0;padding:12vh 1.5rem}main{max-width:32rem;margin:auto}h1{font-size:1.4rem}</style><main><h1>${title}</h1><p>${body}</p></main>`;
347
+ const style = "html{color-scheme:dark}body{background:#0a0a0a;color:#ededed;font-family:system-ui,-apple-system,Segoe UI,sans-serif;margin:0;padding:12vh 1.5rem}main{max-width:32rem;margin:auto}h1{font-size:1.4rem;font-weight:700;color:#1fd18a;margin:0 0 .75rem}p{color:#c9c9c9;line-height:1.55;margin:0}";
348
+ return `<!doctype html><meta charset="utf-8"><meta name="color-scheme" content="dark"><title>${title}</title><style>${style}</style><main><h1>${title}</h1><p>${body}</p></main>`;
327
349
  }
328
350
 
329
351
  // src/payload.ts
@@ -564,7 +586,7 @@ function wipeProductDir(dir) {
564
586
  }
565
587
 
566
588
  // src/index.ts
567
- var LAUNCHER_VERSION = "0.1.0";
589
+ var LAUNCHER_VERSION = true ? "0.1.2" : readVersionFromPackage();
568
590
  function defaultPrint(message) {
569
591
  process.stdout.write(`${message}
570
592
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/installer-launcher",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Single-executable (SEA) launcher: sign-in, gated payload fetch, self-update. One copy ships per product.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",