@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/index.cjs CHANGED
@@ -41,6 +41,8 @@ __export(src_exports, {
41
41
  EngineEntryError: () => EngineEntryError,
42
42
  EnginePinMismatchError: () => EnginePinMismatchError,
43
43
  EnginePinUnreleasedError: () => EnginePinUnreleasedError,
44
+ ExtensionNotImportableError: () => ExtensionNotImportableError,
45
+ ExtensionUnloadableError: () => ExtensionUnloadableError,
44
46
  HOME_META_FILE: () => HOME_META_FILE,
45
47
  INSTALL_COMMAND: () => INSTALL_COMMAND,
46
48
  INTEGRITY_FILE: () => INTEGRITY_FILE,
@@ -51,7 +53,6 @@ __export(src_exports, {
51
53
  NPM_UPDATE_COMMAND: () => NPM_UPDATE_COMMAND,
52
54
  NoProjectError: () => NoProjectError,
53
55
  OfficialRegistryError: () => OfficialRegistryError,
54
- PENV_HOME_VAR: () => PENV_HOME_VAR,
55
56
  PackageCorruptError: () => PackageCorruptError,
56
57
  PackageMissingError: () => PackageMissingError,
57
58
  PackageTooYoungError: () => PackageTooYoungError,
@@ -62,7 +63,15 @@ __export(src_exports, {
62
63
  TrustDeclinedError: () => TrustDeclinedError,
63
64
  TrustPublisherMissingError: () => TrustPublisherMissingError,
64
65
  TrustReasonMissingError: () => TrustReasonMissingError,
66
+ UPGRADE_COMMAND: () => UPGRADE_COMMAND,
67
+ UpgradeDeclinedError: () => UpgradeDeclinedError,
68
+ UpgradeFlagError: () => UpgradeFlagError,
69
+ UpgradeInstallFailedError: () => UpgradeInstallFailedError,
70
+ UpgradeNoDownloadError: () => UpgradeNoDownloadError,
71
+ UpgradeSubjectError: () => UpgradeSubjectError,
72
+ UpgradeUnattendedError: () => UpgradeUnattendedError,
65
73
  VersionUnknownError: () => VersionUnknownError,
74
+ YES_FLAG: () => YES_FLAG,
66
75
  add: () => add,
67
76
  assertReleasePin: () => assertReleasePin,
68
77
  bundledEngine: () => bundledEngine,
@@ -77,9 +86,7 @@ __export(src_exports, {
77
86
  integrityOf: () => integrityOf,
78
87
  launcherUpdateCommand: () => launcherUpdateCommand,
79
88
  nodeSpawner: () => nodeSpawner,
80
- packageDir: () => packageDir,
81
89
  packumentUrl: () => packumentUrl,
82
- penvHome: () => penvHome,
83
90
  readExtensionPackage: () => readExtensionPackage,
84
91
  readProviderEntries: () => readProviderEntries,
85
92
  readTarball: () => readTarball,
@@ -88,6 +95,7 @@ __export(src_exports, {
88
95
  runLauncher: () => runLauncher,
89
96
  setProviderType: () => setProviderType,
90
97
  tarballUrl: () => tarballUrl,
98
+ upgrade: () => upgrade,
91
99
  writeDeclaration: () => writeDeclaration
92
100
  });
93
101
  module.exports = __toCommonJS(src_exports);
@@ -96,6 +104,7 @@ module.exports = __toCommonJS(src_exports);
96
104
  var import_node_fs4 = require("fs");
97
105
  var import_node_module = require("module");
98
106
  var import_node_path4 = require("path");
107
+ var import_node_url = require("url");
99
108
  var import_core5 = require("@penvhq/core");
100
109
 
101
110
  // src/config-edit.ts
@@ -268,12 +277,17 @@ var import_core2 = require("@penvhq/core");
268
277
  // src/errors.ts
269
278
  var import_core = require("@penvhq/core");
270
279
  var INSTALL_COMMAND = "penv install";
280
+ var UPGRADE_COMMAND = "penv upgrade";
281
+ var YES_FLAG = "--yes";
271
282
  var MIN_PACKAGE_AGE_DAYS = 7;
272
283
  var TRUST_YOUNG_FLAG = "--trust-young";
273
284
  function addCommand(name, version, extra) {
274
285
  const spec = version === void 0 ? name : `${name}@${version}`;
275
286
  return `penv add ${spec}${extra === void 0 ? "" : ` ${extra}`}`;
276
287
  }
288
+ function addCommandFor(name, local) {
289
+ return local ? `penv add ${LOCAL_FLAG} ${name}` : addCommand(name);
290
+ }
277
291
  var NoProjectError = class extends import_core.PenvError {
278
292
  name = "NoProjectError";
279
293
  constructor(cwd) {
@@ -385,6 +399,26 @@ var AddLocalFlagError = class extends import_core.PenvError {
385
399
  );
386
400
  }
387
401
  };
402
+ var ExtensionNotImportableError = class extends import_core.PenvError {
403
+ name = "ExtensionNotImportableError";
404
+ constructor(name, entry, local) {
405
+ super(
406
+ "PENV_EXTENSION_NOT_IMPORTABLE",
407
+ entry === void 0 ? `${name} declares no entry point penv can import` : `${name} resolves to ${entry}, which penv cannot import`,
408
+ `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.`
409
+ );
410
+ }
411
+ };
412
+ var ExtensionUnloadableError = class extends import_core.PenvError {
413
+ name = "ExtensionUnloadableError";
414
+ constructor(name, entry, detail, local) {
415
+ super(
416
+ "PENV_EXTENSION_UNLOADABLE",
417
+ `${name} threw while penv imported ${entry}: ${detail}`,
418
+ `Build the package and install its dependencies, then run \`${addCommandFor(name, local)}\` again. penv adds a provider it has loaded once, or none.`
419
+ );
420
+ }
421
+ };
388
422
  var LocalExtensionUnresolvedError = class extends import_core.PenvError {
389
423
  name = "LocalExtensionUnresolvedError";
390
424
  constructor(name, root) {
@@ -427,11 +461,11 @@ var OfficialRegistryError = class extends import_core.PenvError {
427
461
  };
428
462
  var RegistryUnreadableError = class extends import_core.PenvError {
429
463
  name = "RegistryUnreadableError";
430
- constructor(name, url, detail) {
464
+ constructor(name, url, detail, retry = addCommand(name)) {
431
465
  super(
432
466
  "PENV_REGISTRY_UNREADABLE",
433
467
  `Reading ${name} from ${url} failed: ${detail}`,
434
- `Run \`${addCommand(name)}\` again when the registry is reachable.`
468
+ `Run \`${retry}\` again when the registry is reachable.`
435
469
  );
436
470
  }
437
471
  };
@@ -447,11 +481,11 @@ var PackageUnknownError = class extends import_core.PenvError {
447
481
  };
448
482
  var VersionUnknownError = class extends import_core.PenvError {
449
483
  name = "VersionUnknownError";
450
- constructor(name, version, url) {
484
+ constructor(name, version, url, retry = addCommand(name)) {
451
485
  super(
452
486
  "PENV_VERSION_UNKNOWN",
453
487
  `${url} publishes no ${version} of ${name}`,
454
- `Run \`${addCommand(name)}\` to take the version \`latest\` points at.`
488
+ `Run \`${retry}\` to take the version \`latest\` points at.`
455
489
  );
456
490
  }
457
491
  };
@@ -576,6 +610,66 @@ var EnginePinMismatchError = class extends import_core.PenvError {
576
610
  );
577
611
  }
578
612
  };
613
+ var UpgradeSubjectError = class extends import_core.PenvError {
614
+ name = "UpgradeSubjectError";
615
+ constructor() {
616
+ super(
617
+ "PENV_UPGRADE_SUBJECT",
618
+ "`penv upgrade` takes one version, or none",
619
+ `Run \`${UPGRADE_COMMAND}\` to move to whatever \`latest\` points at, or \`${UPGRADE_COMMAND} 0.9.6\` to move to an exact release.`
620
+ );
621
+ }
622
+ };
623
+ var UpgradeFlagError = class extends import_core.PenvError {
624
+ name = "UpgradeFlagError";
625
+ constructor(flag) {
626
+ super(
627
+ "PENV_UPGRADE_FLAG",
628
+ `\`${UPGRADE_COMMAND}\` does not understand \`${flag}\``,
629
+ `Run \`${UPGRADE_COMMAND} [version]\` with \`${YES_FLAG}\` \u2014 that is the one flag it takes.`
630
+ );
631
+ }
632
+ };
633
+ var UpgradeNoDownloadError = class extends import_core.PenvError {
634
+ name = "UpgradeNoDownloadError";
635
+ constructor() {
636
+ super(
637
+ "PENV_UPGRADE_NO_DOWNLOAD",
638
+ `Upgrading means reading the registry for the version and integrity to pin, and \`--no-download\` says this run does not`,
639
+ `Run \`${UPGRADE_COMMAND}\` without \`--no-download\`. Nothing was fetched or written.`
640
+ );
641
+ }
642
+ };
643
+ var UpgradeUnattendedError = class extends import_core.PenvError {
644
+ name = "UpgradeUnattendedError";
645
+ constructor() {
646
+ super(
647
+ "PENV_UPGRADE_UNATTENDED",
648
+ `Upgrading rewrites ${import_core.MANIFEST_PATH} and package.json, and this run has nobody to decide that`,
649
+ `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.`
650
+ );
651
+ }
652
+ };
653
+ var UpgradeDeclinedError = class extends import_core.PenvError {
654
+ name = "UpgradeDeclinedError";
655
+ constructor(version) {
656
+ super(
657
+ "PENV_UPGRADE_DECLINED",
658
+ `${import_core.ENGINE_PACKAGE} ${version} was not installed, so ${import_core.MANIFEST_PATH} and package.json are unchanged`,
659
+ `Run \`${UPGRADE_COMMAND} ${version}\` again when you want both of them to move.`
660
+ );
661
+ }
662
+ };
663
+ var UpgradeInstallFailedError = class extends import_core.PenvError {
664
+ name = "UpgradeInstallFailedError";
665
+ constructor(command) {
666
+ super(
667
+ "PENV_UPGRADE_INSTALL_FAILED",
668
+ `${command} did not finish, so ${import_core.MANIFEST_PATH} still pins the engine it pinned before`,
669
+ `Run \`${command}\` yourself, then run \`${UPGRADE_COMMAND}\` again \u2014 the pin and the dependency move together or not at all.`
670
+ );
671
+ }
672
+ };
579
673
  var EngineEntryError = class extends import_core.PenvError {
580
674
  name = "EngineEntryError";
581
675
  constructor(name, version, dir) {
@@ -689,35 +783,14 @@ function writeDeclaration(options) {
689
783
  // src/store.ts
690
784
  var import_node_fs3 = require("fs");
691
785
  var import_node_path3 = require("path");
786
+ var import_core3 = require("@penvhq/core");
692
787
 
693
788
  // src/home.ts
694
789
  var import_node_fs2 = require("fs");
695
- var import_node_os = require("os");
696
790
  var import_node_path2 = require("path");
697
- var import_core3 = require("@penvhq/core");
698
- var PENV_HOME_VAR = "PENV_HOME";
699
791
  var HOME_META_FILE = "meta.json";
700
792
  var INTEGRITY_FILE = ".penv-integrity";
701
793
  var NPM_UPDATE_COMMAND = "npm install -g @penvhq/launcher";
702
- function penvHome(env) {
703
- const declared = env[PENV_HOME_VAR];
704
- if (declared !== void 0 && declared.trim() !== "") {
705
- return (0, import_node_path2.resolve)(declared);
706
- }
707
- return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".penv");
708
- }
709
- function packageDir(home, kind, name, version) {
710
- const bucket = (0, import_node_path2.resolve)(home, kind);
711
- const dir = (0, import_node_path2.resolve)(bucket, ...name.split("/"), version);
712
- if (!dir.startsWith(bucket + import_node_path2.sep)) {
713
- throw new import_core3.PenvError(
714
- "PENV_HOME_ESCAPE",
715
- `\`${name}\` at \`${version}\` resolves to ${dir}, which is outside ${bucket}`,
716
- "Name the package exactly as npm does, e.g. `@penvhq/provider-vault`."
717
- );
718
- }
719
- return dir;
720
- }
721
794
  function launcherUpdateCommand(home) {
722
795
  let meta;
723
796
  try {
@@ -819,7 +892,7 @@ function tarballUrl(pin) {
819
892
  return `${registry}/${pin.name}/-/${basename}-${pin.version}.tgz`;
820
893
  }
821
894
  function inspectInstall(home, kind, pin) {
822
- const dir = packageDir(home, kind, pin.name, pin.version);
895
+ const dir = (0, import_core3.packageDir)(home, kind, pin.name, pin.version);
823
896
  if (!(0, import_node_fs3.existsSync)(dir)) {
824
897
  return { dir, state: "absent" };
825
898
  }
@@ -833,7 +906,7 @@ function inspectInstall(home, kind, pin) {
833
906
  }
834
907
  async function installPin(options) {
835
908
  const { home, kind, pin, fetcher } = options;
836
- const dir = packageDir(home, kind, pin.name, pin.version);
909
+ const dir = (0, import_core3.packageDir)(home, kind, pin.name, pin.version);
837
910
  const url = tarballUrl(pin);
838
911
  let bytes;
839
912
  try {
@@ -898,7 +971,8 @@ async function fetchRelease(query) {
898
971
  throw new RegistryUnreadableError(
899
972
  query.name,
900
973
  url,
901
- cause instanceof Error ? cause.message : String(cause)
974
+ cause instanceof Error ? cause.message : String(cause),
975
+ query.retry
902
976
  );
903
977
  }
904
978
  let parsed;
@@ -908,7 +982,8 @@ async function fetchRelease(query) {
908
982
  throw new RegistryUnreadableError(
909
983
  query.name,
910
984
  url,
911
- cause instanceof Error ? cause.message : String(cause)
985
+ cause instanceof Error ? cause.message : String(cause),
986
+ query.retry
912
987
  );
913
988
  }
914
989
  const packument = record(parsed);
@@ -924,7 +999,7 @@ async function fetchRelease(query) {
924
999
  const version = record(at(versions, asked)) === void 0 ? text3(at(tags, asked)) ?? asked : asked;
925
1000
  const release = record(at(versions, version));
926
1001
  if (release === void 0) {
927
- throw new VersionUnknownError(query.name, asked, url);
1002
+ throw new VersionUnknownError(query.name, asked, url, query.retry);
928
1003
  }
929
1004
  const dist = record(at(release, "dist"));
930
1005
  const integrity = text3(at(dist, "integrity"));
@@ -1206,6 +1281,26 @@ function resolvePackageDir(name, root) {
1206
1281
  return void 0;
1207
1282
  }
1208
1283
  }
1284
+ function resolveProjectEntry(name, root) {
1285
+ let file;
1286
+ try {
1287
+ file = (0, import_node_module.createRequire)((0, import_node_path4.resolve)(root, "noop.js")).resolve(name);
1288
+ } catch {
1289
+ return void 0;
1290
+ }
1291
+ return { file, importable: (0, import_core5.isImportableEntry)(file) };
1292
+ }
1293
+ async function assertLoadable(name, entry, local) {
1294
+ if (entry === void 0 || !entry.importable) {
1295
+ throw new ExtensionNotImportableError(name, entry?.file, local);
1296
+ }
1297
+ try {
1298
+ await import((0, import_node_url.pathToFileURL)(entry.file).href);
1299
+ } catch (cause) {
1300
+ const detail = cause instanceof Error ? cause.message : String(cause);
1301
+ throw new ExtensionUnloadableError(name, entry.file, detail, local);
1302
+ }
1303
+ }
1209
1304
  function recordLocalExtension(root, name) {
1210
1305
  const file = (0, import_core5.localExtensionsFile)(root);
1211
1306
  let current;
@@ -1226,6 +1321,7 @@ async function addLocal(options, request) {
1226
1321
  if (dir === void 0) {
1227
1322
  throw new LocalExtensionUnresolvedError(request.name, root);
1228
1323
  }
1324
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1229
1325
  const installed = readExtensionPackage(dir);
1230
1326
  const declaration = writeDeclaration({
1231
1327
  root,
@@ -1237,7 +1333,7 @@ async function addLocal(options, request) {
1237
1333
  types: installed.types
1238
1334
  });
1239
1335
  recordLocalExtension(root, request.name);
1240
- io.out(`\u2713 ${request.name} resolves from this project \u2014 nothing is pinned`);
1336
+ io.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1241
1337
  io.out(`\u2713 ${import_core5.LOCAL_EXTENSIONS_PATH} records it`);
1242
1338
  io.out(`\u2713 ${declaration} declares its config type`);
1243
1339
  await offerConfigEdit(options, request.name);
@@ -1282,6 +1378,7 @@ async function add(options) {
1282
1378
  },
1283
1379
  fetcher
1284
1380
  });
1381
+ await assertLoadable(release.name, (0, import_core5.packageEntry)(dir), false);
1285
1382
  const installed = readExtensionPackage(dir);
1286
1383
  const declaration = writeDeclaration({
1287
1384
  root,
@@ -1302,10 +1399,10 @@ async function add(options) {
1302
1399
 
1303
1400
  // src/delegate.ts
1304
1401
  var import_node_child_process = require("child_process");
1305
- var import_node_os2 = require("os");
1402
+ var import_node_os = require("os");
1306
1403
  var FORWARDED = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
1307
1404
  function signalNumber(signal) {
1308
- const signals = import_node_os2.constants.signals;
1405
+ const signals = import_node_os.constants.signals;
1309
1406
  return signals[signal] ?? 0;
1310
1407
  }
1311
1408
  function nodeSpawner() {
@@ -1412,18 +1509,63 @@ function httpFetcher() {
1412
1509
  }
1413
1510
 
1414
1511
  // src/launcher.ts
1415
- var import_node_fs7 = require("fs");
1512
+ var import_node_fs8 = require("fs");
1416
1513
  var import_node_path7 = require("path");
1417
- var import_core9 = require("@penvhq/core");
1514
+ var import_core11 = require("@penvhq/core");
1418
1515
 
1419
- // src/pins.ts
1516
+ // src/help.ts
1517
+ var import_install = require("@penvhq/cli/install");
1420
1518
  var import_core7 = require("@penvhq/core");
1519
+ function printLauncherCommands(io) {
1520
+ io.out("");
1521
+ io.out("LAUNCHER COMMANDS (penv itself, whatever engine a project pins)");
1522
+ io.out("");
1523
+ io.out(` install Installs everything ${import_core7.MANIFEST_PATH} pins`);
1524
+ io.out(" add <package> Pins a provider extension, and commits the decision");
1525
+ io.out(` add ${LOCAL_FLAG} <package> Adds the one this repository builds \u2014 nothing is pinned`);
1526
+ io.out(` upgrade [version] Moves the engine pin and ${import_install.RUNTIME_PACKAGE} together`);
1527
+ io.out("");
1528
+ }
1529
+ function printInstallHelp(io) {
1530
+ io.out(`${INSTALL_COMMAND}`);
1531
+ io.out("");
1532
+ io.out(`Downloads, verifies and installs every version ${import_core7.MANIFEST_PATH} pins \u2014 the engine and`);
1533
+ io.out("every extension \u2014 into $PENV_HOME. The one penv command CI and production run that");
1534
+ io.out("may reach the registry.");
1535
+ }
1536
+ function printAddHelp(io) {
1537
+ io.out("penv add <package>[@<version>]");
1538
+ io.out("");
1539
+ io.out(` ${LOCAL_FLAG} Add the package this repository builds, resolved from it`);
1540
+ io.out(" --registry <url> Take the release from a private registry instead of npmjs");
1541
+ io.out(
1542
+ ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1543
+ );
1544
+ io.out("");
1545
+ io.out(`Pins the release in ${import_core7.MANIFEST_PATH} and writes its type declaration to`);
1546
+ io.out(`${import_core7.EXTENSIONS_PATH}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1547
+ io.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1548
+ }
1549
+ function printUpgradeHelp(io) {
1550
+ io.out("penv upgrade [version]");
1551
+ io.out("");
1552
+ io.out(` ${YES_FLAG} Skip the question \u2014 unattended runs need it and a version`);
1553
+ io.out("");
1554
+ io.out(`Moves the ${import_core7.ENGINE_PACKAGE} pin in ${import_core7.MANIFEST_PATH} and this project's`);
1555
+ io.out(`${import_install.RUNTIME_PACKAGE} dependency to the same exact version. No version takes whatever`);
1556
+ io.out("`latest` points at; the integrity is the one the registry states, never one penv");
1557
+ io.out("computed. Both files are shown before either moves, and they move together or not");
1558
+ io.out("at all. Extensions keep their own pins.");
1559
+ }
1560
+
1561
+ // src/pins.ts
1562
+ var import_core8 = require("@penvhq/core");
1421
1563
  var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1422
1564
  var DEV_PIN_VERSION = "0.0.0-dev";
1423
1565
  var BUNDLED_ENGINE_PIN = {
1424
- package: import_core7.ENGINE_PACKAGE,
1425
- version: "0.9.5",
1426
- integrity: "sha512-cacsvynscs6G0NThpGaK1BfKy3POQSoogI/vvQNZ5sTZaftzwg4mPtUpT+i2GSc3KbYAHQu4Kq9MpYLOvSvulA=="
1566
+ package: import_core8.ENGINE_PACKAGE,
1567
+ version: "0.11.0",
1568
+ integrity: "sha512-Y+WKyuPsX4U8/1j0e+VMUhj0LaGrp67YEoDAWyyinrgA+c8IM4H1blqje/YK4LsQwzQDQQDF9bhWIhfmDAWoxA=="
1427
1569
  };
1428
1570
  function assertReleasePin(pin) {
1429
1571
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1441,8 +1583,8 @@ function releaseEnginePin(pin, ranVersion) {
1441
1583
  // src/project.ts
1442
1584
  var import_node_fs6 = require("fs");
1443
1585
  var import_node_path6 = require("path");
1444
- var import_core8 = require("@penvhq/core");
1445
- var MANIFEST_SEGMENTS = import_core8.MANIFEST_PATH.split("/");
1586
+ var import_core9 = require("@penvhq/core");
1587
+ var MANIFEST_SEGMENTS = import_core9.MANIFEST_PATH.split("/");
1446
1588
  function findUp(cwd, matches) {
1447
1589
  let dir = (0, import_node_path6.resolve)(cwd);
1448
1590
  for (; ; ) {
@@ -1461,12 +1603,113 @@ function findProject(cwd) {
1461
1603
  return root === void 0 ? void 0 : { root, manifestFile: (0, import_node_path6.join)(root, ...MANIFEST_SEGMENTS) };
1462
1604
  }
1463
1605
  function findAdoptedRoot(cwd) {
1464
- return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core8.stateDir)(dir)));
1606
+ return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core9.stateDir)(dir)));
1607
+ }
1608
+
1609
+ // src/upgrade.ts
1610
+ var import_node_fs7 = require("fs");
1611
+ var import_install2 = require("@penvhq/cli/install");
1612
+ var import_core10 = require("@penvhq/core");
1613
+ function parseRequest2(argv) {
1614
+ let version;
1615
+ let yes = false;
1616
+ for (const token of argv) {
1617
+ if (token === YES_FLAG) {
1618
+ yes = true;
1619
+ continue;
1620
+ }
1621
+ if (token.startsWith("-")) {
1622
+ throw new UpgradeFlagError(token);
1623
+ }
1624
+ if (version !== void 0 || token === "") {
1625
+ throw new UpgradeSubjectError();
1626
+ }
1627
+ version = token;
1628
+ }
1629
+ return { version, yes };
1630
+ }
1631
+ function renderPinChange(engine, release) {
1632
+ return [
1633
+ import_core10.MANIFEST_PATH,
1634
+ ` - "integrity": "${engine.integrity}"`,
1635
+ ` - "version": "${engine.version}"`,
1636
+ ` + "integrity": "${release.integrity}"`,
1637
+ ` + "version": "${release.version}"`
1638
+ ];
1639
+ }
1640
+ function pinnedElsewhere(manifest, version) {
1641
+ return Object.entries(manifest.extensions).filter(([, entry]) => entry.version !== version).map(([name]) => name);
1642
+ }
1643
+ async function consent(options, release, plan, yes) {
1644
+ const { io, manifest } = options;
1645
+ io.out(`${import_core10.ENGINE_PACKAGE} ${manifest.engine.version} \u2192 ${release.version}`);
1646
+ io.out("");
1647
+ for (const line of renderPinChange(manifest.engine, release)) {
1648
+ io.out(line);
1649
+ }
1650
+ for (const line of (0, import_install2.renderInstallPlan)(plan)) {
1651
+ io.out(line);
1652
+ }
1653
+ if (yes) {
1654
+ return;
1655
+ }
1656
+ if (!await io.confirm(`Move this project to ${release.version}?`)) {
1657
+ throw new UpgradeDeclinedError(release.version);
1658
+ }
1659
+ }
1660
+ async function upgrade(options) {
1661
+ const { io, fetcher, home, root, manifest, manifestFile } = options;
1662
+ const request = parseRequest2(options.argv);
1663
+ if (options.noDownload === true) {
1664
+ throw new UpgradeNoDownloadError();
1665
+ }
1666
+ if ((options.ci === true || !io.interactive) && !(request.yes && request.version !== void 0)) {
1667
+ throw new UpgradeUnattendedError();
1668
+ }
1669
+ const release = await fetchRelease({
1670
+ name: import_core10.ENGINE_PACKAGE,
1671
+ ...request.version === void 0 ? {} : { version: request.version },
1672
+ retry: UPGRADE_COMMAND,
1673
+ fetcher
1674
+ });
1675
+ const plan = (0, import_install2.planInstall)(root, release.version);
1676
+ if (manifest.engine.version === release.version && plan.satisfied) {
1677
+ io.out(`\u2713 Already on ${import_core10.ENGINE_PACKAGE} ${release.version} \u2014 nothing to move`);
1678
+ return;
1679
+ }
1680
+ const manifestText = (0, import_core10.serializeManifest)({
1681
+ ...manifest,
1682
+ engine: { package: import_core10.ENGINE_PACKAGE, version: release.version, integrity: release.integrity }
1683
+ });
1684
+ await consent(options, release, plan, request.yes);
1685
+ await installPin({
1686
+ home,
1687
+ kind: "engines",
1688
+ pin: { name: import_core10.ENGINE_PACKAGE, version: release.version, integrity: release.integrity },
1689
+ fetcher
1690
+ });
1691
+ io.out(`\u2713 ${import_core10.ENGINE_PACKAGE} ${release.version} installed`);
1692
+ if (!plan.satisfied) {
1693
+ try {
1694
+ await (options.install ?? import_install2.installWithPackageManager)(plan);
1695
+ } catch {
1696
+ throw new UpgradeInstallFailedError(plan.command.join(" "));
1697
+ }
1698
+ io.out(`\u2713 package.json depends on ${import_install2.RUNTIME_PACKAGE} ${release.version}`);
1699
+ }
1700
+ (0, import_node_fs7.writeFileSync)(manifestFile, manifestText);
1701
+ io.out(`\u2713 ${import_core10.MANIFEST_PATH} pins ${import_core10.ENGINE_PACKAGE} ${release.version}`);
1702
+ const kept = pinnedElsewhere(manifest, release.version);
1703
+ if (kept.length > 0) {
1704
+ const subject = kept.length === 1 ? "keeps its own pin" : "keep their own pins";
1705
+ io.out(`\u2192 ${kept.join(", ")} ${subject} \u2014 \`penv add <package>@<version>\` moves one.`);
1706
+ }
1465
1707
  }
1466
1708
 
1467
1709
  // src/launcher.ts
1468
1710
  var INSTALL = "install";
1469
1711
  var ADD = "add";
1712
+ var UPGRADE = "upgrade";
1470
1713
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v"]);
1471
1714
  var HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
1472
1715
  var NO_DOWNLOAD = "--no-download";
@@ -1510,27 +1753,27 @@ function pinsOf(manifest) {
1510
1753
  ];
1511
1754
  }
1512
1755
  function reportable(error, home, argv) {
1513
- return error instanceof import_core9.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1756
+ return error instanceof import_core11.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1514
1757
  updateCommand: launcherUpdateCommand(home),
1515
1758
  invokedCommand: invokedCommand(argv)
1516
1759
  }) : error;
1517
1760
  }
1518
1761
  function readManifest(manifestFile, home, argv) {
1519
1762
  try {
1520
- return (0, import_core9.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1763
+ return (0, import_core11.parseManifest)((0, import_node_fs8.readFileSync)(manifestFile, "utf8"));
1521
1764
  } catch (error) {
1522
1765
  throw reportable(error, home, argv);
1523
1766
  }
1524
1767
  }
1525
1768
  function readManifestToRepair(manifestFile, home, argv) {
1526
1769
  try {
1527
- return readManifestForRepair((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1770
+ return readManifestForRepair((0, import_node_fs8.readFileSync)(manifestFile, "utf8"));
1528
1771
  } catch (error) {
1529
1772
  throw reportable(error, home, argv);
1530
1773
  }
1531
1774
  }
1532
1775
  function report(error, io) {
1533
- if (error instanceof import_core9.PenvError && error.remedy !== void 0) {
1776
+ if (error instanceof import_core11.PenvError && error.remedy !== void 0) {
1534
1777
  const suffix = `
1535
1778
  ${error.remedy}`;
1536
1779
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1552,7 +1795,21 @@ async function launch(options) {
1552
1795
  const { argv, cwd, env, io } = options;
1553
1796
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1554
1797
  const first = forwarded[0];
1555
- const home = penvHome(env);
1798
+ const home = (0, import_core11.penvHome)(env);
1799
+ if (first !== void 0 && forwarded.slice(1).some((token) => HELP_FLAGS.has(token))) {
1800
+ if (first === INSTALL) {
1801
+ printInstallHelp(io);
1802
+ return 0;
1803
+ }
1804
+ if (first === ADD) {
1805
+ printAddHelp(io);
1806
+ return 0;
1807
+ }
1808
+ if (first === UPGRADE) {
1809
+ printUpgradeHelp(io);
1810
+ return 0;
1811
+ }
1812
+ }
1556
1813
  const project = findProject(cwd);
1557
1814
  if (project === void 0) {
1558
1815
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1579,6 +1836,20 @@ async function launch(options) {
1579
1836
  io.out(`penv ${manifest.engine.version}`);
1580
1837
  return 0;
1581
1838
  }
1839
+ if (first === UPGRADE) {
1840
+ await upgrade({
1841
+ argv: forwarded.slice(1),
1842
+ root: project.root,
1843
+ manifestFile: project.manifestFile,
1844
+ manifest,
1845
+ home,
1846
+ io,
1847
+ fetcher: options.fetcher,
1848
+ noDownload,
1849
+ ci: isCi(env)
1850
+ });
1851
+ return 0;
1852
+ }
1582
1853
  const enginePin = enginePinOf(manifest);
1583
1854
  const engineDir = await ensure(options, "engines", enginePin, home, noDownload);
1584
1855
  for (const pin of extensionPinsOf(manifest)) {
@@ -1597,16 +1868,16 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1597
1868
  if (root === void 0) {
1598
1869
  return 0;
1599
1870
  }
1600
- const manifestFile = (0, import_node_path7.join)(root, ...import_core9.MANIFEST_PATH.split("/"));
1601
- if ((0, import_node_fs7.existsSync)(manifestFile)) {
1871
+ const manifestFile = (0, import_node_path7.join)(root, ...import_core11.MANIFEST_PATH.split("/"));
1872
+ if ((0, import_node_fs8.existsSync)(manifestFile)) {
1602
1873
  return 0;
1603
1874
  }
1604
1875
  const pin = releaseEnginePin(options.bundledPin, engine.version);
1605
- (0, import_node_fs7.writeFileSync)(
1876
+ (0, import_node_fs8.writeFileSync)(
1606
1877
  manifestFile,
1607
- (0, import_core9.serializeManifest)({ format: import_core9.MANIFEST_FORMAT, engine: pin, extensions: {} })
1878
+ (0, import_core11.serializeManifest)({ format: import_core11.MANIFEST_FORMAT, engine: pin, extensions: {} })
1608
1879
  );
1609
- options.io.out(`\u2713 ${import_core9.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1880
+ options.io.out(`\u2713 ${import_core11.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1610
1881
  await ensureAdoptedEngine(
1611
1882
  options,
1612
1883
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1685,13 +1956,21 @@ async function addExtension(options, project, home, argv, noDownload) {
1685
1956
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1686
1957
  return delegate(options, engine, onboard, home, options.cwd);
1687
1958
  }
1688
- function delegate(options, engine, forwarded, home, cwd) {
1689
- return options.spawn({
1959
+ function isRootHelp(forwarded) {
1960
+ const first = forwarded[0];
1961
+ return first === void 0 || HELP_FLAGS.has(first);
1962
+ }
1963
+ async function delegate(options, engine, forwarded, home, cwd) {
1964
+ const code = await options.spawn({
1690
1965
  command: process.execPath,
1691
1966
  args: [engine.entry, ...forwarded],
1692
1967
  cwd,
1693
- env: { ...options.env, [PENV_HOME_VAR]: home }
1968
+ env: { ...options.env, [import_core11.PENV_HOME_VAR]: home }
1694
1969
  });
1970
+ if (code === 0 && isRootHelp(forwarded)) {
1971
+ printLauncherCommands(options.io);
1972
+ }
1973
+ return code;
1695
1974
  }
1696
1975
  // Annotate the CommonJS export names for ESM import in node:
1697
1976
  0 && (module.exports = {
@@ -1716,6 +1995,8 @@ function delegate(options, engine, forwarded, home, cwd) {
1716
1995
  EngineEntryError,
1717
1996
  EnginePinMismatchError,
1718
1997
  EnginePinUnreleasedError,
1998
+ ExtensionNotImportableError,
1999
+ ExtensionUnloadableError,
1719
2000
  HOME_META_FILE,
1720
2001
  INSTALL_COMMAND,
1721
2002
  INTEGRITY_FILE,
@@ -1726,7 +2007,6 @@ function delegate(options, engine, forwarded, home, cwd) {
1726
2007
  NPM_UPDATE_COMMAND,
1727
2008
  NoProjectError,
1728
2009
  OfficialRegistryError,
1729
- PENV_HOME_VAR,
1730
2010
  PackageCorruptError,
1731
2011
  PackageMissingError,
1732
2012
  PackageTooYoungError,
@@ -1737,7 +2017,15 @@ function delegate(options, engine, forwarded, home, cwd) {
1737
2017
  TrustDeclinedError,
1738
2018
  TrustPublisherMissingError,
1739
2019
  TrustReasonMissingError,
2020
+ UPGRADE_COMMAND,
2021
+ UpgradeDeclinedError,
2022
+ UpgradeFlagError,
2023
+ UpgradeInstallFailedError,
2024
+ UpgradeNoDownloadError,
2025
+ UpgradeSubjectError,
2026
+ UpgradeUnattendedError,
1740
2027
  VersionUnknownError,
2028
+ YES_FLAG,
1741
2029
  add,
1742
2030
  assertReleasePin,
1743
2031
  bundledEngine,
@@ -1752,9 +2040,7 @@ function delegate(options, engine, forwarded, home, cwd) {
1752
2040
  integrityOf,
1753
2041
  launcherUpdateCommand,
1754
2042
  nodeSpawner,
1755
- packageDir,
1756
2043
  packumentUrl,
1757
- penvHome,
1758
2044
  readExtensionPackage,
1759
2045
  readProviderEntries,
1760
2046
  readTarball,
@@ -1763,6 +2049,7 @@ function delegate(options, engine, forwarded, home, cwd) {
1763
2049
  runLauncher,
1764
2050
  setProviderType,
1765
2051
  tarballUrl,
2052
+ upgrade,
1766
2053
  writeDeclaration
1767
2054
  });
1768
2055
  //# sourceMappingURL=index.cjs.map