@mutmutco/installer-launcher 0.1.3 → 0.1.4

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/dist/launcher.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { spawnSync as spawnSync2 } from "node:child_process";
5
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
5
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
6
6
  import { dirname as dirname2, join as join4, resolve } from "node:path";
7
7
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "node:url";
8
8
 
@@ -121,9 +121,10 @@ function createFace({ product, color = false, columns }) {
121
121
  const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
122
122
  const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
123
123
  const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
124
+ const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(PALETTE.green, GLYPH.check)}${line.slice(1)}` : line;
124
125
  return [
125
126
  frame(GLYPH.boxTop, GLYPH.boxTopEnd),
126
- ...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${line}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
127
+ ...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
127
128
  frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
128
129
  ];
129
130
  };
@@ -132,6 +133,8 @@ function createFace({ product, color = false, columns }) {
132
133
  return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
133
134
  }
134
135
  var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
136
+ var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
137
+ var SPINNER_FRAMES = Object.freeze([...FRAMES]);
135
138
  var ALLOWED = new Set(Object.values(GLYPH));
136
139
 
137
140
  // src/autoupdate.ts
@@ -860,7 +863,7 @@ function wipeProductDir(dir) {
860
863
  }
861
864
 
862
865
  // src/index.ts
863
- var LAUNCHER_VERSION = true ? "0.1.3" : readVersionFromPackage();
866
+ var LAUNCHER_VERSION = true ? "0.1.4" : readVersionFromPackage();
864
867
  function defaultPrint(message) {
865
868
  process.stdout.write(`${message}
