@penvhq/launcher 0.11.0 → 0.12.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/dist/bin.cjs CHANGED
@@ -160,7 +160,7 @@ var AddFlagError = class extends import_core.PenvError {
160
160
  super(
161
161
  "PENV_ADD_FLAG",
162
162
  `\`penv add\` does not understand \`${flag}\``,
163
- `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\`, \`--registry <url>\`, or \`${LOCAL_FLAG}\` \u2014 those are the three it takes.`
163
+ `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\`, \`--registry <url>\`, \`${LOCAL_FLAG}\`, or \`${YES_FLAG}\` \u2014 those are the four it takes.`
164
164
  );
165
165
  }
166
166
  };
@@ -306,13 +306,13 @@ var AddNoDownloadError = class extends import_core.PenvError {
306
306
  );
307
307
  }
308
308
  };
309
- var AddNotInteractiveError = class extends import_core.PenvError {
310
- name = "AddNotInteractiveError";
311
- constructor(name) {
309
+ var AddTrustUnattendedError = class extends import_core.PenvError {
310
+ name = "AddTrustUnattendedError";
311
+ constructor(name, ceremony) {
312
312
  super(
313
- "PENV_ADD_NOT_INTERACTIVE",
314
- `Adding ${name} rewrites ${import_core.MANIFEST_PATH}, and this run has nobody to decide that`,
315
- `Run \`${addCommand(name)}\` from a terminal and commit what it writes. In CI, run \`${INSTALL_COMMAND}\` \u2014 it installs the versions the committed manifest already pins.`
313
+ "PENV_ADD_TRUST_UNATTENDED",
314
+ `Adding ${name} records ${ceremony}, and this run has nobody to write it`,
315
+ `Run \`${addCommand(name)}\` from a terminal, without \`${YES_FLAG}\` \u2014 the trust block is a line only a person can write. In CI, run \`${INSTALL_COMMAND}\`: it installs the versions the committed manifest already pins.`
316
316
  );
317
317
  }
318
318
  };
@@ -438,11 +438,11 @@ var UpgradeDeclinedError = class extends import_core.PenvError {
438
438
  };
439
439
  var UpgradeInstallFailedError = class extends import_core.PenvError {
440
440
  name = "UpgradeInstallFailedError";
441
- constructor(command) {
441
+ constructor(manager, version) {
442
442
  super(
443
443
  "PENV_UPGRADE_INSTALL_FAILED",
444
- `${command} did not finish, so ${import_core.MANIFEST_PATH} still pins the engine it pinned before`,
445
- `Run \`${command}\` yourself, then run \`${UPGRADE_COMMAND}\` again \u2014 the pin and the dependency move together or not at all.`
444
+ `${manager} did not finish moving this project to ${import_core.ENGINE_PACKAGE} ${version}, so ${import_core.MANIFEST_PATH} still pins the engine it pinned before`,
445
+ `Read what ${manager} printed above \u2014 it names what it refused. Fix that and run \`${UPGRADE_COMMAND} ${version}\` again; the pin and the dependency move together or not at all.`
446
446
  );
447
447
  }
448
448
  };
@@ -1100,12 +1100,17 @@ function parseRequest(argv) {
1100
1100
  let registry;
1101
1101
  let trustYoung = false;
1102
1102
  let local = false;
1103
+ let yes = false;
1103
1104
  for (let index = 0; index < argv.length; index += 1) {
1104
1105
  const token = argv[index] ?? "";
1105
1106
  if (token === LOCAL_FLAG) {
1106
1107
  local = true;
1107
1108
  continue;
1108
1109
  }
1110
+ if (token === YES_FLAG) {
1111
+ yes = true;
1112
+ continue;
1113
+ }
1109
1114
  if (token === TRUST_YOUNG_FLAG) {
1110
1115
  trustYoung = true;
1111
1116
  continue;
@@ -1145,7 +1150,10 @@ function parseRequest(argv) {
1145
1150
  throw new AddLocalFlagError(`\`${TRUST_YOUNG_FLAG}\``);
1146
1151
  }
1147
1152
  }
1148
- return { name, version, registry, trustYoung, local };
1153
+ return { name, version, registry, trustYoung, local, yes };
1154
+ }
1155
+ function ceremonyFor(tier) {
1156
+ return tier === "official" ? void 0 : "who publishes it and why you trust it";
1149
1157
  }
1150
1158
  function tierOf(request) {
1151
1159
  if (request.name.startsWith(import_core6.OFFICIAL_SCOPE)) {
@@ -1224,7 +1232,29 @@ function recordExtension(manifestFile, name, entry) {
1224
1232
  };
1225
1233
  return (0, import_core6.serializeManifest)(next);
1226
1234
  }
1227
- async function offerConfigEdit(options, name) {
1235
+ var NONE = /* @__PURE__ */ new Set(["", "none", "no", "n"]);
1236
+ var ALL = /* @__PURE__ */ new Set(["all", "a", "yes", "y"]);
1237
+ function chooseEnvironments(answer, offered) {
1238
+ const normalized = answer.trim().toLowerCase();
1239
+ if (NONE.has(normalized)) {
1240
+ return { chosen: [], unknown: [] };
1241
+ }
1242
+ if (ALL.has(normalized)) {
1243
+ return { chosen: offered, unknown: [] };
1244
+ }
1245
+ const chosen = [];
1246
+ const unknown = [];
1247
+ for (const token of normalized.split(/[\s,]+/).filter((part) => part !== "")) {
1248
+ const match = offered.find((environment) => environment.toLowerCase() === token);
1249
+ if (match === void 0) {
1250
+ unknown.push(token);
1251
+ } else {
1252
+ chosen.push(match);
1253
+ }
1254
+ }
1255
+ return { chosen, unknown };
1256
+ }
1257
+ async function offerConfigEdit(options, name, ask) {
1228
1258
  const { io: io2, root } = options;
1229
1259
  const configFile = (0, import_core6.findConfigFile)(root);
1230
1260
  const line = `Add \`type: ${JSON.stringify(name)}\` to an environment in penv.config.ts.`;
@@ -1239,31 +1269,35 @@ async function offerConfigEdit(options, name) {
1239
1269
  io2.out(line);
1240
1270
  return;
1241
1271
  }
1242
- if (entries.every((entry) => entry.type === name)) {
1272
+ const offered = entries.filter((entry) => entry.type !== name).map((entry) => entry.environment);
1273
+ if (offered.length === 0) {
1243
1274
  return;
1244
1275
  }
1245
- if (!io2.interactive) {
1276
+ if (!ask) {
1246
1277
  io2.out(line);
1247
1278
  return;
1248
1279
  }
1249
- for (const entry of entries) {
1250
- if (entry.type === name) {
1251
- continue;
1252
- }
1253
- if (!await io2.confirm(`Point \`${entry.environment}\` at ${name} in ${shown}?`)) {
1254
- continue;
1255
- }
1256
- const next = setProviderType(current, entry.environment, name);
1280
+ const { chosen, unknown } = chooseEnvironments(
1281
+ await io2.ask(`Point which of ${offered.join(", ")} at ${name} in ${shown}? all / none / names`),
1282
+ offered
1283
+ );
1284
+ if (unknown.length > 0) {
1285
+ io2.out(`${shown} declares no ${unknown.join(", ")}, so nothing was repointed.`);
1286
+ io2.out(line);
1287
+ return;
1288
+ }
1289
+ for (const environment of chosen) {
1290
+ const next = setProviderType(current, environment, name);
1257
1291
  if (next === void 0) {
1258
- io2.out(`Add \`type: ${JSON.stringify(name)}\` to \`${entry.environment}\` in ${shown}.`);
1292
+ io2.out(`Add \`type: ${JSON.stringify(name)}\` to \`${environment}\` in ${shown}.`);
1259
1293
  continue;
1260
1294
  }
1261
1295
  current = next;
1262
1296
  (0, import_node_fs5.writeFileSync)(configFile, current);
1263
- io2.out(`\u2713 ${shown} points \`${entry.environment}\` at ${name}`);
1297
+ io2.out(`\u2713 ${shown} points \`${environment}\` at ${name}`);
1264
1298
  }
1265
1299
  }
1266
- async function offerOnboarding(io2, name, onboard) {
1300
+ async function offerOnboarding(io2, name, onboard, ask) {
1267
1301
  if (onboard === void 0) {
1268
1302
  return void 0;
1269
1303
  }
@@ -1272,7 +1306,7 @@ async function offerOnboarding(io2, name, onboard) {
1272
1306
  return void 0;
1273
1307
  }
1274
1308
  const command = `penv ${args.join(" ")}`;
1275
- if (io2.interactive && await io2.confirm(`Run \`${command}\` now?`)) {
1309
+ if (ask && await io2.confirm(`Run \`${command}\` now?`)) {
1276
1310
  return args;
1277
1311
  }
1278
1312
  io2.out(`Run \`${command}\` to finish setting ${name} up.`);
@@ -1353,8 +1387,9 @@ async function addLocal(options, request) {
1353
1387
  io2.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1354
1388
  io2.out(`\u2713 ${import_core6.LOCAL_EXTENSIONS_PATH} records it`);
1355
1389
  io2.out(`\u2713 ${declaration} declares its config type`);
1356
- await offerConfigEdit(options, request.name);
1357
- return { onboard: await offerOnboarding(io2, request.name, installed.onboard) };
1390
+ const ask = io2.interactive && !request.yes;
1391
+ await offerConfigEdit(options, request.name, ask);
1392
+ return { onboard: await offerOnboarding(io2, request.name, installed.onboard, ask) };
1358
1393
  }
1359
1394
  async function add(options) {
1360
1395
  const { io: io2, fetcher, home, root, manifestFile } = options;
@@ -1364,11 +1399,13 @@ async function add(options) {
1364
1399
  }
1365
1400
  const tier = tierOf(request);
1366
1401
  const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
1402
+ const ask = io2.interactive && !request.yes;
1367
1403
  if (options.noDownload === true) {
1368
1404
  throw new AddNoDownloadError(request.name);
1369
1405
  }
1370
- if (options.ci === true || !io2.interactive) {
1371
- throw new AddNotInteractiveError(request.name);
1406
+ const ceremony = ceremonyFor(tier);
1407
+ if (ceremony !== void 0 && (options.ci === true || !ask)) {
1408
+ throw new AddTrustUnattendedError(request.name, ceremony);
1372
1409
  }
1373
1410
  const release = await fetchRelease({
1374
1411
  name: request.name,
@@ -1410,8 +1447,8 @@ async function add(options) {
1410
1447
  io2.out(`\u2713 ${release.name} ${release.version} installed \u2014 ${provenance}`);
1411
1448
  io2.out(`\u2713 ${import_core6.MANIFEST_PATH} pins it`);
1412
1449
  io2.out(`\u2713 ${declaration} declares its config type`);
1413
- await offerConfigEdit(options, release.name);
1414
- return { onboard: await offerOnboarding(io2, release.name, installed.onboard) };
1450
+ await offerConfigEdit(options, release.name, ask);
1451
+ return { onboard: await offerOnboarding(io2, release.name, installed.onboard, ask) };
1415
1452
  }
1416
1453
 
1417
1454
  // src/help.ts
@@ -1442,10 +1479,15 @@ function printAddHelp(io2) {
1442
1479
  io2.out(
1443
1480
  ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1444
1481
  );
1482
+ io2.out(` ${YES_FLAG} Ask nothing \u2014 the config and onboarding offers print instead`);
1445
1483
  io2.out("");
1446
1484
  io2.out(`Pins the release in ${import_core7.MANIFEST_PATH} and writes its type declaration to`);
1447
1485
  io2.out(`${import_core7.EXTENSIONS_PATH}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1448
1486
  io2.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1487
+ io2.out("");
1488
+ io2.out(`An \`${import_core7.OFFICIAL_SCOPE}*\` package asks nothing, so it adds unattended. Anything else`);
1489
+ io2.out("records who publishes it and why you trust it, which needs a person at a terminal \u2014");
1490
+ io2.out(`\`${YES_FLAG}\` cannot write that line for you.`);
1449
1491
  }
1450
1492
  function printUpgradeHelp(io2) {
1451
1493
  io2.out("penv upgrade [version]");
@@ -1465,8 +1507,8 @@ var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1465
1507
  var DEV_PIN_VERSION = "0.0.0-dev";
1466
1508
  var BUNDLED_ENGINE_PIN = {
1467
1509
  package: import_core8.ENGINE_PACKAGE,
1468
- version: "0.11.0",
1469
- integrity: "sha512-Y+WKyuPsX4U8/1j0e+VMUhj0LaGrp67YEoDAWyyinrgA+c8IM4H1blqje/YK4LsQwzQDQQDF9bhWIhfmDAWoxA=="
1510
+ version: "0.12.0",
1511
+ integrity: "sha512-9HUylEIbFq0BGTOW54Vxhkzv8AvuB7H3bImTFP5J+dfglkR7udP2KUsr8b0I2mf45YMTQO5xsYpI5+nhLERGtA=="
1470
1512
  };
1471
1513
  function assertReleasePin(pin) {
1472
1514
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1594,9 +1636,11 @@ async function upgrade(options) {
1594
1636
  try {
1595
1637
  await (options.install ?? import_install2.installWithPackageManager)(plan);
1596
1638
  } catch {
1597
- throw new UpgradeInstallFailedError(plan.command.join(" "));
1639
+ throw new UpgradeInstallFailedError(plan.manager, release.version);
1640
+ }
1641
+ for (const step of plan.steps.filter((entry) => !entry.satisfied)) {
1642
+ io2.out(`\u2713 ${step.manifest} depends on ${import_install2.RUNTIME_PACKAGE} ${release.version}`);
1598
1643
  }
1599
- io2.out(`\u2713 package.json depends on ${import_install2.RUNTIME_PACKAGE} ${release.version}`);
1600
1644
  }
1601
1645
  (0, import_node_fs7.writeFileSync)(manifestFile, manifestText);
1602
1646
  io2.out(`\u2713 ${import_core10.MANIFEST_PATH} pins ${import_core10.ENGINE_PACKAGE} ${release.version}`);
@@ -1825,12 +1869,14 @@ async function install(options, pins, home, broken) {
1825
1869
  if (state === "corrupt") {
1826
1870
  throw new PackageCorruptError(pin.name, pin.version, dir);
1827
1871
  }
1828
- if (state === "installed") {
1829
- options.io.out(`\u2713 ${pin.name} ${pin.version} already installed`);
1830
- continue;
1872
+ if (state === "absent") {
1873
+ await installPin({ home, kind, pin, fetcher: options.fetcher });
1874
+ }
1875
+ if (kind === "extensions") {
1876
+ await assertLoadable(pin.name, (0, import_core11.packageEntry)(dir), false);
1831
1877
  }
1832
- await installPin({ home, kind, pin, fetcher: options.fetcher });
1833
- options.io.out(`\u2713 ${pin.name} ${pin.version} installed`);
1878
+ const verb = state === "absent" ? "installed" : "already installed";
1879
+ options.io.out(`\u2713 ${pin.name} ${pin.version} ${verb}`);
1834
1880
  }
1835
1881
  if (broken.length > 0) {
1836
1882
  report(new ManifestEntriesUnreadableError(broken), options.io);