@penvhq/launcher 0.9.5 → 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) {
@@ -170,6 +173,26 @@ var AddLocalFlagError = class extends import_core.PenvError {
170
173
  );
171
174
  }
172
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
+ };
173
196
  var LocalExtensionUnresolvedError = class extends import_core.PenvError {
174
197
  name = "LocalExtensionUnresolvedError";
175
198
  constructor(name, root) {
@@ -439,12 +462,13 @@ function httpFetcher() {
439
462
  // src/launcher.ts
440
463
  var import_node_fs7 = require("fs");
441
464
  var import_node_path7 = require("path");
442
- var import_core9 = require("@penvhq/core");
465
+ var import_core10 = require("@penvhq/core");
443
466
 
444
467
  // src/add.ts
445
468
  var import_node_fs5 = require("fs");
446
469
  var import_node_module2 = require("module");
447
470
  var import_node_path5 = require("path");
471
+ var import_node_url = require("url");
448
472
  var import_core6 = require("@penvhq/core");
449
473
 
450
474
  // src/config-edit.ts
@@ -714,35 +738,14 @@ function writeDeclaration(options) {
714
738
  // src/store.ts
715
739
  var import_node_fs4 = require("fs");
716
740
  var import_node_path4 = require("path");
741
+ var import_core4 = require("@penvhq/core");
717
742
 
718
743
  // src/home.ts
719
744
  var import_node_fs3 = require("fs");
720
- var import_node_os2 = require("os");
721
745
  var import_node_path3 = require("path");
722
- var import_core4 = require("@penvhq/core");
723
- var PENV_HOME_VAR = "PENV_HOME";
724
746
  var HOME_META_FILE = "meta.json";
725
747
  var INTEGRITY_FILE = ".penv-integrity";
726
748
  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
749
  function launcherUpdateCommand(home) {
747
750
  let meta;
748
751
  try {
@@ -844,7 +847,7 @@ function tarballUrl(pin) {
844
847
  return `${registry}/${pin.name}/-/${basename}-${pin.version}.tgz`;
845
848
  }
846
849
  function inspectInstall(home, kind, pin) {
847
- const dir = packageDir(home, kind, pin.name, pin.version);
850
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
848
851
  if (!(0, import_node_fs4.existsSync)(dir)) {
849
852
  return { dir, state: "absent" };
850
853
  }
@@ -858,7 +861,7 @@ function inspectInstall(home, kind, pin) {
858
861
  }
859
862
  async function installPin(options) {
860
863
  const { home, kind, pin, fetcher } = options;
861
- const dir = packageDir(home, kind, pin.name, pin.version);
864
+ const dir = (0, import_core4.packageDir)(home, kind, pin.name, pin.version);
862
865
  const url = tarballUrl(pin);
863
866
  let bytes;
864
867
  try {
@@ -1231,6 +1234,26 @@ function resolvePackageDir(name, root) {
1231
1234
  return void 0;
1232
1235
  }
1233
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
+ }
1234
1257
  function recordLocalExtension(root, name) {
1235
1258
  const file = (0, import_core6.localExtensionsFile)(root);
1236
1259
  let current;
@@ -1251,6 +1274,7 @@ async function addLocal(options, request) {
1251
1274
  if (dir === void 0) {
1252
1275
  throw new LocalExtensionUnresolvedError(request.name, root);
1253
1276
  }
1277
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1254
1278
  const installed = readExtensionPackage(dir);
1255
1279
  const declaration = writeDeclaration({
1256
1280
  root,
@@ -1262,7 +1286,7 @@ async function addLocal(options, request) {
1262
1286
  types: installed.types
1263
1287
  });
1264
1288
  recordLocalExtension(root, request.name);
1265
- io2.out(`\u2713 ${request.name} resolves from this project \u2014 nothing is pinned`);
1289
+ io2.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1266
1290
  io2.out(`\u2713 ${import_core6.LOCAL_EXTENSIONS_PATH} records it`);
1267
1291
  io2.out(`\u2713 ${declaration} declares its config type`);
1268
1292
  await offerConfigEdit(options, request.name);
@@ -1307,6 +1331,7 @@ async function add(options) {
1307
1331
  },
1308
1332
  fetcher
1309
1333
  });
1334
+ await assertLoadable(release.name, (0, import_core6.packageEntry)(dir), false);
1310
1335
  const installed = readExtensionPackage(dir);
1311
1336
  const declaration = writeDeclaration({
1312
1337
  root,
@@ -1325,14 +1350,46 @@ async function add(options) {
1325
1350
  return { onboard: await offerOnboarding(io2, release.name, installed.onboard) };
1326
1351
  }
1327
1352
 
1328
- // src/pins.ts
1353
+ // src/help.ts
1329
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");
1330
1387
  var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1331
1388
  var DEV_PIN_VERSION = "0.0.0-dev";
1332
1389
  var BUNDLED_ENGINE_PIN = {
1333
- package: import_core7.ENGINE_PACKAGE,
1334
- version: "0.9.5",
1335
- integrity: "sha512-cacsvynscs6G0NThpGaK1BfKy3POQSoogI/vvQNZ5sTZaftzwg4mPtUpT+i2GSc3KbYAHQu4Kq9MpYLOvSvulA=="
1390
+ package: import_core8.ENGINE_PACKAGE,
1391
+ version: "0.10.0",
1392
+ integrity: "sha512-b+U0c3xWbk1ILtMfPjJW8x5XBxSLAaw/IAOLMBmGvT2uuYv41JPED8PrenEj4s3ZJY5BxpWU9mQcJHEBouaw/Q=="
1336
1393
  };
1337
1394
  function assertReleasePin(pin) {
1338
1395
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1350,8 +1407,8 @@ function releaseEnginePin(pin, ranVersion) {
1350
1407
  // src/project.ts
1351
1408
  var import_node_fs6 = require("fs");
1352
1409
  var import_node_path6 = require("path");
1353
- var import_core8 = require("@penvhq/core");
1354
- var MANIFEST_SEGMENTS = import_core8.MANIFEST_PATH.split("/");
1410
+ var import_core9 = require("@penvhq/core");
1411
+ var MANIFEST_SEGMENTS = import_core9.MANIFEST_PATH.split("/");
1355
1412
  function findUp(cwd, matches) {
1356
1413
  let dir = (0, import_node_path6.resolve)(cwd);
1357
1414
  for (; ; ) {
@@ -1370,7 +1427,7 @@ function findProject(cwd) {
1370
1427
  return root === void 0 ? void 0 : { root, manifestFile: (0, import_node_path6.join)(root, ...MANIFEST_SEGMENTS) };
1371
1428
  }
1372
1429
  function findAdoptedRoot(cwd) {
1373
- 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)));
1374
1431
  }
1375
1432
 
1376
1433
  // src/launcher.ts
@@ -1419,14 +1476,14 @@ function pinsOf(manifest) {
1419
1476
  ];
1420
1477
  }
1421
1478
  function reportable(error, home, argv) {
1422
- return error instanceof import_core9.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1479
+ return error instanceof import_core10.UnsupportedManifestFormatError ? error.withLauncherUpdate({
1423
1480
  updateCommand: launcherUpdateCommand(home),
1424
1481
  invokedCommand: invokedCommand(argv)
1425
1482
  }) : error;
1426
1483
  }
1427
1484
  function readManifest(manifestFile, home, argv) {
1428
1485
  try {
1429
- 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"));
1430
1487
  } catch (error) {
1431
1488
  throw reportable(error, home, argv);
1432
1489
  }
@@ -1439,7 +1496,7 @@ function readManifestToRepair(manifestFile, home, argv) {
1439
1496
  }
1440
1497
  }
1441
1498
  function report(error, io2) {
1442
- if (error instanceof import_core9.PenvError && error.remedy !== void 0) {
1499
+ if (error instanceof import_core10.PenvError && error.remedy !== void 0) {
1443
1500
  const suffix = `
1444
1501
  ${error.remedy}`;
1445
1502
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1461,7 +1518,17 @@ async function launch(options) {
1461
1518
  const { argv, cwd, env, io: io2 } = options;
1462
1519
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1463
1520
  const first = forwarded[0];
1464
- 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
+ }
1465
1532
  const project = findProject(cwd);
1466
1533
  if (project === void 0) {
1467
1534
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1506,16 +1573,16 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1506
1573
  if (root === void 0) {
1507
1574
  return 0;
1508
1575
  }
1509
- 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("/"));
1510
1577
  if ((0, import_node_fs7.existsSync)(manifestFile)) {
1511
1578
  return 0;
1512
1579
  }
1513
1580
  const pin = releaseEnginePin(options.bundledPin, engine.version);
1514
1581
  (0, import_node_fs7.writeFileSync)(
1515
1582
  manifestFile,
1516
- (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: {} })
1517
1584
  );
1518
- 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}`);
1519
1586
  await ensureAdoptedEngine(
1520
1587
  options,
1521
1588
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1594,13 +1661,21 @@ async function addExtension(options, project, home, argv, noDownload) {
1594
1661
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1595
1662
  return delegate(options, engine, onboard, home, options.cwd);
1596
1663
  }
1597
- function delegate(options, engine, forwarded, home, cwd) {
1598
- 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({
1599
1670
  command: process.execPath,
1600
1671
  args: [engine.entry, ...forwarded],
1601
1672
  cwd,
1602
- env: { ...options.env, [PENV_HOME_VAR]: home }
1673
+ env: { ...options.env, [import_core10.PENV_HOME_VAR]: home }
1603
1674
  });
1675
+ if (code === 0 && isRootHelp(forwarded)) {
1676
+ printLauncherCommands(options.io);
1677
+ }
1678
+ return code;
1604
1679
  }
1605
1680
 
1606
1681
  // src/bin.ts