@penvhq/launcher 0.9.4 → 0.10.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
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  AddFlagError: () => AddFlagError,
24
+ AddLocalFlagError: () => AddLocalFlagError,
25
+ AddLocalInCiError: () => AddLocalInCiError,
24
26
  AddNoDownloadError: () => AddNoDownloadError,
25
27
  AddNotInteractiveError: () => AddNotInteractiveError,
26
28
  AddPackageNameError: () => AddPackageNameError,
@@ -39,15 +41,18 @@ __export(src_exports, {
39
41
  EngineEntryError: () => EngineEntryError,
40
42
  EnginePinMismatchError: () => EnginePinMismatchError,
41
43
  EnginePinUnreleasedError: () => EnginePinUnreleasedError,
44
+ ExtensionNotImportableError: () => ExtensionNotImportableError,
45
+ ExtensionUnloadableError: () => ExtensionUnloadableError,
42
46
  HOME_META_FILE: () => HOME_META_FILE,
43
47
  INSTALL_COMMAND: () => INSTALL_COMMAND,
44
48
  INTEGRITY_FILE: () => INTEGRITY_FILE,
45
49
  InstallDeclinedError: () => InstallDeclinedError,
50
+ LOCAL_FLAG: () => LOCAL_FLAG,
51
+ LocalExtensionUnresolvedError: () => LocalExtensionUnresolvedError,
46
52
  MIN_PACKAGE_AGE_DAYS: () => MIN_PACKAGE_AGE_DAYS,
47
53
  NPM_UPDATE_COMMAND: () => NPM_UPDATE_COMMAND,
48
54
  NoProjectError: () => NoProjectError,
49
55
  OfficialRegistryError: () => OfficialRegistryError,
50
- PENV_HOME_VAR: () => PENV_HOME_VAR,
51
56
  PackageCorruptError: () => PackageCorruptError,
52
57
  PackageMissingError: () => PackageMissingError,
53
58
  PackageTooYoungError: () => PackageTooYoungError,
@@ -73,9 +78,7 @@ __export(src_exports, {
73
78
  integrityOf: () => integrityOf,
74
79
  launcherUpdateCommand: () => launcherUpdateCommand,
75
80
  nodeSpawner: () => nodeSpawner,
76
- packageDir: () => packageDir,
77
81
  packumentUrl: () => packumentUrl,
78
- penvHome: () => penvHome,
79
82
  readExtensionPackage: () => readExtensionPackage,
80
83
  readProviderEntries: () => readProviderEntries,
81
84
  readTarball: () => readTarball,
@@ -90,7 +93,9 @@ module.exports = __toCommonJS(src_exports);
90
93
 
91
94
  // src/add.ts
92
95
  var import_node_fs4 = require("fs");
96
+ var import_node_module = require("module");
93
97
  var import_node_path4 = require("path");
98
+ var import_node_url = require("url");
94
99
  var import_core5 = require("@penvhq/core");
95
100
 
96
101
  // src/config-edit.ts
@@ -269,6 +274,9 @@ function addCommand(name, version, extra) {
269
274
  const spec = version === void 0 ? name : `${name}@${version}`;
270
275
  return `penv add ${spec}${extra === void 0 ? "" : ` ${extra}`}`;
271
276
  }
277
+ function addCommandFor(name, local) {
278
+ return local ? `penv add ${LOCAL_FLAG} ${name}` : addCommand(name);
279
+ }
272
280
  var NoProjectError = class extends import_core.PenvError {
273
281
  name = "NoProjectError";
274
282
  constructor(cwd) {
@@ -365,7 +373,58 @@ var AddFlagError = class extends import_core.PenvError {
365
373
  super(
366
374
  "PENV_ADD_FLAG",
367
375
  `\`penv add\` does not understand \`${flag}\``,
368
- `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\` or \`--registry <url>\` \u2014 those are the two it takes.`
376
+ `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\`, \`--registry <url>\`, or \`${LOCAL_FLAG}\` \u2014 those are the three it takes.`
377
+ );
378
+ }
379
+ };
380
+ var LOCAL_FLAG = "--local";
381
+ var AddLocalFlagError = class extends import_core.PenvError {
382
+ name = "AddLocalFlagError";
383
+ constructor(subject) {
384
+ super(
385
+ "PENV_ADD_LOCAL_FLAG",
386
+ `\`penv add ${LOCAL_FLAG}\` cannot take ${subject}`,
387
+ `${LOCAL_FLAG} adds the package this project already builds, so there is no release to name and nothing to pin. Publish it and run \`penv add <package>\` to pin one.`
388
+ );
389
+ }
390
+ };
391
+ var ExtensionNotImportableError = class extends import_core.PenvError {
392
+ name = "ExtensionNotImportableError";
393
+ constructor(name, entry, local) {
394
+ super(
395
+ "PENV_EXTENSION_NOT_IMPORTABLE",
396
+ entry === void 0 ? `${name} declares no entry point penv can import` : `${name} resolves to ${entry}, which penv cannot import`,
397
+ `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.`
398
+ );
399
+ }
400
+ };
401
+ var ExtensionUnloadableError = class extends import_core.PenvError {
402
+ name = "ExtensionUnloadableError";
403
+ constructor(name, entry, detail, local) {
404
+ super(
405
+ "PENV_EXTENSION_UNLOADABLE",
406
+ `${name} threw while penv imported ${entry}: ${detail}`,
407
+ `Build the package and install its dependencies, then run \`${addCommandFor(name, local)}\` again. penv adds a provider it has loaded once, or none.`
408
+ );
409
+ }
410
+ };
411
+ var LocalExtensionUnresolvedError = class extends import_core.PenvError {
412
+ name = "LocalExtensionUnresolvedError";
413
+ constructor(name, root) {
414
+ super(
415
+ "PENV_LOCAL_EXTENSION_UNRESOLVED",
416
+ `${name} does not resolve from ${root}`,
417
+ `${LOCAL_FLAG} records that penv should read this package out of the project, so the project has to have it \u2014 add it as a dependency of the root (a workspace link is one), then run this again.`
418
+ );
419
+ }
420
+ };
421
+ var AddLocalInCiError = class extends import_core.PenvError {
422
+ name = "AddLocalInCiError";
423
+ constructor(name) {
424
+ super(
425
+ "PENV_ADD_LOCAL_IN_CI",
426
+ `Adding ${name} writes ${import_core.LOCAL_EXTENSIONS_PATH}, and CI does not decide what a project develops`,
427
+ `Run \`penv add ${LOCAL_FLAG} ${name}\` from a terminal and commit what it writes.`
369
428
  );
370
429
  }
371
430
  };
@@ -565,14 +624,20 @@ function readExtensionPackage(dir) {
565
624
  try {
566
625
  parsed = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "package.json"), "utf8"));
567
626
  } catch {
568
- return { types: void 0, onboard: void 0 };
627
+ return { name: void 0, version: void 0, types: void 0, onboard: void 0 };
569
628
  }
570
629
  const penv = field(parsed, "penv");
571
- return { types: text(field(penv, "types")), onboard: text(field(penv, "onboard")) };
630
+ return {
631
+ name: text(field(parsed, "name")),
632
+ version: text(field(parsed, "version")),
633
+ types: text(field(penv, "types")),
634
+ onboard: text(field(penv, "onboard"))
635
+ };
572
636
  }
573
637
  function header(subject) {
574
- const provenance = subject.attested ? "npm records a provenance attestation for it" : "npm records no provenance attestation for it";
575
- return `// Written by \`penv add ${subject.name}\`, and committed.
638
+ const local = subject.local === true;
639
+ const provenance = local ? "resolved from this project, not from a published release" : subject.attested ? "npm records a provenance attestation for it" : "npm records no provenance attestation for it";
640
+ return `// Written by \`penv add ${local ? "--local " : ""}${subject.name}\`, and committed.
576
641
  // ${subject.name} ${subject.version} \u2014 ${provenance}.
577
642
  //
578
643
  // Types only: this declares the shape of the provider's \`penv.config.ts\`
@@ -647,35 +712,14 @@ function writeDeclaration(options) {
647
712
  // src/store.ts
648
713
  var import_node_fs3 = require("fs");
649
714
  var import_node_path3 = require("path");
715
+ var import_core3 = require("@penvhq/core");
650
716
 
651
717
  // src/home.ts
652
718
  var import_node_fs2 = require("fs");
653
- var import_node_os = require("os");
654
719
  var import_node_path2 = require("path");
655
- var import_core3 = require("@penvhq/core");
656
- var PENV_HOME_VAR = "PENV_HOME";
657
720
  var HOME_META_FILE = "meta.json";
658
721
  var INTEGRITY_FILE = ".penv-integrity";
659
722
  var NPM_UPDATE_COMMAND = "npm install -g @penvhq/launcher";
660
- function penvHome(env) {
661
- const declared = env[PENV_HOME_VAR];
662
- if (declared !== void 0 && declared.trim() !== "") {
663
- return (0, import_node_path2.resolve)(declared);
664
- }
665
- return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".penv");
666
- }
667
- function packageDir(home, kind, name, version) {
668
- const bucket = (0, import_node_path2.resolve)(home, kind);
669
- const dir = (0, import_node_path2.resolve)(bucket, ...name.split("/"), version);
670
- if (!dir.startsWith(bucket + import_node_path2.sep)) {
671
- throw new import_core3.PenvError(
672
- "PENV_HOME_ESCAPE",
673
- `\`${name}\` at \`${version}\` resolves to ${dir}, which is outside ${bucket}`,
674
- "Name the package exactly as npm does, e.g. `@penvhq/provider-vault`."
675
- );
676
- }
677
- return dir;
678
- }
679
723
  function launcherUpdateCommand(home) {
680
724
  let meta;
681
725
  try {
@@ -777,7 +821,7 @@ function tarballUrl(pin) {
777
821
  return `${registry}/${pin.name}/-/${basename}-${pin.version}.tgz`;
778
822
  }
779
823
  function inspectInstall(home, kind, pin) {
780
- const dir = packageDir(home, kind, pin.name, pin.version);
824
+ const dir = (0, import_core3.packageDir)(home, kind, pin.name, pin.version);
781
825
  if (!(0, import_node_fs3.existsSync)(dir)) {
782
826
  return { dir, state: "absent" };
783
827
  }
@@ -791,7 +835,7 @@ function inspectInstall(home, kind, pin) {
791
835
  }
792
836
  async function installPin(options) {
793
837
  const { home, kind, pin, fetcher } = options;
794
- const dir = packageDir(home, kind, pin.name, pin.version);
838
+ const dir = (0, import_core3.packageDir)(home, kind, pin.name, pin.version);
795
839
  const url = tarballUrl(pin);
796
840
  let bytes;
797
841
  try {
@@ -965,8 +1009,13 @@ function parseRequest(argv) {
965
1009
  let spec;
966
1010
  let registry;
967
1011
  let trustYoung = false;
1012
+ let local = false;
968
1013
  for (let index = 0; index < argv.length; index += 1) {
969
1014
  const token = argv[index] ?? "";
1015
+ if (token === LOCAL_FLAG) {
1016
+ local = true;
1017
+ continue;
1018
+ }
970
1019
  if (token === TRUST_YOUNG_FLAG) {
971
1020
  trustYoung = true;
972
1021
  continue;
@@ -995,7 +1044,18 @@ function parseRequest(argv) {
995
1044
  if (!PACKAGE_NAME.test(name) || version === "") {
996
1045
  throw new AddPackageNameError(spec);
997
1046
  }
998
- return { name, version, registry, trustYoung };
1047
+ if (local) {
1048
+ if (version !== void 0) {
1049
+ throw new AddLocalFlagError("a version");
1050
+ }
1051
+ if (registry !== void 0) {
1052
+ throw new AddLocalFlagError(`\`${REGISTRY_FLAG}\``);
1053
+ }
1054
+ if (trustYoung) {
1055
+ throw new AddLocalFlagError(`\`${TRUST_YOUNG_FLAG}\``);
1056
+ }
1057
+ }
1058
+ return { name, version, registry, trustYoung, local };
999
1059
  }
1000
1060
  function tierOf(request) {
1001
1061
  if (request.name.startsWith(import_core5.OFFICIAL_SCOPE)) {
@@ -1128,9 +1188,90 @@ async function offerOnboarding(io, name, onboard) {
1128
1188
  io.out(`Run \`${command}\` to finish setting ${name} up.`);
1129
1189
  return void 0;
1130
1190
  }
1191
+ function resolvePackageDir(name, root) {
1192
+ const require2 = (0, import_node_module.createRequire)((0, import_node_path4.resolve)(root, "noop.js"));
1193
+ try {
1194
+ return (0, import_node_path4.dirname)(require2.resolve(`${name}/package.json`));
1195
+ } catch {
1196
+ let dir;
1197
+ try {
1198
+ dir = (0, import_node_path4.dirname)(require2.resolve(name));
1199
+ } catch {
1200
+ return void 0;
1201
+ }
1202
+ for (let current = dir, parent = (0, import_node_path4.dirname)(dir); current !== parent; parent = (0, import_node_path4.dirname)(current)) {
1203
+ if (readExtensionPackage(current).name === name) {
1204
+ return current;
1205
+ }
1206
+ current = parent;
1207
+ }
1208
+ return void 0;
1209
+ }
1210
+ }
1211
+ function resolveProjectEntry(name, root) {
1212
+ let file;
1213
+ try {
1214
+ file = (0, import_node_module.createRequire)((0, import_node_path4.resolve)(root, "noop.js")).resolve(name);
1215
+ } catch {
1216
+ return void 0;
1217
+ }
1218
+ return { file, importable: (0, import_core5.isImportableEntry)(file) };
1219
+ }
1220
+ async function assertLoadable(name, entry, local) {
1221
+ if (entry === void 0 || !entry.importable) {
1222
+ throw new ExtensionNotImportableError(name, entry?.file, local);
1223
+ }
1224
+ try {
1225
+ await import((0, import_node_url.pathToFileURL)(entry.file).href);
1226
+ } catch (cause) {
1227
+ const detail = cause instanceof Error ? cause.message : String(cause);
1228
+ throw new ExtensionUnloadableError(name, entry.file, detail, local);
1229
+ }
1230
+ }
1231
+ function recordLocalExtension(root, name) {
1232
+ const file = (0, import_core5.localExtensionsFile)(root);
1233
+ let current;
1234
+ try {
1235
+ current = (0, import_node_fs4.readFileSync)(file, "utf8");
1236
+ } catch {
1237
+ current = "[]";
1238
+ }
1239
+ (0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(file), { recursive: true });
1240
+ (0, import_node_fs4.writeFileSync)(file, (0, import_core5.serializeLocalExtensions)([...(0, import_core5.parseLocalExtensions)(current), name]));
1241
+ }
1242
+ async function addLocal(options, request) {
1243
+ const { io, root } = options;
1244
+ if (options.ci === true) {
1245
+ throw new AddLocalInCiError(request.name);
1246
+ }
1247
+ const dir = resolvePackageDir(request.name, root);
1248
+ if (dir === void 0) {
1249
+ throw new LocalExtensionUnresolvedError(request.name, root);
1250
+ }
1251
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1252
+ const installed = readExtensionPackage(dir);
1253
+ const declaration = writeDeclaration({
1254
+ root,
1255
+ dir,
1256
+ name: request.name,
1257
+ version: installed.version ?? "unpublished",
1258
+ attested: false,
1259
+ local: true,
1260
+ types: installed.types
1261
+ });
1262
+ recordLocalExtension(root, request.name);
1263
+ io.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1264
+ io.out(`\u2713 ${import_core5.LOCAL_EXTENSIONS_PATH} records it`);
1265
+ io.out(`\u2713 ${declaration} declares its config type`);
1266
+ await offerConfigEdit(options, request.name);
1267
+ return { onboard: await offerOnboarding(io, request.name, installed.onboard) };
1268
+ }
1131
1269
  async function add(options) {
1132
1270
  const { io, fetcher, home, root, manifestFile } = options;
1133
1271
  const request = parseRequest(options.argv);
1272
+ if (request.local) {
1273
+ return addLocal(options, request);
1274
+ }
1134
1275
  const tier = tierOf(request);
1135
1276
  const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
1136
1277
  if (options.noDownload === true) {
@@ -1164,6 +1305,7 @@ async function add(options) {
1164
1305
  },
1165
1306
  fetcher
1166
1307
  });
1308
+ await assertLoadable(release.name, (0, import_core5.packageEntry)(dir), false);
1167
1309
  const installed = readExtensionPackage(dir);
1168
1310
  const declaration = writeDeclaration({
1169
1311
  root,
@@ -1184,10 +1326,10 @@ async function add(options) {
1184
1326
 
1185
1327
  // src/delegate.ts
1186
1328
  var import_node_child_process = require("child_process");
1187
- var import_node_os2 = require("os");
1329
+ var import_node_os = require("os");
1188
1330
  var FORWARDED = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
1189
1331
  function signalNumber(signal) {
1190
- const signals = import_node_os2.constants.signals;
1332
+ const signals = import_node_os.constants.signals;
1191
1333
  return signals[signal] ?? 0;
1192
1334
  }
1193
1335
  function nodeSpawner() {
@@ -1227,7 +1369,7 @@ function nodeSpawner() {
1227
1369
 
1228
1370
  // src/engine.ts
1229
1371
  var import_node_fs5 = require("fs");
1230
- var import_node_module = require("module");
1372
+ var import_node_module2 = require("module");
1231
1373
  var import_node_path5 = require("path");
1232
1374
  var import_core6 = require("@penvhq/core");
1233
1375
  var import_meta = {};
@@ -1264,7 +1406,7 @@ function engineAt(dir, name, version) {
1264
1406
  return { name, version, dir, entry };
1265
1407
  }
1266
1408
  function bundledEngine() {
1267
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
1409
+ const require2 = (0, import_node_module2.createRequire)(import_meta.url);
1268
1410
  let manifestFile;
1269
1411
  try {
1270
1412
  manifestFile = require2.resolve(`${import_core6.ENGINE_PACKAGE}/package.json`);
@@ -1296,16 +1438,48 @@ function httpFetcher() {
1296
1438
  // src/launcher.ts
1297
1439
  var import_node_fs7 = require("fs");
1298
1440
  var import_node_path7 = require("path");
1299
- var import_core9 = require("@penvhq/core");
1441
+ var import_core10 = require("@penvhq/core");
1300
1442
 
1301
- // src/pins.ts
1443
+ // src/help.ts
1302
1444
  var import_core7 = require("@penvhq/core");
1445
+ function printLauncherCommands(io) {
1446
+ io.out("");
1447
+ io.out("LAUNCHER COMMANDS (penv itself, whatever engine a project pins)");
1448
+ io.out("");
1449
+ io.out(` install Installs everything ${import_core7.MANIFEST_PATH} pins`);
1450
+ io.out(" add <package> Pins a provider extension, and commits the decision");
1451
+ io.out(` add ${LOCAL_FLAG} <package> Adds the one this repository builds \u2014 nothing is pinned`);
1452
+ io.out("");
1453
+ }
1454
+ function printInstallHelp(io) {
1455
+ io.out(`${INSTALL_COMMAND}`);
1456
+ io.out("");
1457
+ io.out(`Downloads, verifies and installs every version ${import_core7.MANIFEST_PATH} pins \u2014 the engine and`);
1458
+ io.out("every extension \u2014 into $PENV_HOME. The one penv command CI and production run that");
1459
+ io.out("may reach the registry.");
1460
+ }
1461
+ function printAddHelp(io) {
1462
+ io.out("penv add <package>[@<version>]");
1463
+ io.out("");
1464
+ io.out(` ${LOCAL_FLAG} Add the package this repository builds, resolved from it`);
1465
+ io.out(" --registry <url> Take the release from a private registry instead of npmjs");
1466
+ io.out(
1467
+ ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1468
+ );
1469
+ io.out("");
1470
+ io.out(`Pins the release in ${import_core7.MANIFEST_PATH} and writes its type declaration to`);
1471
+ io.out(`${import_core7.EXTENSIONS_PATH}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1472
+ io.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1473
+ }
1474
+
1475
+ // src/pins.ts
1476
+ var import_core8 = require("@penvhq/core");
1303
1477
  var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1304
1478
  var DEV_PIN_VERSION = "0.0.0-dev";
1305
1479
  var BUNDLED_ENGINE_PIN = {
1306
- package: import_core7.ENGINE_PACKAGE,
1307
- version: "0.9.4",
1308
- integrity: "sha512-XnG+KlEAZyUaaKUfANPiyhY1dfYZ9KgnkF2R+5A0OskRgfYPSpBRN0i1UPtq8RnwMJaPxfSNDNdqfiiESz6kRw=="
1480
+ package: import_core8.ENGINE_PACKAGE,
1481
+ version: "0.10.0",
1482
+ integrity: "sha512-b+U0c3xWbk1ILtMfPjJW8x5XBxSLAaw/IAOLMBmGvT2uuYv41JPED8PrenEj4s3ZJY5BxpWU9mQcJHEBouaw/Q=="
1309
1483
  };
1310
1484
  function assertReleasePin(pin) {
1311
1485
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1323,8 +1497,8 @@ function releaseEnginePin(pin, ranVersion) {
1323
1497
  // src/project.ts
1324
1498
  var import_node_fs6 = require("fs");
1325
1499
  var import_node_path6 = require("path");
1326
- var import_core8 = require("@penvhq/core");
1327
- var MANIFEST_SEGMENTS = import_core8.MANIFEST_PATH.split("/");
1500
+ var import_core9 = require("@penvhq/core");
1501
+ var MANIFEST_SEGMENTS = import_core9.MANIFEST_PATH.split("/");
1328
1502
  function findUp(cwd, matches) {
1329
1503
  let dir = (0, import_node_path6.resolve)(cwd);
1330
1504
  for (; ; ) {
@@ -1343,7 +1517,7 @@ function findProject(cwd) {
1343
1517
  return root === void 0 ? void 0 : { root, manifestFile: (0, import_node_path6.join)(root, ...MANIFEST_SEGMENTS) };
1344
1518
  }
1345
1519
  function findAdoptedRoot(cwd) {
1346
- return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core8.stateDir)(dir)));
1520
+ return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core9.stateDir)(dir)));
1347
1521
  }
1348
1522
 
1349
1523
  // src/launcher.ts
@@ -1392,14 +1566,14 @@ function pinsOf(manifest) {
1392
1566
  ];
1393
1567
  }
1394
1568
  function reportable(error, home, argv) {
1395
- return error instanceof import_core9.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1569
+ return error instanceof import_core10.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1396
1570
  updateCommand: launcherUpdateCommand(home),
1397
1571
  invokedCommand: invokedCommand(argv)
1398
1572
  }) : error;
1399
1573
  }
1400
1574
  function readManifest(manifestFile, home, argv) {
1401
1575
  try {
1402
- return (0, import_core9.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1576
+ return (0, import_core10.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1403
1577
  } catch (error) {
1404
1578
  throw reportable(error, home, argv);
1405
1579
  }
@@ -1412,7 +1586,7 @@ function readManifestToRepair(manifestFile, home, argv) {
1412
1586
  }
1413
1587
  }
1414
1588
  function report(error, io) {
1415
- if (error instanceof import_core9.PenvError && error.remedy !== void 0) {
1589
+ if (error instanceof import_core10.PenvError && error.remedy !== void 0) {
1416
1590
  const suffix = `
1417
1591
  ${error.remedy}`;
1418
1592
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1434,7 +1608,17 @@ async function launch(options) {
1434
1608
  const { argv, cwd, env, io } = options;
1435
1609
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1436
1610
  const first = forwarded[0];
1437
- const home = penvHome(env);
1611
+ const home = (0, import_core10.penvHome)(env);
1612
+ if (first !== void 0 && forwarded.slice(1).some((token) => HELP_FLAGS.has(token))) {
1613
+ if (first === INSTALL) {
1614
+ printInstallHelp(io);
1615
+ return 0;
1616
+ }
1617
+ if (first === ADD) {
1618
+ printAddHelp(io);
1619
+ return 0;
1620
+ }
1621
+ }
1438
1622
  const project = findProject(cwd);
1439
1623
  if (project === void 0) {
1440
1624
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1479,16 +1663,16 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1479
1663
  if (root === void 0) {
1480
1664
  return 0;
1481
1665
  }
1482
- const manifestFile = (0, import_node_path7.join)(root, ...import_core9.MANIFEST_PATH.split("/"));
1666
+ const manifestFile = (0, import_node_path7.join)(root, ...import_core10.MANIFEST_PATH.split("/"));
1483
1667
  if ((0, import_node_fs7.existsSync)(manifestFile)) {
1484
1668
  return 0;
1485
1669
  }
1486
1670
  const pin = releaseEnginePin(options.bundledPin, engine.version);
1487
1671
  (0, import_node_fs7.writeFileSync)(
1488
1672
  manifestFile,
1489
- (0, import_core9.serializeManifest)({ format: import_core9.MANIFEST_FORMAT, engine: pin, extensions: {} })
1673
+ (0, import_core10.serializeManifest)({ format: import_core10.MANIFEST_FORMAT, engine: pin, extensions: {} })
1490
1674
  );
1491
- options.io.out(`\u2713 ${import_core9.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1675
+ options.io.out(`\u2713 ${import_core10.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1492
1676
  await ensureAdoptedEngine(
1493
1677
  options,
1494
1678
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1567,17 +1751,27 @@ async function addExtension(options, project, home, argv, noDownload) {
1567
1751
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1568
1752
  return delegate(options, engine, onboard, home, options.cwd);
1569
1753
  }
1570
- function delegate(options, engine, forwarded, home, cwd) {
1571
- return options.spawn({
1754
+ function isRootHelp(forwarded) {
1755
+ const first = forwarded[0];
1756
+ return first === void 0 || HELP_FLAGS.has(first);
1757
+ }
1758
+ async function delegate(options, engine, forwarded, home, cwd) {
1759
+ const code = await options.spawn({
1572
1760
  command: process.execPath,
1573
1761
  args: [engine.entry, ...forwarded],
1574
1762
  cwd,
1575
- env: { ...options.env, [PENV_HOME_VAR]: home }
1763
+ env: { ...options.env, [import_core10.PENV_HOME_VAR]: home }
1576
1764
  });
1765
+ if (code === 0 && isRootHelp(forwarded)) {
1766
+ printLauncherCommands(options.io);
1767
+ }
1768
+ return code;
1577
1769
  }
1578
1770
  // Annotate the CommonJS export names for ESM import in node:
1579
1771
  0 && (module.exports = {
1580
1772
  AddFlagError,
1773
+ AddLocalFlagError,
1774
+ AddLocalInCiError,
1581
1775
  AddNoDownloadError,
1582
1776
  AddNotInteractiveError,
1583
1777
  AddPackageNameError,
@@ -1596,15 +1790,18 @@ function delegate(options, engine, forwarded, home, cwd) {
1596
1790
  EngineEntryError,
1597
1791
  EnginePinMismatchError,
1598
1792
  EnginePinUnreleasedError,
1793
+ ExtensionNotImportableError,
1794
+ ExtensionUnloadableError,
1599
1795
  HOME_META_FILE,
1600
1796
  INSTALL_COMMAND,
1601
1797
  INTEGRITY_FILE,
1602
1798
  InstallDeclinedError,
1799
+ LOCAL_FLAG,
1800
+ LocalExtensionUnresolvedError,
1603
1801
  MIN_PACKAGE_AGE_DAYS,
1604
1802
  NPM_UPDATE_COMMAND,
1605
1803
  NoProjectError,
1606
1804
  OfficialRegistryError,
1607
- PENV_HOME_VAR,
1608
1805
  PackageCorruptError,
1609
1806
  PackageMissingError,
1610
1807
  PackageTooYoungError,
@@ -1630,9 +1827,7 @@ function delegate(options, engine, forwarded, home, cwd) {
1630
1827
  integrityOf,
1631
1828
  launcherUpdateCommand,
1632
1829
  nodeSpawner,
1633
- packageDir,
1634
1830
  packumentUrl,
1635
- penvHome,
1636
1831
  readExtensionPackage,
1637
1832
  readProviderEntries,
1638
1833
  readTarball,