866
869
  `);
@@ -1072,7 +1075,7 @@ function needsShell(command) {
1072
1075
  function quoteForShell(arg) {
1073
1076
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
1074
1077
  }
1075
- function defaultRunEntry(entry, cwd) {
1078
+ function defaultRunEntry(entry, cwd, env) {
1076
1079
  const [command, ...args] = entry;
1077
1080
  const shell = needsShell(command);
1078
1081
  const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
@@ -1080,6 +1083,7 @@ function defaultRunEntry(entry, cwd) {
1080
1083
  cwd,
1081
1084
  stdio: "inherit",
1082
1085
  shell,
1086
+ env: env ?? process.env,
1083
1087
  windowsHide: true
1084
1088
  });
1085
1089
  if (result.error) return { ok: false, error: result.error.message };
@@ -1092,10 +1096,11 @@ function faceProduct(config) {
1092
1096
  }
1093
1097
  function faceFor(config, options) {
1094
1098
  const tty = options.tty ?? Boolean(process.stdout.isTTY);
1099
+ if (!tty) return null;
1095
1100
  try {
1096
1101
  return createFace({
1097
1102
  product: faceProduct(config),
1098
- color: tty && !process.env.NO_COLOR && process.env.TERM !== "dumb",
1103
+ color: !process.env.NO_COLOR && process.env.TERM !== "dumb",
1099
1104
  columns: process.stdout.columns
1100
1105
  });
1101
1106
  } catch {
@@ -1128,7 +1133,7 @@ async function doInstall(config, dir, options, print) {
1128
1133
  const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
1129
1134
  await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
1130
1135
  writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1131
- printStep(face, print, `installed ${config.product} ${manifest.version} into ${dir}`, since(started));
1136
+ printStep(face, print, `installed ${config.product} ${manifest.version}`, since(started));
1132
1137
  return finishLastMile(config, dir, options, print, face, manifest.version);
1133
1138
  }
1134
1139
  async function doUpdate(config, dir, options, print) {
@@ -1148,6 +1153,26 @@ async function doUpdate(config, dir, options, print) {
1148
1153
  printStep(face, print, `updated ${config.product} to ${manifest.version}`, since(started));
1149
1154
  return finishLastMile(config, dir, options, print, face, manifest.version);
1150
1155
  }
1156
+ function payloadEnv(dir) {
1157
+ const stored = readTokens(dir);
1158
+ if (!stored?.accessToken) return void 0;
1159
+ return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
1160
+ }
1161
+ var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
1162
+ function payloadFileAsCommand(entry, payload) {
1163
+ const first = entry[0];
1164
+ if (!first || first === "$self") return null;
1165
+ if (first.includes("/") || first.includes("\\")) return null;
1166
+ const candidate = join4(payload, first);
1167
+ if (!existsSync4(candidate)) return null;
1168
+ if (NEVER_A_PROGRAM.test(first)) return first;
1169
+ if (process.platform === "win32") return null;
1170
+ try {
1171
+ return (statSync(candidate).mode & 73) === 0 ? first : null;
1172
+ } catch {
1173
+ return null;
1174
+ }
1175
+ }
1151
1176
  function finishLastMile(config, dir, options, print, face, version) {
1152
1177
  const entry = readPayloadEntry(dir);
1153
1178
  const payload = payloadDir(dir);
@@ -1159,8 +1184,18 @@ function finishLastMile(config, dir, options, print, face, version) {
1159
1184
  return 0;
1160
1185
  }
1161
1186
  const command = resolveEntry(entry);
1187
+ const dataFile = payloadFileAsCommand(entry, payload);
1188
+ if (dataFile) {
1189
+ printStep(face, print, `The payload names ${dataFile} as its command, but that is a file, not a program`, null, "fail");
1190
+ printReceipt(face, config, print, false, [
1191
+ ` Downloaded ${version} into ${dir}`,
1192
+ ` This payload was built wrong: its entry must be a command, not one of its own files.`,
1193
+ ` Nothing on this machine can finish it \u2014 report it to the ${config.product} maintainers.`
1194
+ ]);
1195
+ return 2;
1196
+ }
1162
1197
  const started = Date.now();
1163
- const result = (options.runEntry ?? defaultRunEntry)(command, payload);
1198
+ const result = (options.runEntry ?? defaultRunEntry)(command, payload, payloadEnv(dir));
1164
1199
  if (result.ok) {
1165
1200
  printStep(face, print, "Armed this machine", since(started));
1166
1201
  printReceipt(face, config, print, true, [
@@ -1170,7 +1205,13 @@ function finishLastMile(config, dir, options, print, face, version) {
1170
1205
  return 0;
1171
1206
  }
1172
1207
  const code = result.code ?? 1;
1173
- printStep(face, print, `Arming this machine did not finish (exit code ${code})`, null, "fail");
1208
+ printStep(
1209
+ face,
1210
+ print,
1211
+ result.code === void 0 ? `Arming this machine could not start: ${result.error ?? `could not run ${command[0]}`}` : `Arming this machine did not finish (exit code ${code})`,
1212
+ null,
1213
+ "fail"
1214
+ );
1174
1215
  printReceipt(face, config, print, false, [
1175
1216
  ` Downloaded ${version} into ${dir}`,
1176
1217
  ` Finish it with: (cd ${payload} && ${command.join(" ")})`
@@ -1191,7 +1232,7 @@ function doForward(config, dir, options, command, argv, print) {
1191
1232
  return 2;
1192
1233
  }
1193
1234
  const forwarded = [...resolveEntry(target), ...argv];
1194
- const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir));
1235
+ const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir), payloadEnv(dir));
1195
1236
  if (!result.ok && result.code === void 0) {
1196
1237
  print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
1197
1238
  return 1;
@@ -1242,7 +1283,7 @@ function doDoctor(config, dir, options, print) {
1242
1283
  const verbs = readPayloadVerbs(dir);
1243
1284
  const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
1244
1285
  if (!chains) return 0;
1245
- const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir));
1286
+ const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir), payloadEnv(dir));
1246
1287
  return result.code ?? (result.ok ? 0 : 1);
1247
1288
  }
1248
1289
  function doLauncherDoctor(config, dir, options, print) {
@@ -151,9 +151,10 @@ function createFace({ product, color = false, columns }) {
151
151
  const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
152
152
  const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
153
153
  const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
154
+ const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(PALETTE.green, GLYPH.check)}${line.slice(1)}` : line;
154
155
  return [
155
156
  frame(GLYPH.boxTop, GLYPH.boxTopEnd),
156
- ...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${line}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
157
+ ...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
157
158
  frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
158
159
  ];
159
160
  };
