@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.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  httpFetcher,
6
6
  nodeSpawner,
7
7
  runLauncher
8
- } from "./chunk-4YZYBC5T.js";
8
+ } from "./chunk-BNUZ52VN.js";
9
9
 
10
10
  // src/bin.ts
11
11
  function readLine() {
@@ -176,6 +176,9 @@ function addCommand(name, version, extra) {
176
176
  const spec = version === void 0 ? name : `${name}@${version}`;
177
177
  return `penv add ${spec}${extra === void 0 ? "" : ` ${extra}`}`;
178
178
  }
179
+ function addCommandFor(name, local) {
180
+ return local ? `penv add ${LOCAL_FLAG} ${name}` : addCommand(name);
181
+ }
179
182
  var NoProjectError = class extends PenvError {
180
183
  name = "NoProjectError";
181
184
  constructor(cwd) {
@@ -287,6 +290,26 @@ var AddLocalFlagError = class extends PenvError {
287
290
  );
288
291
  }
289
292
  };
293
+ var ExtensionNotImportableError = class extends PenvError {
294
+ name = "ExtensionNotImportableError";
295
+ constructor(name, entry, local) {
296
+ super(
297
+ "PENV_EXTENSION_NOT_IMPORTABLE",
298
+ entry === void 0 ? `${name} declares no entry point penv can import` : `${name} resolves to ${entry}, which penv cannot import`,
299
+ `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.`
300
+ );
301
+ }
302
+ };
303
+ var ExtensionUnloadableError = class extends PenvError {
304
+ name = "ExtensionUnloadableError";
305
+ constructor(name, entry, detail, local) {
306
+ super(
307
+ "PENV_EXTENSION_UNLOADABLE",
308
+ `${name} threw while penv imported ${entry}: ${detail}`,
309
+ `Build the package and install its dependencies, then run \`${addCommandFor(name, local)}\` again. penv adds a provider it has loaded once, or none.`
310
+ );
311
+ }
312
+ };
290
313
  var LocalExtensionUnresolvedError = class extends PenvError {
291
314
  name = "LocalExtensionUnresolvedError";
292
315
  constructor(name, root) {
@@ -593,32 +616,10 @@ function writeDeclaration(options) {
593
616
 
594
617
  // src/home.ts
595
618
  import { readFileSync as readFileSync2 } from "fs";
596
- import { homedir } from "os";
597
- import { join as join2, resolve as resolve2, sep as sep2 } from "path";
598
- import { PenvError as PenvError2 } from "@penvhq/core";
599
- var PENV_HOME_VAR = "PENV_HOME";
619
+ import { join as join2 } from "path";
600
620
  var HOME_META_FILE = "meta.json";
601
621
  var INTEGRITY_FILE = ".penv-integrity";
602
622
  var NPM_UPDATE_COMMAND = "npm install -g @penvhq/launcher";
603
- function penvHome(env) {
604
- const declared = env[PENV_HOME_VAR];
605
- if (declared !== void 0 && declared.trim() !== "") {
606
- return resolve2(declared);
607
- }
608
- return join2(homedir(), ".penv");
609
- }
610
- function packageDir(home, kind, name, version) {
611
- const bucket = resolve2(home, kind);
612
- const dir = resolve2(bucket, ...name.split("/"), version);
613
- if (!dir.startsWith(bucket + sep2)) {
614
- throw new PenvError2(
615
- "PENV_HOME_ESCAPE",
616
- `\`${name}\` at \`${version}\` resolves to ${dir}, which is outside ${bucket}`,
617
- "Name the package exactly as npm does, e.g. `@penvhq/provider-vault`."
618
- );
619
- }
620
- return dir;
621
- }
622
623
  function launcherUpdateCommand(home) {
623
624
  let meta;
624
625
  try {
@@ -723,6 +724,7 @@ import {
723
724
  writeFileSync as writeFileSync2
724
725
  } from "fs";
725
726
  import { dirname as dirname2, join as join3 } from "path";
727
+ import { packageDir } from "@penvhq/core";
726
728
  var DEFAULT_REGISTRY = "https://registry.npmjs.org";
727
729
  function tarballUrl(pin) {
728
730
  const registry = (pin.registry ?? DEFAULT_REGISTRY).replace(/\/+$/, "");
@@ -859,13 +861,16 @@ async function fetchRelease(query) {
859
861
  // src/add.ts
860
862
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
861
863
  import { createRequire } from "module";
862
- import { dirname as dirname3, relative, resolve as resolve3, sep as sep3 } from "path";
864
+ import { dirname as dirname3, relative, resolve as resolve2, sep as sep2 } from "path";
865
+ import { pathToFileURL } from "url";
863
866
  import {
864
867
  findConfigFile,
868
+ isImportableEntry,
865
869
  LOCAL_EXTENSIONS_PATH as LOCAL_EXTENSIONS_PATH2,
866
870
  localExtensionsFile,
867
871
  MANIFEST_PATH as MANIFEST_PATH2,
868
872
  OFFICIAL_SCOPE as OFFICIAL_SCOPE2,
873
+ packageEntry,
869
874
  parseLocalExtensions,
870
875
  serializeLocalExtensions,
871
876
  serializeManifest
@@ -1066,7 +1071,7 @@ async function offerConfigEdit(options, name) {
1066
1071
  io.out(line);
1067
1072
  return;
1068
1073
  }
1069
- const shown = relative(root, configFile).split(sep3).join("/");
1074
+ const shown = relative(root, configFile).split(sep2).join("/");
1070
1075
  let current = readFileSync4(configFile, "utf8");
1071
1076
  const entries = readProviderEntries(current);
1072
1077
  if (entries === void 0 || entries.length === 0) {
@@ -1113,7 +1118,7 @@ async function offerOnboarding(io, name, onboard) {
1113
1118
  return void 0;
1114
1119
  }
1115
1120
  function resolvePackageDir(name, root) {
1116
- const require2 = createRequire(resolve3(root, "noop.js"));
1121
+ const require2 = createRequire(resolve2(root, "noop.js"));
1117
1122
  try {
1118
1123
  return dirname3(require2.resolve(`${name}/package.json`));
1119
1124
  } catch {
@@ -1132,6 +1137,26 @@ function resolvePackageDir(name, root) {
1132
1137
  return void 0;
1133
1138
  }
1134
1139
  }
1140
+ function resolveProjectEntry(name, root) {
1141
+ let file;
1142
+ try {
1143
+ file = createRequire(resolve2(root, "noop.js")).resolve(name);
1144
+ } catch {
1145
+ return void 0;
1146
+ }
1147
+ return { file, importable: isImportableEntry(file) };
1148
+ }
1149
+ async function assertLoadable(name, entry, local) {
1150
+ if (entry === void 0 || !entry.importable) {
1151
+ throw new ExtensionNotImportableError(name, entry?.file, local);
1152
+ }
1153
+ try {
1154
+ await import(pathToFileURL(entry.file).href);
1155
+ } catch (cause) {
1156
+ const detail = cause instanceof Error ? cause.message : String(cause);
1157
+ throw new ExtensionUnloadableError(name, entry.file, detail, local);
1158
+ }
1159
+ }
1135
1160
  function recordLocalExtension(root, name) {
1136
1161
  const file = localExtensionsFile(root);
1137
1162
  let current;
@@ -1152,6 +1177,7 @@ async function addLocal(options, request) {
1152
1177
  if (dir === void 0) {
1153
1178
  throw new LocalExtensionUnresolvedError(request.name, root);
1154
1179
  }
1180
+ await assertLoadable(request.name, resolveProjectEntry(request.name, root), true);
1155
1181
  const installed = readExtensionPackage(dir);
1156
1182
  const declaration = writeDeclaration({
1157
1183
  root,
@@ -1163,7 +1189,7 @@ async function addLocal(options, request) {
1163
1189
  types: installed.types
1164
1190
  });
1165
1191
  recordLocalExtension(root, request.name);
1166
- io.out(`\u2713 ${request.name} resolves from this project \u2014 nothing is pinned`);
1192
+ io.out(`\u2713 ${request.name} resolves from this project and imports \u2014 nothing is pinned`);
1167
1193
  io.out(`\u2713 ${LOCAL_EXTENSIONS_PATH2} records it`);
1168
1194
  io.out(`\u2713 ${declaration} declares its config type`);
1169
1195
  await offerConfigEdit(options, request.name);
@@ -1208,6 +1234,7 @@ async function add(options) {
1208
1234
  },
1209
1235
  fetcher
1210
1236
  });
1237
+ await assertLoadable(release.name, packageEntry(dir), false);
1211
1238
  const installed = readExtensionPackage(dir);
1212
1239
  const declaration = writeDeclaration({
1213
1240
  root,
@@ -1272,8 +1299,8 @@ function nodeSpawner() {
1272
1299
  // src/engine.ts
1273
1300
  import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
1274
1301
  import { createRequire as createRequire2 } from "module";
1275
- import { dirname as dirname4, join as join5, resolve as resolve4, sep as sep4 } from "path";
1276
- import { ENGINE_PACKAGE as ENGINE_PACKAGE2, PenvError as PenvError3 } from "@penvhq/core";
1302
+ import { dirname as dirname4, join as join5, resolve as resolve3, sep as sep3 } from "path";
1303
+ import { ENGINE_PACKAGE as ENGINE_PACKAGE2, PenvError as PenvError2 } from "@penvhq/core";
1277
1304
  var ENGINE_BIN = "penv-engine";
1278
1305
  function readPackageManifest(dir) {
1279
1306
  try {
@@ -1299,9 +1326,9 @@ function binPath(bin) {
1299
1326
  function engineAt(dir, name, version) {
1300
1327
  const manifest = readPackageManifest(dir);
1301
1328
  const bin = manifest === void 0 ? void 0 : binPath(manifest.bin);
1302
- const root = resolve4(dir);
1303
- const entry = bin === void 0 ? void 0 : resolve4(root, bin);
1304
- if (entry === void 0 || !entry.startsWith(root + sep4) || !existsSync2(entry)) {
1329
+ const root = resolve3(dir);
1330
+ const entry = bin === void 0 ? void 0 : resolve3(root, bin);
1331
+ if (entry === void 0 || !entry.startsWith(root + sep3) || !existsSync2(entry)) {
1305
1332
  throw new EngineEntryError(name, version, dir);
1306
1333
  }
1307
1334
  return { name, version, dir, entry };
@@ -1312,7 +1339,7 @@ function bundledEngine() {
1312
1339
  try {
1313
1340
  manifestFile = require2.resolve(`${ENGINE_PACKAGE2}/package.json`);
1314
1341
  } catch {
1315
- throw new PenvError3(
1342
+ throw new PenvError2(
1316
1343
  "PENV_NO_BUNDLED_ENGINE",
1317
1344
  `This penv installation is missing ${ENGINE_PACKAGE2}, the engine it runs \`init\` with`,
1318
1345
  "Reinstall the launcher with `npm install -g @penvhq/launcher`."
@@ -1342,8 +1369,8 @@ var DEV_PIN_INTEGRITY = "sha512-development-build-not-a-published-release";
1342
1369
  var DEV_PIN_VERSION = "0.0.0-dev";
1343
1370
  var BUNDLED_ENGINE_PIN = {
1344
1371
  package: ENGINE_PACKAGE3,
1345
- version: "0.9.5",
1346
- integrity: "sha512-cacsvynscs6G0NThpGaK1BfKy3POQSoogI/vvQNZ5sTZaftzwg4mPtUpT+i2GSc3KbYAHQu4Kq9MpYLOvSvulA=="
1372
+ version: "0.10.0",
1373
+ integrity: "sha512-b+U0c3xWbk1ILtMfPjJW8x5XBxSLAaw/IAOLMBmGvT2uuYv41JPED8PrenEj4s3ZJY5BxpWU9mQcJHEBouaw/Q=="
1347
1374
  };
1348
1375
  function assertReleasePin(pin) {
1349
1376
  if (pin.integrity === DEV_PIN_INTEGRITY || pin.version === DEV_PIN_VERSION) {
@@ -1360,11 +1387,11 @@ function releaseEnginePin(pin, ranVersion) {
1360
1387
 
1361
1388
  // src/project.ts
1362
1389
  import { existsSync as existsSync3 } from "fs";
1363
- import { dirname as dirname5, join as join6, resolve as resolve5 } from "path";
1390
+ import { dirname as dirname5, join as join6, resolve as resolve4 } from "path";
1364
1391
  import { MANIFEST_PATH as MANIFEST_PATH3, stateDir } from "@penvhq/core";
1365
1392
  var MANIFEST_SEGMENTS = MANIFEST_PATH3.split("/");
1366
1393
  function findUp(cwd, matches) {
1367
- let dir = resolve5(cwd);
1394
+ let dir = resolve4(cwd);
1368
1395
  for (; ; ) {
1369
1396
  if (matches(dir)) {
1370
1397
  return dir;
@@ -1389,12 +1416,48 @@ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync
1389
1416
  import { join as join7 } from "path";
1390
1417
  import {
1391
1418
  MANIFEST_FORMAT,
1392
- MANIFEST_PATH as MANIFEST_PATH4,
1393
- PenvError as PenvError4,
1419
+ MANIFEST_PATH as MANIFEST_PATH5,
1420
+ PENV_HOME_VAR,
1421
+ PenvError as PenvError3,
1394
1422
  parseManifest as parseManifest2,
1423
+ penvHome,
1395
1424
  serializeManifest as serializeManifest2,
1396
1425
  UnsupportedManifestFormatError
1397
1426
  } from "@penvhq/core";
1427
+
1428
+ // src/help.ts
1429
+ import { EXTENSIONS_PATH as EXTENSIONS_PATH3, MANIFEST_PATH as MANIFEST_PATH4 } from "@penvhq/core";
1430
+ function printLauncherCommands(io) {
1431
+ io.out("");
1432
+ io.out("LAUNCHER COMMANDS (penv itself, whatever engine a project pins)");
1433
+ io.out("");
1434
+ io.out(` install Installs everything ${MANIFEST_PATH4} pins`);
1435
+ io.out(" add <package> Pins a provider extension, and commits the decision");
1436
+ io.out(` add ${LOCAL_FLAG} <package> Adds the one this repository builds \u2014 nothing is pinned`);
1437
+ io.out("");
1438
+ }
1439
+ function printInstallHelp(io) {
1440
+ io.out(`${INSTALL_COMMAND}`);
1441
+ io.out("");
1442
+ io.out(`Downloads, verifies and installs every version ${MANIFEST_PATH4} pins \u2014 the engine and`);
1443
+ io.out("every extension \u2014 into $PENV_HOME. The one penv command CI and production run that");
1444
+ io.out("may reach the registry.");
1445
+ }
1446
+ function printAddHelp(io) {
1447
+ io.out("penv add <package>[@<version>]");
1448
+ io.out("");
1449
+ io.out(` ${LOCAL_FLAG} Add the package this repository builds, resolved from it`);
1450
+ io.out(" --registry <url> Take the release from a private registry instead of npmjs");
1451
+ io.out(
1452
+ ` ${TRUST_YOUNG_FLAG} Add a release published less than ${MIN_PACKAGE_AGE_DAYS} days ago`
1453
+ );
1454
+ io.out("");
1455
+ io.out(`Pins the release in ${MANIFEST_PATH4} and writes its type declaration to`);
1456
+ io.out(`${EXTENSIONS_PATH3}/. Both are committed, so \`${INSTALL_COMMAND}\` gives every machine`);
1457
+ io.out(`the bytes you reviewed. \`${LOCAL_FLAG}\` pins nothing and touches no manifest.`);
1458
+ }
1459
+
1460
+ // src/launcher.ts
1398
1461
  var INSTALL = "install";
1399
1462
  var ADD = "add";
1400
1463
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["--version", "-v"]);
@@ -1460,7 +1523,7 @@ function readManifestToRepair(manifestFile, home, argv) {
1460
1523
  }
1461
1524
  }
1462
1525
  function report(error, io) {
1463
- if (error instanceof PenvError4 && error.remedy !== void 0) {
1526
+ if (error instanceof PenvError3 && error.remedy !== void 0) {
1464
1527
  const suffix = `
1465
1528
  ${error.remedy}`;
1466
1529
  const message = error.message.endsWith(suffix) ? error.message.slice(0, -suffix.length) : error.message;
@@ -1483,6 +1546,16 @@ async function launch(options) {
1483
1546
  const { noDownload, forwarded } = splitLauncherFlags(argv);
1484
1547
  const first = forwarded[0];
1485
1548
  const home = penvHome(env);
1549
+ if (first !== void 0 && forwarded.slice(1).some((token) => HELP_FLAGS.has(token))) {
1550
+ if (first === INSTALL) {
1551
+ printInstallHelp(io);
1552
+ return 0;
1553
+ }
1554
+ if (first === ADD) {
1555
+ printAddHelp(io);
1556
+ return 0;
1557
+ }
1558
+ }
1486
1559
  const project = findProject(cwd);
1487
1560
  if (project === void 0) {
1488
1561
  if (first !== void 0 && VERSION_FLAGS.has(first)) {
@@ -1527,7 +1600,7 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1527
1600
  if (root === void 0) {
1528
1601
  return 0;
1529
1602
  }
1530
- const manifestFile = join7(root, ...MANIFEST_PATH4.split("/"));
1603
+ const manifestFile = join7(root, ...MANIFEST_PATH5.split("/"));
1531
1604
  if (existsSync4(manifestFile)) {
1532
1605
  return 0;
1533
1606
  }
@@ -1536,7 +1609,7 @@ async function adopt(options, forwarded, home, cwd, noDownload) {
1536
1609
  manifestFile,
1537
1610
  serializeManifest2({ format: MANIFEST_FORMAT, engine: pin, extensions: {} })
1538
1611
  );
1539
- options.io.out(`\u2713 ${MANIFEST_PATH4} pins ${pin.package} ${pin.version}`);
1612
+ options.io.out(`\u2713 ${MANIFEST_PATH5} pins ${pin.package} ${pin.version}`);
1540
1613
  await ensureAdoptedEngine(
1541
1614
  options,
1542
1615
  { name: pin.package, version: pin.version, integrity: pin.integrity },
@@ -1615,13 +1688,21 @@ async function addExtension(options, project, home, argv, noDownload) {
1615
1688
  const engine = engineAt(dir, enginePin.name, enginePin.version);
1616
1689
  return delegate(options, engine, onboard, home, options.cwd);
1617
1690
  }
1618
- function delegate(options, engine, forwarded, home, cwd) {
1619
- return options.spawn({
1691
+ function isRootHelp(forwarded) {
1692
+ const first = forwarded[0];
1693
+ return first === void 0 || HELP_FLAGS.has(first);
1694
+ }
1695
+ async function delegate(options, engine, forwarded, home, cwd) {
1696
+ const code = await options.spawn({
1620
1697
  command: process.execPath,
1621
1698
  args: [engine.entry, ...forwarded],
1622
1699
  cwd,
1623
1700
  env: { ...options.env, [PENV_HOME_VAR]: home }
1624
1701
  });
1702
+ if (code === 0 && isRootHelp(forwarded)) {
1703
+ printLauncherCommands(options.io);
1704
+ }
1705
+ return code;
1625
1706
  }
1626
1707
 
1627
1708
  export {
@@ -1642,6 +1723,8 @@ export {
1642
1723
  AddFlagError,
1643
1724
  LOCAL_FLAG,
1644
1725
  AddLocalFlagError,
1726
+ ExtensionNotImportableError,
1727
+ ExtensionUnloadableError,
1645
1728
  LocalExtensionUnresolvedError,
1646
1729
  AddLocalInCiError,
1647
1730
  AddRegistryError,
@@ -1665,12 +1748,9 @@ export {
1665
1748
  renderDeclaration,
1666
1749
  declarationPath,
1667
1750
  writeDeclaration,
1668
- PENV_HOME_VAR,
1669
1751
  HOME_META_FILE,
1670
1752
  INTEGRITY_FILE,
1671
1753
  NPM_UPDATE_COMMAND,
1672
- penvHome,
1673
- packageDir,
1674
1754
  launcherUpdateCommand,
1675
1755
  integrityOf,
1676
1756
  readTarball,
@@ -1695,4 +1775,4 @@ export {
1695
1775
  findAdoptedRoot,
1696
1776
  runLauncher
1697
1777
  };
1698
- //# sourceMappingURL=chunk-4YZYBC5T.js.map
1778
+ //# sourceMappingURL=chunk-BNUZ52VN.js.map