@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/bin.cjs CHANGED
@@ -59,6 +59,9 @@ function addCommand(name, version, extra) {
59
59
  const spec = version === void 0 ? name : `${name}@${version}`;
60
60
  return `penv add ${spec}${extra === void 0 ? "" : ` ${extra}`}`;
61
61
  }
62
+ function addCommandFor(name, local) {
63
+ return local ? `penv add ${LOCAL_FLAG} ${name}` : addCommand(name);
64
+ }
62
65
  var NoProjectError = class extends import_core.PenvError {
63
66
  name = "NoProjectError";
64
67
  constructor(cwd) {
@@ -155,7 +158,58 @@ var AddFlagError = class extends import_core.PenvError {
155
158
  super(
156
159
  "PENV_ADD_FLAG",
157
160
  `\`penv add\` does not understand \`${flag}\``,
158
- `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\` or \`--registry <url>\` \u2014 those are the two it takes.`
161
+ `Run \`penv add <package>\` with \`${TRUST_YOUNG_FLAG}\`, \`--registry <url>\`, or \`${LOCAL_FLAG}\` \u2014 those are the three it takes.`
162
+ );
163
+ }
164
+ };
165
+ var LOCAL_FLAG = "--local";
166
+ var AddLocalFlagError = class extends import_core.PenvError {
167
+ name = "AddLocalFlagError";
168
+ constructor(subject) {
169
+ super(
170
+ "PENV_ADD_LOCAL_FLAG",
171
+ `\`penv add ${LOCAL_FLAG}\` cannot take ${subject}`,
172
+ `${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.`
173
+ );
174
+ }
175
+ };
176
+ var ExtensionNotImportableError = class extends import_core.PenvError {
177
+ name = "ExtensionNotImportableError";
178
+ constructor(name, entry, local) {
179
+ super(
180
+ "PENV_EXTENSION_NOT_IMPORTABLE",
181
+ entry === void 0 ? `${name} declares no entry point penv can import` : `${name} resolves to ${entry}, which penv cannot import`,
182
+ `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.`
183
+ );
184
+ }
185
+ };
186
+ var ExtensionUnloadableError = class extends import_core.PenvError {
187
+ name = "ExtensionUnloadableError";
188
+ constructor(name, entry, detail, local) {
189
+ super(
190
+ "PENV_EXTENSION_UNLOADABLE",
191
+ `${name} threw while penv imported ${entry}: ${detail}`,
192
+ `Build the package and install its dependencies, then run \`${addCommandFor(name, local)}\` again. penv adds a provider it has loaded once, or none.`
193
+ );
194
+ }
195
+ };
196
+ var LocalExtensionUnresolvedError = class extends import_core.PenvError {
197
+ name = "LocalExtensionUnresolvedError";
198
+ constructor(name, root) {
199
+ super(
200
+ "PENV_LOCAL_EXTENSION_UNRESOLVED",
201
+ `${name} does not resolve from ${root}`,
202
+ `${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.`
203
+ );
204
+ }
205
+ };
206
+ var AddLocalInCiError = class extends import_core.PenvError {
207
+ name = "AddLocalInCiError";
208
+ constructor(name) {
209
+ super(
210
+ "PENV_ADD_LOCAL_IN_CI",
211
+ `Adding ${name} writes ${import_core.LOCAL_EXTENSIONS_PATH}, and CI does not decide what a project develops`,
212
+ `Run \`penv add ${LOCAL_FLAG} ${name}\` from a terminal and commit what it writes.`
159
213
  );
160
214
  }
161
215
  };
@@ -408,11 +462,13 @@ function httpFetcher() {
408
462
  // src/launcher.ts
409
463
  var import_node_fs7 = require("fs");
410
464
  var import_node_path7 = require("path");
411
- var import_core9 = require("@penvhq/core");
465
+ var import_core10 = require("@penvhq/core");
412
466
 
413
467
  // src/add.ts
414
468
  var import_node_fs5 = require("fs");
469
+ var import_node_module2 = require("module");
415
470
  var import_node_path5 = require("path");
471
+ var import_node_url = require("url");
416
472
  var import_core6 = require("@penvhq/core");
417
473
 
418
474
  // src/config-edit.ts
@@ -594,14 +650,20 @@ function readExtensionPackage(dir) {
594
650
  try {
595
651
  parsed = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(dir, "package.json"), "utf8"));
596
652
  } catch {
597
- return { types: void 0, onboard: void 0 };
653
+ return { name: void 0, version: void 0, types: void 0, onboard: void 0 };
598
654
  }
599
655
  const penv = field(parsed, "penv");
600
- return { types: text(field(penv, "types")), onboard: text(field(penv, "onboard")) };
656
+ return {
657
+ name: text(field(parsed, "name")),
658
+ version: text(field(parsed, "version")),
659
+ types: text(field(penv, "types")),
660
+ onboard: text(field(penv, "onboard"))
661
+ };
601
662
  }