@@ -162,6 +163,8 @@ function createFace({ product, color = false, columns }) {
162
163
  return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
163
164
  }
164
165
  var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
166
+ var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
167
+ var SPINNER_FRAMES = Object.freeze([...FRAMES]);
165
168
  var ALLOWED = new Set(Object.values(GLYPH));
166
169
 
167
170
  // src/autoupdate.ts
@@ -891,7 +894,7 @@ function wipeProductDir(dir) {
891
894
  }
892
895
 
893
896
  // src/index.ts
894
- var LAUNCHER_VERSION = true ? "0.1.3" : readVersionFromPackage();
897
+ var LAUNCHER_VERSION = true ? "0.1.4" : readVersionFromPackage();
895
898
  function defaultPrint(message) {
896
899
  process.stdout.write(`${message}
897
900
  `);
@@ -1103,7 +1106,7 @@ function needsShell(command) {
1103
1106
  function quoteForShell(arg) {
1104
1107
  return /[\s"&|<>^%]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg;
1105
1108
  }
1106
- function defaultRunEntry(entry, cwd) {
1109
+ function defaultRunEntry(entry, cwd, env) {
1107
1110
  const [command, ...args] = entry;
1108
1111
  const shell = needsShell(command);
1109
1112
  const commandLine = shell ? [command, ...args].map(quoteForShell).join(" ") : command;
@@ -1111,6 +1114,7 @@ function defaultRunEntry(entry, cwd) {
1111
1114
  cwd,
1112
1115
  stdio: "inherit",
1113
1116
  shell,
1117
+ env: env ?? process.env,
1114
1118
  windowsHide: true
1115
1119
  });
1116
1120
  if (result.error) return { ok: false, error: result.error.message };
@@ -1123,10 +1127,11 @@ function faceProduct(config) {
1123
1127
  }
1124
1128
  function faceFor(config, options) {
1125
1129
  const tty = options.tty ?? Boolean(process.stdout.isTTY);
1130
+ if (!tty) return null;
1126
1131
  try {
1127
1132
  return createFace({
1128
1133
  product: faceProduct(config),
1129
- color: tty && !process.env.NO_COLOR && process.env.TERM !== "dumb",
1134
+ color: !process.env.NO_COLOR && process.env.TERM !== "dumb",
1130
1135
  columns: process.stdout.columns
1131
1136
  });
1132
1137
  } catch {
@@ -1159,7 +1164,7 @@ async function doInstall(config, dir, options, print) {
1159
1164
  const manifest = await fetchVerifiedManifest(config, accessToken, fetchImpl);
1160
1165
  await downloadAndUnpack(config, dir, manifest, accessToken, { fetchImpl });
1161
1166
  writeState(dir, { version: manifest.version, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1162
- printStep(face, print, `installed ${config.product} ${manifest.version} into ${dir}`, since(started));
1167
+ printStep(face, print, `installed ${config.product} ${manifest.version}`, since(started));
1163
1168
  return finishLastMile(config, dir, options, print, face, manifest.version);
1164
1169
  }
1165
1170
  async function doUpdate(config, dir, options, print) {
@@ -1179,6 +1184,26 @@ async function doUpdate(config, dir, options, print) {
1179
1184
  printStep(face, print, `updated ${config.product} to ${manifest.version}`, since(started));
1180
1185
  return finishLastMile(config, dir, options, print, face, manifest.version);
1181
1186
  }
1187
+ function payloadEnv(dir) {
1188
+ const stored = readTokens(dir);
1189
+ if (!stored?.accessToken) return void 0;
1190
+ return { ...process.env, MM_INSTALLER_TOKEN: stored.accessToken };
1191
+ }
1192
+ var NEVER_A_PROGRAM = /\.(tgz|tar|gz|bz2|xz|zip|json|txt|md|lock)$/i;
1193
+ function payloadFileAsCommand(entry, payload) {
1194
+ const first = entry[0];
1195
+ if (!first || first === "$self") return null;
1196
+ if (first.includes("/") || first.includes("\\")) return null;
1197
+ const candidate = (0, import_node_path4.join)(payload, first);
1198
+ if (!(0, import_node_fs5.existsSync)(candidate)) return null;
1199
+ if (NEVER_A_PROGRAM.test(first)) return first;
1200
+ if (process.platform === "win32") return null;
1201
+ try {
1202
+ return ((0, import_node_fs5.statSync)(candidate).mode & 73) === 0 ? first : null;
1203
+ } catch {
1204
+ return null;
1205
+ }
1206
+ }
1182
1207
  function finishLastMile(config, dir, options, print, face, version) {
1183
1208
  const entry = readPayloadEntry(dir);
1184
1209
  const payload = payloadDir(dir);
@@ -1190,8 +1215,18 @@ function finishLastMile(config, dir, options, print, face, version) {
1190
1215
  return 0;
1191
1216
  }
1192
1217
  const command = resolveEntry(entry);
1218
+ const dataFile = payloadFileAsCommand(entry, payload);
1219
+ if (dataFile) {
1220
+ printStep(face, print, `The payload names ${dataFile} as its command, but that is a file, not a program`, null, "fail");
1221
+ printReceipt(face, config, print, false, [
1222
+ ` Downloaded ${version} into ${dir}`,
1223
+ ` This payload was built wrong: its entry must be a command, not one of its own files.`,
1224
+ ` Nothing on this machine can finish it \u2014 report it to the ${config.product} maintainers.`
1225
+ ]);
1226
+ return 2;
1227
+ }
1193
1228
  const started = Date.now();
1194
- const result = (options.runEntry ?? defaultRunEntry)(command, payload);
1229
+ const result = (options.runEntry ?? defaultRunEntry)(command, payload, payloadEnv(dir));
1195
1230
  if (result.ok) {
1196
1231
  printStep(face, print, "Armed this machine", since(started));
1197
1232
  printReceipt(face, config, print, true, [
@@ -1201,7 +1236,13 @@ function finishLastMile(config, dir, options, print, face, version) {
1201
1236
  return 0;
1202
1237
  }
1203
1238
  const code = result.code ?? 1;
1204
- printStep(face, print, `Arming this machine did not finish (exit code ${code})`, null, "fail");
1239
+ printStep(
1240
+ face,
1241
+ print,
1242
+ result.code === void 0 ? `Arming this machine could not start: ${result.error ?? `could not run ${command[0]}`}` : `Arming this machine did not finish (exit code ${code})`,
1243
+ null,
1244
+ "fail"
1245
+ );
1205
1246
  printReceipt(face, config, print, false, [
1206
1247
  ` Downloaded ${version} into ${dir}`,
1207
1248
  ` Finish it with: (cd ${payload} && ${command.join(" ")})`
@@ -1222,7 +1263,7 @@ function doForward(config, dir, options, command, argv, print) {
1222
1263
  return 2;
1223
1264
  }
1224
1265
  const forwarded = [...resolveEntry(target), ...argv];
1225
- const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir));
1266
+ const result = (options.runEntry ?? defaultRunEntry)(forwarded, payloadDir(dir), payloadEnv(dir));
1226
1267
  if (!result.ok && result.code === void 0) {
1227
1268
  print(`failed: ${result.error ?? `could not run ${forwarded[0]}`}`);
1228
1269
  return 1;
@@ -1273,7 +1314,7 @@ function doDoctor(config, dir, options, print) {
1273
1314
  const verbs = readPayloadVerbs(dir);
1274
1315
  const chains = target !== null && (verbs === "*" || Array.isArray(verbs) && verbs.includes("doctor"));
1275
1316
  if (!chains) return 0;
1276
- const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir));
1317
+ const result = (options.runEntry ?? defaultRunEntry)([...resolveEntry(target), "doctor"], payloadDir(dir), payloadEnv(dir));
1277
1318
  return result.code ?? (result.ok ? 0 : 1);
1278
1319
  }
1279
1320
  function doLauncherDoctor(config, dir, options, print) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/installer-launcher",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",
@@ -34,6 +34,6 @@
34
34
  "vitest": "^4.1.0"
35
35
  },
36
36
  "dependencies": {
37
- "@mutmutco/installer-face": "^0.1.0"
37
+ "@mutmutco/installer-face": "^0.2.1"
38
38
  }
39
39
  }