@penvhq/launcher 0.9.5 → 0.11.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
@@ -53,12 +53,17 @@ var import_core2 = require("@penvhq/core");
53
53
  // src/errors.ts
54
54
  var import_core = require("@penvhq/core");
55
55
  var INSTALL_COMMAND = "penv install";
56
+ var UPGRADE_COMMAND = "penv upgrade";
57
+ var YES_FLAG = "--yes";
56
58
  var MIN_PACKAGE_AGE_DAYS = 7;
57
59
  var TRUST_YOUNG_FLAG = "--trust-young";
58
60
  function addCommand(name, version, extra) {
59
61
  const spec = version === void 0 ? name : `${name}@${version}`;
60
62
  return `penv add ${spec}${extra === void 0 ? "" : ` ${extra}`}`;
61
63
  }
64
+ function addCommandFor(name, local) {
65
+ return local ? `penv add ${LOCAL_FLAG} ${name}` : addCommand(name);
66
+ }
62
67
  var NoProjectError = class extends import_core.PenvError {
63
68
  name = "NoProjectError";
64
69
  constructor(cwd) {
@@ -170,6 +175,26 @@ var AddLocalFlagError = class extends import_core.PenvError {
170
175
  );
171
176
  }
172
177
  };
178
+ var ExtensionNotImportableError = class extends import_core.PenvError {
179
+ name = "ExtensionNotImportableError";
180
+ constructor(name, entry, local) {
181
+ super(
182
+ "PENV_EXTENSION_NOT_IMPORTABLE",
183
+ entry === void 0 ? `${name} declares no entry point penv can import` : `${name} resolves to ${entry}, which penv cannot import`,
184
+ `penv imports a provider with no transform, so its entry has to be built JavaScript. Point the package's \`exports\` at its build output \u2014 \`dist/index.js\` \u2014 then run \`${addCommandFor(name, local)}\` again.`
185
+ );
186
+ }
187
+ };
188
+ var ExtensionUnloadableError = class extends import_core.PenvError {
189
+ name = "ExtensionUnloadableError";
190
+ constructor(name, entry, detail, local) {
191
+ super(
192
+ "PENV_EXTENSION_UNLOADABLE",
193
+ `${name} threw while penv imported ${entry}: ${detail}`,
194
+ `Build the package and install its dependencies, then run \`${addCommandFor(name, local)}\` again. penv adds a provider it has loaded once, or none.`
195
+ );
196
+ }
197
+ };
173
198
  var LocalExtensionUnresolvedError = class extends import_core.PenvError {
174
199
  name = "LocalExtensionUnresolvedError";
175
200
  constructor(name, root) {
@@ -212,11 +237,11 @@ var OfficialRegistryError = class extends import_core.PenvError {
212
237
  };
213
238
  var RegistryUnreadableError = class extends import_core.PenvError {
214
239
  name = "RegistryUnreadableError";
215
- constructor(name, url, detail) {
240
+ constructor(name, url, detail, retry = addCommand(name)) {
216
241
  super(
217
242
  "PENV_REGISTRY_UNREADABLE",
218
243
  `Reading ${name} from ${url} failed: ${detail}`,
219
- `Run \`${addCommand(name)}\` again when the registry is reachable.`
244
+ `Run \`${retry}\` again when the registry is reachable.`
220
245
  );
221
246
  }
222
247
  };
@@ -232,11 +257,11 @@ var PackageUnknownError = class extends import_core.PenvError {
232
257
  };
233
258
  var VersionUnknownError = class extends import_core.PenvError {
234
259
  name = "VersionUnknownError";
235
- constructor(name, version, url) {
260
+ constructor(name, version, url, retry = addCommand(name)) {
236
261
  super(
237
262
  "PENV_VERSION_UNKNOWN",
238
263
  `${url} publishes no ${version} of ${name}`,
239
- `Run \`${addCommand(name)}\` to take the version \`latest\` points at.`
264
+ `Run \`${retry}\` to take the version \`latest\` points at.`
240
265
  );
241
266
  }
242
267
  };
@@ -361,6 +386,66 @@ var EnginePinMismatchError = class extends import_core.PenvError {
361
386
  );
362
387
  }
363
388
  };
389
+ var UpgradeSubjectError = class extends import_core.PenvError {
390
+ name = "UpgradeSubjectError";
391
+ constructor() {
392
+ super(
393
+ "PENV_UPGRADE_SUBJECT",
394
+ "`penv upgrade` takes one version, or none",
395
+ `Run \`${UPGRADE_COMMAND}\` to move to whatever \`latest\` points at, or \`${UPGRADE_COMMAND} 0.9.6\` to move to an exact release.`
396
+ );
397
+ }
398
+ };
399
+ var UpgradeFlagError = class extends import_core.PenvError {
400
+ name = "UpgradeFlagError";
401
+ constructor(flag) {
402
+ super(
403
+ "PENV_UPGRADE_FLAG",
404
+ `\`${UPGRADE_COMMAND}\` does not understand \`${flag}\``,
405
+ `Run \`${UPGRADE_COMMAND} [version]\` with \`${YES_FLAG}\` \u2014 that is the one flag it takes.`
406
+ );
407
+ }
408
+ };
409
+ var UpgradeNoDownloadError = class extends import_core.PenvError {
410
+ name = "UpgradeNoDownloadError";
411
+ constructor() {
412
+ super(
413
+ "PENV_UPGRADE_NO_DOWNLOAD",
414
+ `Upgrading means reading the registry for the version and integrity to pin, and \`--no-download\` says this run does not`,
415
+ `Run \`${UPGRADE_COMMAND}\` without \`--no-download\`. Nothing was fetched or written.`
416
+ );
417
+ }
418
+ };
419
+ var UpgradeUnattendedError = class extends import_core.PenvError {
420
+ name = "UpgradeUnattendedError";
421
+ constructor() {
422
+ super(
423
+ "PENV_UPGRADE_UNATTENDED",
424
+ `Upgrading rewrites ${import_core.MANIFEST_PATH} and package.json, and this run has nobody to decide that`,
425
+ `Run \`${UPGRADE_COMMAND} <version> ${YES_FLAG}\` \u2014 unattended, penv moves the pin only to a version you named. In CI, run \`${INSTALL_COMMAND}\`: it installs what the committed manifest already pins.`
426
+ );
427
+ }
428
+ };
429
+ var UpgradeDeclinedError = class extends import_core.PenvError {
430
+ name = "UpgradeDeclinedError";
431
+ constructor(version) {
432
+ super(
433
+ "PENV_UPGRADE_DECLINED",
434
+ `${import_core.ENGINE_PACKAGE} ${version} was not installed, so ${import_core.MANIFEST_PATH} and package.json are unchanged`,
435
+ `Run \`${UPGRADE_COMMAND} ${version}\` again when you want both of them to move.`
436
+ );
437
+ }
438
+ };
439
+ var UpgradeInstallFailedError = class extends import_core.PenvError {
440
+ name = "UpgradeInstallFailedError";
441
+ constructor(command) {
442
+ super(
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.`
446
+ );
447
+ }
448
+ };
364
449
  var EngineEntryError = class extends import_core.PenvError {
365
450
  name = "EngineEntryError";
366
451
  constructor(name, version, dir) {
@@ -437,14 +522,15 @@ function httpFetcher() {
437
522
  }
438
523
 
439
524
  // src/launcher.ts
440
- var import_node_fs7 = require("fs");
525
+ var import_node_fs8 = require("fs");
441
526
  var import_node_path7 = require("path");
442
- var import_core9 = require("@penvhq/core");
527
+ var import_core11 = require("@penvhq/core");
443
528
 
444
529
  // src/add.ts
445
530
  var import_node_fs5 = require("fs");
446
531
  var import_node_module2 = require("module");
447
532
  var import_node_path5 = require("path");
533
+ var import_node_url = require("url");
448
534
  var import_core6 = require("@penvhq/core");
449
535
 
450
536
  // src/config-edit.ts
@@ -714,35 +800,14 @@ function writeDeclaration(options) {
714
800
  // src/store.ts
715
801
  var import_node_fs4 = require("fs");
716
802
  var import_node_path4 = require("path");
803
+ var import_core4 = require("@penvhq/core");
717
804
 
718
805
  // src/home.ts
719
806
  var import_node_fs3 = require("fs");
720
- var import_node_os2 = require("os");
721
807
  var import_node_path3 = require("path");
722
- var import_core4 = require("@penvhq/core");
723
- var PENV_HOME_VAR = "PENV_HOME";
724
808
  var HOME_META_FILE = "meta.json";
725
809
  var INTEGRITY_FILE = ".penv-integrity";
726
810
  var NPM_UPDATE_COMMAND = "npm install -g @penvhq/launcher";
727
- function penvHome(env) {
728
- const declared = env[PENV_HOME_VAR];
729
- if (declared !== void 0 && declared.trim() !== "") {
730
- return (0, import_node_path3.resolve)(declared);
731
- }
732
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".penv");
733
- }
734
- function packageDir(home, kind, name, version) {
735
- const bucket = (0, import_node_path3.resolve)(home, kind);
736
- const dir = (0, import_node_path3.resolve)(bucket, ...name.split("/"), version);
737
- if (!dir.startsWith(bucket + import_node_path3.sep)) {
738
- throw new import_core4.PenvError(
739
- "PENV_HOME_ESCAPE",
740
- `\`${name}\` at \`${version}\` resolves to ${dir}, which is outside ${bucket}`,
741
- "Name the package exactly as npm does, e.g. `@penvhq/provider-vault`."
742
- );
743
- }
744
- return dir;
745
- }
746
811
  function launcherUpdateCommand(home) {
747
812
  let meta;
748
813
  try {
@@ -844,7 +909,7 @@ function tarballUrl(pin) {
844
909
  return `${registry}/${pin.name}/-/${basename}-${pin.version}.tgz`;
845
910
  }
846
911
  function inspectInstall(home, kind, pin) {
847
- const dir = packageDir(home, kind, pin.name, pin.version);
912
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
848
913
  if (!(0, import_node_fs4.existsSync)(dir)) {
849
914
  return { dir, state: "absent" };
850
915
  }
@@ -858,7 +923,7 @@ function inspectInstall(home, kind, pin) {
858
923
  }
859
924
  async function installPin(options) {
860
925
  const { home, kind, pin, fetcher } = options;
861
- const dir = packageDir(home, kind, pin.name, pin.version);
926
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
862
927
  const url = tarballUrl(pin);
863
928
  let bytes;
864
929
  try {
@@ -923,7 +988,8 @@ async function fetchRelease(query) {
923
988
  throw new RegistryUnreadableError(
924
989
  query.name,
925
990
  url,
926
- cause instanceof Error ? cause.message : String(cause)
991
+ cause instanceof Error ? cause.message : String(cause),
992
+ query.retry
927
993
  );
928
994
  }
929
995
  let parsed;
@@ -933,7 +999,8 @@ async function fetchRelease(query) {
933
999
  throw new RegistryUnreadableError(
934
1000
  query.name,
935
1001
  url,
936
- cause instanceof Error ? cause.message : String(cause)
1002
+ cause instanceof Error ? cause.message : String(cause),
1003
+ query.retry
937
1004
  );
938
1005
  }
939
1006
  const packument = record(parsed);
@@ -949,7 +1016,7 @@ async function fetchRelease(query) {
949
1016
  const version = record(at(versions, asked)) === void 0 ? text3(at(tags, asked)) ?? asked : asked;
950
1017
  const release = record(at(versions, version));
951
1018
  if (release === void 0) {
952
- throw new VersionUnknownError(query.name, asked, url);
1019
+ throw new VersionUnknownError(query.name, asked, url, query.retry);
953
1020
  }
954
1021
  const dist = record(at(release, "dist"));
955
1022
  const integrity = text3(at(dist, "integrity"));
@@ -1231,6 +1298,26 @@ function resolvePackageDir(name, root) {
1231
1298
  return void 0;
1232
1299
  }
1233
1300
  }
1301
+ function resolveProjectEntry(name, root) {
1302
+ let file;
1303
+ try {
1304
+ file = (0, import_node_module2.createRequire)((0, import_node_path5.resolve)(root, "noop.js")).resolve(name);
1305
+ } catch {
1306
+ return void 0;
1307
+ }
1308
+ return { file, importable: (0, import_core6.isImportableEntry)(file) };
1309
+ }
1310
+ async function assertLoadable(name, entry, local) {
1311
+ if (entry === void 0 || !entry.importable) {
1312
+ throw new ExtensionNotImportableError(name, entry?.file, local);
1313
+ }
1314
+ try {
1315
+ await import((0, import_node_url.pathToFileURL)(entry.file).href);
1316
+ } catch (cause) {
1317
+ const detail = cause instanceof Error ? cause.message : String(cause);
1318
+ throw new ExtensionUnloadableError(name, entry.file, detail, local);
1319
+ }
1320
+ }
1234
1321
  function recordLocalExtension(root, name) {
1235
1322
  const file = (0, import_core6.localExtensionsFile)(root);
1236
1323
  let current;
@@ -1251,6 +1338,7 @@ async function addLocal(options, request) {
1251
1338
  if (dir === void 0) {
1252
1339
  throw new LocalExtensionUnresolvedError(request.name, root);
1253
1340
  }
1341
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1254
1342
  const installed = readExtensionPackage(dir);
1255
1343
  const declaration = writeDeclaration({
1256
1344
  root,
@@ -1262,7 +1350,7 @@ async function addLocal(options, request) {
1262
1350
  types: installed.types
1263
1351
  });
1264
1352
  recordLocalExtension(root, request.name);
1265
- io2.out(`\u2713 ${request.name} resolves from this project \u2014 nothing is pinned`);
1353
+ io2.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1266
1354
  io2.out(`\u2713 ${import_core6.LOCAL_EXTENSIONS_PATH} records it`);
1267
1355
  io2.out(`\u2713 ${declaration} declares its config type`);
1268
1356
  await offerConfigEdit(options, request.name);
@@ -1307,6 +1395,7 @@ async function add(options) {
1307
1395
  },
1308
1396
  fetcher
1309
1397
  });
1398
+ await assertLoadable(release.name, (0, import_core6.packageEntry)(dir), false);
1310
1399
  const installed = readExtensionPackage(dir);
1311
1400
  const declaration = writeDeclaration({
1312
1401
  root,
@@ -1325,14 +1414,59 @@ async function add(options) {
1325
1414
  return { onboard: await offerOnboarding(io2, release.name, installed.onboard) };
1326
1415
  }
1327
1416
 
1328
- // src/pins.ts
1417
+ // src/help.ts
1418
+ var import_install = require("@penvhq/cli/install");
1329
1419
  var import_core7 = require("@penvhq/core");
1420
+ function printLauncherCommands(io2) {
1421
+ io2.out("");
1422
+ io2.out("LAUNCHER COMMANDS (penv itself, whatever engine a project pins)");
1423
+ io2.out("");
1424
+ io2.out(` install Installs everything ${import_core7.MANIFEST_PATH} pins`);
1425
+ io2.out(" add <package> Pins a provider extension, and commits the decision");
1426
+ io2.out(` add ${LOCAL_FLAG} <package> Adds the one this repository builds \u2014 nothing is pinned`);
1427
+ io2.out(` upgrade [version] Moves the engine pin and ${import_install.RUNTIME_PACKAGE} together`);
1428
+ io2.out("");
1429
+ }
1430
+ function printInstallHelp(io2) {
1431
+ io2.out(`${INSTALL_COMMAND}`);
1432
+ io2.out("");
1433
+ io2.out(`Downloads, verifies and installs every version ${import_core7.MANIFEST_PATH} pins \u2014 the engine and`);
1434
+ io2.out("every extension \u2014 into $PENV_HOME. The one penv command CI and production run that");
1435
+ io2.out("may reach the registry.");
1436
+ }
1437
+ function printAddHelp(io2) {
1438
+ io2.out("penv add <package>[@<version>]");
1439
+ io2.out("");
1440
+ io2.out(` ${LOCAL_FLAG} Add the package this repository builds, resolved from it`);
1441
+ io2.out(" --registry <url> Take the release from a private registry instead of npmjs");
1442
+ io2.out(
1443
+ ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1444
+ );
1445
+ io2.out("");
1446
+ io2.out(`Pins the release in ${import_core7.MANIFEST_PATH} and writes its type declaration to`);
1447
+ io2.out(`${import_core7.EXTENSIONS_PATH}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1448
+ io2.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1449
+ }
1450
+ function printUpgradeHelp(io2) {
1451
+ io2.out("penv upgrade [version]");
1452
+ io2.out("");
1453
+ io2.out(` ${YES_FLAG} Skip the question \u2014 unattended runs need it and a version`);
1454
+ io2.out("");
1455
+ io2.out(`Moves the ${import_core7.ENGINE_PACKAGE} pin in ${import_core7.MANIFEST_PATH} and this project's`);
1456
+ io2.out(`${import_install.RUNTIME_PACKAGE} dependency to the same exact version. No version takes whatever`);
1457
+ io2.out("`latest` points at; the integrity is the one the registry states, never one penv");
1458
+ io2.out("computed. Both files are shown before either moves, and they move together or not");
1459
+ io2.out("at all. Extensions keep their own pins.");
1460
+ }
1461
+
1462
+ // src/pins.ts
1463
+ var import_core8 = require("@penvhq/core");
1330
1464
  var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1331
1465
  var DEV_PIN_VERSION = "0.0.0-dev";
1332
1466
  var BUNDLED_ENGINE_PIN = {
1333
- package: import_core7.ENGINE_PACKAGE,
1334
- version: "0.9.5",
1335
- integrity: "sha512-cacsvynscs6G0NThpGaK1BfKy3POQSoogI/vvQNZ5sTZaftzwg4mPtUpT+i2GSc3KbYAHQu4Kq9MpYLOvSvulA=="
1467
+ package: import_core8.ENGINE_PACKAGE,
1468
+ version: "0.11.0",
1469
+ integrity: "sha512-Y+WKyuPsX4U8/1j0e+VMUhj0LaGrp67YEoDAWyyinrgA+c8IM4H1blqje/YK4LsQwzQDQQDF9bhWIhfmDAWoxA=="
1336
1470
  };
1337
1471
  function assertReleasePin(pin) {
1338
1472
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1350,8 +1484,8 @@ function releaseEnginePin(pin, ranVersion) {
1350
1484
  // src/project.ts
1351
1485
  var import_node_fs6 = require("fs");
1352
1486
  var import_node_path6 = require("path");
1353
- var import_core8 = require("@penvhq/core");
1354
- var MANIFEST_SEGMENTS = import_core8.MANIFEST_PATH.split("/");
1487
+ var import_core9 = require("@penvhq/core");
1488
+ var MANIFEST_SEGMENTS = import_core9.MANIFEST_PATH.split("/");
1355
1489
  function findUp(cwd, matches) {
1356
1490
  let dir = (0, import_node_path6.resolve)(cwd);
1357
1491
  for (; ; ) {
@@ -1370,12 +1504,113 @@ function findProject(cwd) {
1370
1504
  return root === void 0 ? void 0 : { root, manifestFile: (0, import_node_path6.join)(root, ...MANIFEST_SEGMENTS) };
1371
1505
  }
1372
1506
  function findAdoptedRoot(cwd) {
1373
- return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core8.stateDir)(dir)));
1507
+ return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core9.stateDir)(dir)));
1508
+ }
1509
+
1510
+ // src/upgrade.ts
1511
+ var import_node_fs7 = require("fs");
1512
+ var import_install2 = require("@penvhq/cli/install");
1513
+ var import_core10 = require("@penvhq/core");
1514
+ function parseRequest2(argv) {
1515
+ let version;
1516
+ let yes = false;
1517
+ for (const token of argv) {
1518
+ if (token === YES_FLAG) {
1519
+ yes = true;
1520
+ continue;
1521
+ }
1522
+ if (token.startsWith("-")) {
1523
+ throw new UpgradeFlagError(token);
1524
+ }
1525
+ if (version !== void 0 || token === "") {
1526
+ throw new UpgradeSubjectError();
1527
+ }
1528
+ version = token;
1529
+ }
1530
+ return { version, yes };
1531
+ }
1532
+ function renderPinChange(engine, release) {
1533
+ return [
1534
+ import_core10.MANIFEST_PATH,
1535
+ ` - "integrity": "${engine.integrity}"`,
1536
+ ` - "version": "${engine.version}"`,
1537
+ ` + "integrity": "${release.integrity}"`,
1538
+ ` + "version": "${release.version}"`
1539
+ ];
1540
+ }
1541
+ function pinnedElsewhere(manifest, version) {
1542
+ return Object.entries(manifest.extensions).filter(([, entry]) => entry.version !== version).map(([name]) => name);
1543
+ }
1544
+ async function consent(options, release, plan, yes) {
1545
+ const { io: io2, manifest } = options;
1546
+ io2.out(`${import_core10.ENGINE_PACKAGE} ${manifest.engine.version} \u2192 ${release.version}`);
1547
+ io2.out("");
1548
+ for (const line of renderPinChange(manifest.engine, release)) {
1549
+ io2.out(line);
1550
+ }
1551
+ for (const line of (0, import_install2.renderInstallPlan)(plan)) {
1552
+ io2.out(line);
1553
+ }
1554
+ if (yes) {
1555
+ return;
1556
+ }
1557
+ if (!await io2.confirm(`Move this project to ${release.version}?`)) {
1558
+ throw new UpgradeDeclinedError(release.version);
1559
+ }
1560
+ }
1561
+ async function upgrade(options) {
1562
+ const { io: io2, fetcher, home, root, manifest, manifestFile } = options;
1563
+ const request = parseRequest2(options.argv);
1564
+ if (options.noDownload === true) {
1565
+ throw new UpgradeNoDownloadError();
1566
+ }
1567
+ if ((options.ci === true || !io2.interactive) && !(request.yes && request.version !== void 0)) {
1568
+ throw new UpgradeUnattendedError();
1569
+ }
1570
+ const release = await fetchRelease({
1571
+ name: import_core10.ENGINE_PACKAGE,
1572
+ ...request.version === void 0 ? {} : { version: request.version },
1573
+ retry: UPGRADE_COMMAND,
1574
+ fetcher
1575
+ });
1576
+ const plan = (0, import_install2.planInstall)(root, release.version);
1577
+ if (manifest.engine.version === release.version && plan.satisfied) {
1578
+ io2.out(`\u2713 Already on ${import_core10.ENGINE_PACKAGE} ${release.version} \u2014 nothing to move`);
1579
+ return;
1580
+ }
1581
+ const manifestText = (0, import_core10.serializeManifest)({
1582
+ ...manifest,
1583
+ engine: { package: import_core10.ENGINE_PACKAGE, version: release.version, integrity: release.integrity }
1584
+ });
1585
+ await consent(options, release, plan, request.yes);
1586
+ await installPin({
1587
+ home,
1588
+ kind: "engines",
1589
+ pin: { name: import_core10.ENGINE_PACKAGE, version: release.version, integrity: release.integrity },
1590
+ fetcher
1591
+ });
1592
+ io2.out(`\u2713 ${import_core10.ENGINE_PACKAGE} ${release.version} installed`);
1593
+ if (!plan.satisfied) {
1594
+ try {
1595
+ await (options.install ?? import_install2.installWithPackageManager)(plan);
1596
+ } catch {
1597
+ throw new UpgradeInstallFailedError(plan.command.join(" "));
1598
+ }
1599
+ io2.out(`\u2713 package.json depends on ${import_install2.RUNTIME_PACKAGE} ${release.version}`);
1600
+ }
1601
+ (0, import_node_fs7.writeFileSync)(manifestFile, manifestText);
1602
+ io2.out(`\u2713 ${import_core10.MANIFEST_PATH} pins ${import_core10.ENGINE_PACKAGE} ${release.version}`);
1603
+ const kept = pinnedElsewhere(manifest, release.version);
1604
+ if (kept.length > 0) {
1605
+ const subject = kept.length === 1 ? "keeps its own pin" : "keep their own pins";
1606
+ io2.out(`\u2192 ${kept.join(", ")} ${subject} \u2014 \`penv add <package>@<version>\` moves one.`);
1607
+ }
1374
1608
  }
1375
1609
 
1376
1610
  // src/launcher.ts
1377
1611
  var INSTALL = "install";
1378
1612
  var ADD = "add";
1613
+ var UPGRADE = "upgrade";
1379
1614
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v"]);
1380
1615
  var HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
1381
1616
  var NO_DOWNLOAD = "--no-download";
@@ -1419,27 +1654,27 @@ function pinsOf(manifest) {
1419
1654
  ];
1420
1655
  }
1421
1656
  function reportable(error, home, argv) {
1422
- return error instanceof import_core9.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1657
+ return error instanceof import_core11.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1423
1658
  updateCommand: launcherUpdateCommand(home),
1424
1659
  invokedCommand: invokedCommand(argv)
1425
1660
  }) : error;
1426
1661
  }
1427
1662
  function readManifest(manifestFile, home, argv) {
1428
1663
  try {
1429
- return (0, import_core9.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1664
+ return (0, import_core11.parseManifest)((0, import_node_fs8.readFileSync)(manifestFile, "utf8"));
1430
1665
  } catch (error) {
1431
1666
  throw reportable(error, home, argv);
1432
1667
  }
1433
1668
  }
1434
1669
  function readManifestToRepair(manifestFile, home, argv) {
1435
1670
  try {
1436
- return readManifestForRepair((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1671
+ return readManifestForRepair((0, import_node_fs8.readFileSync)(manifestFile, "utf8"));
1437
1672
  } catch (error) {
1438
1673
  throw reportable(error, home, argv);
1439
1674
  }
1440
1675
  }
1441
1676
  function report(error, io2) {
1442
- if (error instanceof import_core9.PenvError && error.remedy !== void 0) {
1677
+ if (error instanceof import_core11.PenvError && error.remedy !== void 0) {
1443
1678
  const suffix = `
1444
1679
  ${error.remedy}`;
1445
1680
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1461,7 +1696,21 @@ async function launch(options) {
1461
1696
  const { argv, cwd, env, io: io2 } = options;
1462
1697
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1463
1698
  const first = forwarded[0];
1464
- const home = penvHome(env);
1699
+ const home = (0, import_core11.penvHome)(env);
1700
+ if (first !== void 0 && forwarded.slice(1).some((token) => HELP_FLAGS.has(token))) {
1701
+ if (first === INSTALL) {
1702
+ printInstallHelp(io2);
1703
+ return 0;
1704
+ }
1705
+ if (first === ADD) {
1706
+ printAddHelp(io2);
1707
+ return 0;
1708
+ }
1709
+ if (first === UPGRADE) {
1710
+ printUpgradeHelp(io2);
1711
+ return 0;
1712
+ }
1713
+ }
1465
1714
  const project = findProject(cwd);
1466
1715
  if (project === void 0) {
1467
1716
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1488,6 +1737,20 @@ async function launch(options) {
1488
1737
  io2.out(`penv ${manifest.engine.version}`);
1489
1738
  return 0;
1490
1739
  }
1740
+ if (first === UPGRADE) {
1741
+ await upgrade({
1742
+ argv: forwarded.slice(1),
1743
+ root: project.root,
1744
+ manifestFile: project.manifestFile,
1745
+ manifest,
1746
+ home,
1747
+ io: io2,
1748
+ fetcher: options.fetcher,
1749
+ noDownload,
1750
+ ci: isCi(env)
1751
+ });
1752
+ return 0;
1753
+ }
1491
1754
  const enginePin = enginePinOf(manifest);
1492
1755
  const engineDir = await ensure(options, "engines", enginePin, home, noDownload);
1493
1756
  for (const pin of extensionPinsOf(manifest)) {
@@ -1506,16 +1769,16 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1506
1769
  if (root === void 0) {
1507
1770
  return 0;
1508
1771
  }
1509
- const manifestFile = (0, import_node_path7.join)(root, ...import_core9.MANIFEST_PATH.split("/"));
1510
- if ((0, import_node_fs7.existsSync)(manifestFile)) {
1772
+ const manifestFile = (0, import_node_path7.join)(root, ...import_core11.MANIFEST_PATH.split("/"));
1773
+ if ((0, import_node_fs8.existsSync)(manifestFile)) {
1511
1774
  return 0;
1512
1775
  }
1513
1776
  const pin = releaseEnginePin(options.bundledPin, engine.version);
1514
- (0, import_node_fs7.writeFileSync)(
1777
+ (0, import_node_fs8.writeFileSync)(
1515
1778
  manifestFile,
1516
- (0, import_core9.serializeManifest)({ format: import_core9.MANIFEST_FORMAT, engine: pin, extensions: {} })
1779
+ (0, import_core11.serializeManifest)({ format: import_core11.MANIFEST_FORMAT, engine: pin, extensions: {} })
1517
1780
  );
1518
- options.io.out(`\u2713 ${import_core9.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1781
+ options.io.out(`\u2713 ${import_core11.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1519
1782
  await ensureAdoptedEngine(
1520
1783
  options,
1521
1784
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1594,13 +1857,21 @@ async function addExtension(options, project, home, argv, noDownload) {
1594
1857
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1595
1858
  return delegate(options, engine, onboard, home, options.cwd);
1596
1859
  }
1597
- function delegate(options, engine, forwarded, home, cwd) {
1598
- return options.spawn({
1860
+ function isRootHelp(forwarded) {
1861
+ const first = forwarded[0];
1862
+ return first === void 0 || HELP_FLAGS.has(first);
1863
+ }
1864
+ async function delegate(options, engine, forwarded, home, cwd) {
1865
+ const code = await options.spawn({
1599
1866
  command: process.execPath,
1600
1867
  args: [engine.entry, ...forwarded],
1601
1868
  cwd,
1602
- env: { ...options.env, [PENV_HOME_VAR]: home }
1869
+ env: { ...options.env, [import_core11.PENV_HOME_VAR]: home }
1603
1870
  });
1871
+ if (code === 0 && isRootHelp(forwarded)) {
1872
+ printLauncherCommands(options.io);
1873
+ }
1874
+ return code;
1604
1875
  }
1605
1876
 
1606
1877
  // src/bin.ts