602
663
  function header(subject) {
603
- const provenance = subject.attested ? "npm records a provenance attestation for it" : "npm records no provenance attestation for it";
604
- return `// Written by \`penv add ${subject.name}\`, and committed.
664
+ const local = subject.local === true;
665
+ 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";
666
+ return `// Written by \`penv add ${local ? "--local " : ""}${subject.name}\`, and committed.
605
667
  // ${subject.name} ${subject.version} \u2014 ${provenance}.
606
668
  //
607
669
  // Types only: this declares the shape of the provider's \`penv.config.ts\`
@@ -676,35 +738,14 @@ function writeDeclaration(options) {
676
738
  // src/store.ts
677
739
  var import_node_fs4 = require("fs");
678
740
  var import_node_path4 = require("path");
741
+ var import_core4 = require("@penvhq/core");
679
742
 
680
743
  // src/home.ts
681
744
  var import_node_fs3 = require("fs");
682
- var import_node_os2 = require("os");
683
745
  var import_node_path3 = require("path");
684
- var import_core4 = require("@penvhq/core");
685
- var PENV_HOME_VAR = "PENV_HOME";
686
746
  var HOME_META_FILE = "meta.json";
687
747
  var INTEGRITY_FILE = ".penv-integrity";
688
748
  var NPM_UPDATE_COMMAND = "npm install -g @penvhq/launcher";
689
- function penvHome(env) {
690
- const declared = env[PENV_HOME_VAR];
691
- if (declared !== void 0 && declared.trim() !== "") {
692
- return (0, import_node_path3.resolve)(declared);
693
- }
694
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".penv");
695
- }
696
- function packageDir(home, kind, name, version) {
697
- const bucket = (0, import_node_path3.resolve)(home, kind);
698
- const dir = (0, import_node_path3.resolve)(bucket, ...name.split("/"), version);
699
- if (!dir.startsWith(bucket + import_node_path3.sep)) {
700
- throw new import_core4.PenvError(
701
- "PENV_HOME_ESCAPE",
702
- `\`${name}\` at \`${version}\` resolves to ${dir}, which is outside ${bucket}`,
703
- "Name the package exactly as npm does, e.g. `@penvhq/provider-vault`."
704
- );
705
- }
706
- return dir;
707
- }
708
749
  function launcherUpdateCommand(home) {
709
750
  let meta;
710
751
  try {
@@ -806,7 +847,7 @@ function tarballUrl(pin) {
806
847
  return `${registry}/${pin.name}/-/${basename}-${pin.version}.tgz`;
807
848
  }
808
849
  function inspectInstall(home, kind, pin) {
809
- const dir = packageDir(home, kind, pin.name, pin.version);
850
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
810
851
  if (!(0, import_node_fs4.existsSync)(dir)) {
811
852
  return { dir, state: "absent" };
812
853
  }
@@ -820,7 +861,7 @@ function inspectInstall(home, kind, pin) {
820
861
  }
821
862
  async function installPin(options) {
822
863
  const { home, kind, pin, fetcher } = options;
823
- const dir = packageDir(home, kind, pin.name, pin.version);
864
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
824
865
  const url = tarballUrl(pin);
825
866
  let bytes;
826
867
  try {
@@ -994,8 +1035,13 @@ function parseRequest(argv) {
994
1035
  let spec;
995
1036
  let registry;
996
1037
  let trustYoung = false;
1038
+ let local = false;
997
1039
  for (let index = 0; index < argv.length; index += 1) {
998
1040
  const token = argv[index] ?? "";
1041
+ if (token === LOCAL_FLAG) {
1042
+ local = true;
1043
+ continue;
1044
+ }
999
1045
  if (token === TRUST_YOUNG_FLAG) {
1000
1046
  trustYoung = true;
1001
1047
  continue;
@@ -1024,7 +1070,18 @@ function parseRequest(argv) {
1024
1070
  if (!PACKAGE_NAME.test(name) || version === "") {
1025
1071
  throw new AddPackageNameError(spec);
1026
1072
  }
1027
- return { name, version, registry, trustYoung };
1073
+ if (local) {
1074
+ if (version !== void 0) {
1075
+ throw new AddLocalFlagError("a version");
1076
+ }
1077
+ if (registry !== void 0) {
1078
+ throw new AddLocalFlagError(`\`${REGISTRY_FLAG}\``);
1079
+ }
1080
+ if (trustYoung) {
1081
+ throw new AddLocalFlagError(`\`${TRUST_YOUNG_FLAG}\``);
1082
+ }
1083
+ }
1084
+ return { name, version, registry, trustYoung, local };
1028
1085
  }
1029
1086
  function tierOf(request) {
1030
1087
  if (request.name.startsWith(import_core6.OFFICIAL_SCOPE)) {
@@ -1157,9 +1214,90 @@ async function offerOnboarding(io2, name, onboard) {
1157
1214
  io2.out(`Run \`${command}\` to finish setting ${name} up.`);
1158
1215
  return void 0;
1159
1216
  }
1217
+ function resolvePackageDir(name, root) {
1218
+ const require2 = (0, import_node_module2.createRequire)((0, import_node_path5.resolve)(root, "noop.js"));
1219
+ try {
1220
+ return (0, import_node_path5.dirname)(require2.resolve(`${name}/package.json`));
1221
+ } catch {
1222
+ let dir;
1223
+ try {
1224
+ dir = (0, import_node_path5.dirname)(require2.resolve(name));
1225
+ } catch {
1226
+ return void 0;
1227
+ }
1228
+ for (let current = dir, parent = (0, import_node_path5.dirname)(dir); current !== parent; parent = (0, import_node_path5.dirname)(current)) {
1229
+ if (readExtensionPackage(current).name === name) {
1230
+ return current;
1231
+ }
1232
+ current = parent;
1233
+ }
1234
+ return void 0;
1235
+ }
1236
+ }
1237
+ function resolveProjectEntry(name, root) {
1238
+ let file;
1239
+ try {
1240
+ file = (0, import_node_module2.createRequire)((0, import_node_path5.resolve)(root, "noop.js")).resolve(name);
1241
+ } catch {
1242
+ return void 0;
1243
+ }
1244
+ return { file, importable: (0, import_core6.isImportableEntry)(file) };
1245
+ }
1246
+ async function assertLoadable(name, entry, local) {
1247
+ if (entry === void 0 || !entry.importable) {
1248
+ throw new ExtensionNotImportableError(name, entry?.file, local);
1249
+ }
1250
+ try {
1251
+ await import((0, import_node_url.pathToFileURL)(entry.file).href);
1252
+ } catch (cause) {
1253
+ const detail = cause instanceof Error ? cause.message : String(cause);
1254
+ throw new ExtensionUnloadableError(name, entry.file, detail, local);
1255
+ }
1256
+ }
1257
+ function recordLocalExtension(root, name) {
1258
+ const file = (0, import_core6.localExtensionsFile)(root);
1259
+ let current;
1260
+ try {
1261
+ current = (0, import_node_fs5.readFileSync)(file, "utf8");
1262
+ } catch {
1263
+ current = "[]";
1264
+ }
1265
+ (0, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(file), { recursive: true });
1266
+ (0, import_node_fs5.writeFileSync)(file, (0, import_core6.serializeLocalExtensions)([...(0, import_core6.parseLocalExtensions)(current), name]));
1267
+ }
1268
+ async function addLocal(options, request) {
1269
+ const { io: io2, root } = options;
1270
+ if (options.ci === true) {
1271
+ throw new AddLocalInCiError(request.name);
1272
+ }
1273
+ const dir = resolvePackageDir(request.name, root);
1274
+ if (dir === void 0) {
1275
+ throw new LocalExtensionUnresolvedError(request.name, root);
1276
+ }
1277
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1278
+ const installed = readExtensionPackage(dir);
1279
+ const declaration = writeDeclaration({
1280
+ root,
1281
+ dir,
1282
+ name: request.name,
1283
+ version: installed.version ?? "unpublished",
1284
+ attested: false,
1285
+ local: true,
1286
+ types: installed.types
1287
+ });
1288
+ recordLocalExtension(root, request.name);
1289
+ io2.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1290
+ io2.out(`\u2713 ${import_core6.LOCAL_EXTENSIONS_PATH} records it`);
1291
+ io2.out(`\u2713 ${declaration} declares its config type`);
1292
+ await offerConfigEdit(options, request.name);
1293
+ return { onboard: await offerOnboarding(io2, request.name, installed.onboard) };
1294
+ }
1160
1295
  async function add(options) {
1161
1296
  const { io: io2, fetcher, home, root, manifestFile } = options;
1162
1297
  const request = parseRequest(options.argv);
1298
+ if (request.local) {
1299
+ return addLocal(options, request);
1300
+ }
1163
1301
  const tier = tierOf(request);
1164
1302
  const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
1165
1303
  if (options.noDownload === true) {
@@ -1193,6 +1331,7 @@ async function add(options) {
1193
1331
  },
1194
1332
  fetcher
1195
1333
  });
1334
+ await assertLoadable(release.name, (0, import_core6.packageEntry)(dir), false);
1196
1335
  const installed = readExtensionPackage(dir);
1197
1336
  const declaration = writeDeclaration({
1198
1337
  root,
@@ -1211,14 +1350,46 @@ async function add(options) {
1211
1350
  return { onboard: await offerOnboarding(io2, release.name, installed.onboard) };
1212
1351
  }
1213
1352
 
1214
- // src/pins.ts
1353
+ // src/help.ts
1215
1354
  var import_core7 = require("@penvhq/core");
1355
+ function printLauncherCommands(io2) {
1356
+ io2.out("");
1357
+ io2.out("LAUNCHER COMMANDS (penv itself, whatever engine a project pins)");
1358
+ io2.out("");
1359
+ io2.out(` install Installs everything ${import_core7.MANIFEST_PATH} pins`);
1360
+ io2.out(" add <package> Pins a provider extension, and commits the decision");
1361
+ io2.out(` add ${LOCAL_FLAG} <package> Adds the one this repository builds \u2014 nothing is pinned`);
1362
+ io2.out("");
1363
+ }
1364
+ function printInstallHelp(io2) {
1365
+ io2.out(`${INSTALL_COMMAND}`);
1366
+ io2.out("");
1367
+ io2.out(`Downloads, verifies and installs every version ${import_core7.MANIFEST_PATH} pins \u2014 the engine and`);
1368
+ io2.out("every extension \u2014 into $PENV_HOME. The one penv command CI and production run that");
1369
+ io2.out("may reach the registry.");
1370
+ }
1371
+ function printAddHelp(io2) {
1372
+ io2.out("penv add <package>[@<version>]");
1373
+ io2.out("");
1374
+ io2.out(` ${LOCAL_FLAG} Add the package this repository builds, resolved from it`);
1375
+ io2.out(" --registry <url> Take the release from a private registry instead of npmjs");
1376
+ io2.out(
1377
+ ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1378
+ );
1379
+ io2.out("");
1380
+ io2.out(`Pins the release in ${import_core7.MANIFEST_PATH} and writes its type declaration to`);
1381
+ io2.out(`${import_core7.EXTENSIONS_PATH}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1382
+ io2.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1383
+ }
1384
+
1385
+ // src/pins.ts
1386
+ var import_core8 = require("@penvhq/core");
1216
1387
  var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1217
1388
  var DEV_PIN_VERSION = "0.0.0-dev";
1218
1389
  var BUNDLED_ENGINE_PIN = {
1219
- package: import_core7.ENGINE_PACKAGE,
1220
- version: "0.9.4",
1221
- integrity: "sha512-XnG+KlEAZyUaaKUfANPiyhY1dfYZ9KgnkF2R+5A0OskRgfYPSpBRN0i1UPtq8RnwMJaPxfSNDNdqfiiESz6kRw=="
1390
+ package: import_core8.ENGINE_PACKAGE,
1391
+ version: "0.10.0",
1392
+ integrity: "sha512-b+U0c3xWbk1ILtMfPjJW8x5XBxSLAaw/IAOLMBmGvT2uuYv41JPED8PrenEj4s3ZJY5BxpWU9mQcJHEBouaw/Q=="
1222
1393
  };
1223
1394
  function assertReleasePin(pin) {
1224
1395
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1236,8 +1407,8 @@ function releaseEnginePin(pin, ranVersion) {
1236
1407
  // src/project.ts
1237
1408
  var import_node_fs6 = require("fs");
1238
1409
  var import_node_path6 = require("path");
1239
- var import_core8 = require("@penvhq/core");
1240
- var MANIFEST_SEGMENTS = import_core8.MANIFEST_PATH.split("/");
1410
+ var import_core9 = require("@penvhq/core");
1411
+ var MANIFEST_SEGMENTS = import_core9.MANIFEST_PATH.split("/");
1241
1412
  function findUp(cwd, matches) {
1242
1413
  let dir = (0, import_node_path6.resolve)(cwd);
1243
1414
  for (; ; ) {
@@ -1256,7 +1427,7 @@ function findProject(cwd) {
1256
1427
  return root === void 0 ? void 0 : { root, manifestFile: (0, import_node_path6.join)(root, ...MANIFEST_SEGMENTS) };
1257
1428
  }
1258
1429
  function findAdoptedRoot(cwd) {
1259
- return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core8.stateDir)(dir)));
1430
+ return findUp(cwd, (dir) => (0, import_node_fs6.existsSync)((0, import_core9.stateDir)(dir)));
1260
1431
  }
1261
1432
 
1262
1433
  // src/launcher.ts
@@ -1305,14 +1476,14 @@ function pinsOf(manifest) {
1305
1476
  ];
1306
1477
  }
1307
1478
  function reportable(error, home, argv) {
1308
- return error instanceof import_core9.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1479
+ return error instanceof import_core10.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1309
1480
  updateCommand: launcherUpdateCommand(home),
1310
1481
  invokedCommand: invokedCommand(argv)
1311
1482
  }) : error;
1312
1483
  }
1313
1484
  function readManifest(manifestFile, home, argv) {
1314
1485
  try {
1315
- return (0, import_core9.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1486
+ return (0, import_core10.parseManifest)((0, import_node_fs7.readFileSync)(manifestFile, "utf8"));
1316
1487
  } catch (error) {
1317
1488
  throw reportable(error, home, argv);
1318
1489
  }
@@ -1325,7 +1496,7 @@ function readManifestToRepair(manifestFile, home, argv) {
1325
1496
  }
1326
1497
  }
1327
1498
  function report(error, io2) {
1328
- if (error instanceof import_core9.PenvError && error.remedy !== void 0) {
1499
+ if (error instanceof import_core10.PenvError && error.remedy !== void 0) {
1329
1500
  const suffix = `
1330
1501
  ${error.remedy}`;
1331
1502
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1347,7 +1518,17 @@ async function launch(options) {
1347
1518
  const { argv, cwd, env, io: io2 } = options;
1348
1519
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1349
1520
  const first = forwarded[0];
1350
- const home = penvHome(env);
1521
+ const home = (0, import_core10.penvHome)(env);
1522
+ if (first !== void 0 && forwarded.slice(1).some((token) => HELP_FLAGS.has(token))) {
1523
+ if (first === INSTALL) {
1524
+ printInstallHelp(io2);
1525
+ return 0;
1526
+ }
1527
+ if (first === ADD) {
1528
+ printAddHelp(io2);
1529
+ return 0;
1530
+ }
1531
+ }
1351
1532
  const project = findProject(cwd);
1352
1533
  if (project === void 0) {
1353
1534
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1392,16 +1573,16 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1392
1573
  if (root === void 0) {
1393
1574
  return 0;
1394
1575
  }
1395
- const manifestFile = (0, import_node_path7.join)(root, ...import_core9.MANIFEST_PATH.split("/"));
1576
+ const manifestFile = (0, import_node_path7.join)(root, ...import_core10.MANIFEST_PATH.split("/"));
1396
1577
  if ((0, import_node_fs7.existsSync)(manifestFile)) {
1397
1578
  return 0;
1398
1579
  }
1399
1580
  const pin = releaseEnginePin(options.bundledPin, engine.version);
1400
1581
  (0, import_node_fs7.writeFileSync)(
1401
1582
  manifestFile,
1402
- (0, import_core9.serializeManifest)({ format: import_core9.MANIFEST_FORMAT, engine: pin, extensions: {} })
1583
+ (0, import_core10.serializeManifest)({ format: import_core10.MANIFEST_FORMAT, engine: pin, extensions: {} })
1403
1584
  );
1404
- options.io.out(`\u2713 ${import_core9.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1585
+ options.io.out(`\u2713 ${import_core10.MANIFEST_PATH} pins ${pin.package} ${pin.version}`);
1405
1586
  await ensureAdoptedEngine(
1406
1587
  options,
1407
1588
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1480,13 +1661,21 @@ async function addExtension(options, project, home, argv, noDownload) {
1480
1661
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1481
1662
  return delegate(options, engine, onboard, home, options.cwd);
1482
1663
  }
1483
- function delegate(options, engine, forwarded, home, cwd) {
1484
- return options.spawn({
1664
+ function isRootHelp(forwarded) {
1665
+ const first = forwarded[0];
1666
+ return first === void 0 || HELP_FLAGS.has(first);
1667
+ }
1668
+ async function delegate(options, engine, forwarded, home, cwd) {
1669
+ const code = await options.spawn({
1485
1670
  command: process.execPath,
1486
1671
  args: [engine.entry, ...forwarded],
1487
1672
  cwd,
1488
- env: { ...options.env, [PENV_HOME_VAR]: home }
1673
+ env: { ...options.env, [import_core10.PENV_HOME_VAR]: home }
1489
1674
  });
1675
+ if (code === 0 && isRootHelp(forwarded)) {
1676
+ printLauncherCommands(options.io);
1677
+ }
1678
+ return code;
1490
1679
  }
1491
1680
 
1492
1681
  // src/bin.ts