@p11-core/cli 0.0.17 → 0.0.18

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.
Files changed (2) hide show
  1. package/dist/index.js +167 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3524,6 +3524,7 @@ var P11_HISTORY_LOCK_TIMEOUT_MS = 5e3;
3524
3524
  var P11_HISTORY_DEFAULT_LIMIT = 20;
3525
3525
  var P11_RUNTIME_VERSION = "0.0.1";
3526
3526
  var P11_MANIFEST_FILE = "p11-manifest.json";
3527
+ var P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
3527
3528
  var cliPackageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
3528
3529
  var docsTopics = /* @__PURE__ */ new Map([
3529
3530
  ["index", "docs/index.md"],
@@ -4416,7 +4417,12 @@ async function buildPageModule(inputFile, outDir) {
4416
4417
  const pageSource = await readFile(inputFile, "utf8");
4417
4418
  const pageAst = parsePageSource(pageSource);
4418
4419
  validatePageAst(pageAst, inputFile);
4420
+ const pageMetadata = extractPageMetadata(pageAst, inputFile);
4421
+ rewriteRelativeModuleSpecifiers(pageAst, path.dirname(inputFile));
4422
+ await mkdir(tempDir, { recursive: true });
4419
4423
  await writeFile(pagePath, addSourceLineAttributes(pageAst, inputFile));
4424
+ const metadataAssetTracker = createSocialPreviewAssetTracker(pageMetadata, tempDir);
4425
+ const pageImport = pageMetadata ? 'import Page, { metadata as __p11PageMetadata } from "./Page";' : 'import Page from "./Page";';
4420
4426
  await writeFile(
4421
4427
  entryPath,
4422
4428
  [
@@ -4430,7 +4436,8 @@ async function buildPageModule(inputFile, outDir) {
4430
4436
  " __P11_CREATE_TEXT_ANNOTATOR__: createTextAnnotator,",
4431
4437
  " __P11_RANGE_TO_SELECTOR__: rangeToSelector",
4432
4438
  "});",
4433
- 'import Page from "./Page";',
4439
+ pageImport,
4440
+ ...pageMetadata ? ["void __p11PageMetadata;"] : [],
4434
4441
  "",
4435
4442
  'createRoot(document.getElementById("root")!).render(',
4436
4443
  " <React.StrictMode>",
@@ -4464,7 +4471,7 @@ async function buildPageModule(inputFile, outDir) {
4464
4471
  base: "./",
4465
4472
  configFile: false,
4466
4473
  logLevel: "warn",
4467
- plugins: [react(), tailwindcss()],
4474
+ plugins: [react(), tailwindcss(), ...metadataAssetTracker.plugin ? [metadataAssetTracker.plugin] : []],
4468
4475
  resolve: {
4469
4476
  alias: {
4470
4477
  "@p11-core/components/styles.css": componentStyles,
@@ -4483,6 +4490,7 @@ async function buildPageModule(inputFile, outDir) {
4483
4490
  outDir: path.relative(tempDir, outDir),
4484
4491
  emptyOutDir: true,
4485
4492
  assetsDir: "assets",
4493
+ assetsInlineLimit: 0,
4486
4494
  rollupOptions: {
4487
4495
  input: {
4488
4496
  index: "index.html"
@@ -4493,11 +4501,167 @@ async function buildPageModule(inputFile, outDir) {
4493
4501
  } finally {
4494
4502
  process.chdir(previousCwd);
4495
4503
  }
4496
- await writeFile(path.join(outDir, P11_MANIFEST_FILE), JSON.stringify({ runtimeVersion: P11_RUNTIME_VERSION }, null, 2));
4504
+ const socialPreviewImage = pageMetadata?.imageSourcePath ? socialPreviewImageForMetadata(pageMetadata, metadataAssetTracker.fileName()) : await firstSocialPreviewImage(outDir);
4505
+ await writeFile(
4506
+ path.join(outDir, P11_MANIFEST_FILE),
4507
+ JSON.stringify(
4508
+ {
4509
+ runtimeVersion: P11_RUNTIME_VERSION,
4510
+ ...pageMetadata?.title ? { title: pageMetadata.title } : {},
4511
+ ...pageMetadata?.description ? { description: pageMetadata.description } : {},
4512
+ ...socialPreviewImage ? { socialPreviewImage } : {}
4513
+ },
4514
+ null,
4515
+ 2
4516
+ )
4517
+ );
4518
+ }
4519
+ function createSocialPreviewAssetTracker(metadata, buildRoot) {
4520
+ let fileName;
4521
+ return {
4522
+ plugin: socialPreviewAssetPlugin(metadata, buildRoot, (nextFileName) => {
4523
+ fileName = nextFileName;
4524
+ }),
4525
+ fileName: () => fileName
4526
+ };
4527
+ }
4528
+ function socialPreviewAssetPlugin(metadata, buildRoot, onAssetPath) {
4529
+ if (!metadata?.imageSourcePath) return null;
4530
+ const metadataImagePath = normalizeFilePath(realpathSync(metadata.imageSourcePath));
4531
+ return {
4532
+ name: "p11-social-preview-asset",
4533
+ generateBundle(_options, bundle) {
4534
+ const matches = Object.values(bundle).filter((output) => {
4535
+ if (output.type !== "asset") return false;
4536
+ return (output.originalFileNames ?? []).some((fileName) => originalFileNameMatches(fileName, metadataImagePath, buildRoot));
4537
+ });
4538
+ if (matches.length === 1) {
4539
+ onAssetPath(matches[0].fileName);
4540
+ return;
4541
+ }
4542
+ if (matches.length > 1) {
4543
+ throw new Error(`metadata.image matched multiple built assets for ${metadata.imageSourcePath}`);
4544
+ }
4545
+ }
4546
+ };
4547
+ }
4548
+ function originalFileNameMatches(fileName, metadataImagePath, buildRoot) {
4549
+ const candidate = path.isAbsolute(fileName) ? fileName : path.resolve(buildRoot, fileName);
4550
+ try {
4551
+ return normalizeFilePath(realpathSync(candidate)) === metadataImagePath;
4552
+ } catch (_error) {
4553
+ return false;
4554
+ }
4555
+ }
4556
+ function normalizeFilePath(filePath) {
4557
+ return path.normalize(filePath).split(path.sep).join("/");
4558
+ }
4559
+ function socialPreviewImageForMetadata(metadata, builtAssetPath) {
4560
+ if (builtAssetPath) return builtAssetPath;
4561
+ throw new Error(`metadata.image was not emitted as a built asset: ${metadata.imageSourcePath}`);
4562
+ }
4563
+ async function firstSocialPreviewImage(outDir) {
4564
+ const imagePaths = await socialPreviewImagesInDir(outDir);
4565
+ return imagePaths.sort()[0];
4566
+ }
4567
+ async function socialPreviewImagesInDir(dir, rootDir = dir) {
4568
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
4569
+ if (!entries) return [];
4570
+ const images = [];
4571
+ for (const entry of entries) {
4572
+ const entryPath = path.join(dir, entry.name);
4573
+ if (entry.isDirectory()) {
4574
+ images.push(...await socialPreviewImagesInDir(entryPath, rootDir));
4575
+ continue;
4576
+ }
4577
+ if (!entry.isFile() || !P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) continue;
4578
+ images.push(path.relative(rootDir, entryPath).split(path.sep).join("/"));
4579
+ }
4580
+ return images;
4497
4581
  }
4498
4582
  function resolveBuildParentDir(buildDir) {
4499
4583
  return path.resolve(buildDir || process.env.P11_BUILD_DIR || tmpdir());
4500
4584
  }
4585
+ function rewriteRelativeModuleSpecifiers(ast, sourceDir) {
4586
+ for (const statement of ast.program.body) {
4587
+ if (statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") {
4588
+ const source = statement.source;
4589
+ if (!source || !isRelativeModuleSpecifier(source.value)) continue;
4590
+ source.value = path.resolve(sourceDir, source.value);
4591
+ }
4592
+ }
4593
+ }
4594
+ function isRelativeModuleSpecifier(value) {
4595
+ return value.startsWith("./") || value.startsWith("../");
4596
+ }
4597
+ function extractPageMetadata(ast, inputFile) {
4598
+ const sourceDir = path.dirname(inputFile);
4599
+ const importSources = importedDefaultSpecifiers(ast);
4600
+ for (const statement of ast.program.body) {
4601
+ if (statement.type !== "ExportNamedDeclaration") continue;
4602
+ const declaration = statement.declaration;
4603
+ if (!declaration || declaration.type !== "VariableDeclaration") continue;
4604
+ for (const declarator of declaration.declarations) {
4605
+ if (declarator.id.type !== "Identifier" || declarator.id.name !== "metadata") continue;
4606
+ if (!declarator.init || declarator.init.type !== "ObjectExpression") {
4607
+ throw new Error("metadata export must be an object literal.");
4608
+ }
4609
+ return metadataFromObjectExpression(declarator.init, importSources, sourceDir);
4610
+ }
4611
+ }
4612
+ return null;
4613
+ }
4614
+ function importedDefaultSpecifiers(ast) {
4615
+ const imports = /* @__PURE__ */ new Map();
4616
+ for (const statement of ast.program.body) {
4617
+ if (statement.type !== "ImportDeclaration") continue;
4618
+ for (const specifier of statement.specifiers) {
4619
+ if (specifier.type === "ImportDefaultSpecifier") {
4620
+ imports.set(specifier.local.name, statement.source.value);
4621
+ }
4622
+ }
4623
+ }
4624
+ return imports;
4625
+ }
4626
+ function metadataFromObjectExpression(objectExpression, importSources, sourceDir) {
4627
+ const metadata = {};
4628
+ for (const property of objectExpression.properties) {
4629
+ if (property.type !== "ObjectProperty" || property.computed) continue;
4630
+ const key = objectPropertyName(property.key);
4631
+ if (!key || !["title", "description", "image"].includes(key)) continue;
4632
+ if (key === "title" || key === "description") {
4633
+ metadata[key] = metadataStringValue(property.value, key);
4634
+ continue;
4635
+ }
4636
+ metadata.imageSourcePath = metadataImageSourcePath(property.value, importSources, sourceDir);
4637
+ }
4638
+ return metadata;
4639
+ }
4640
+ function objectPropertyName(key) {
4641
+ if (key.type === "Identifier") return key.name;
4642
+ if (key.type === "StringLiteral") return key.value;
4643
+ return null;
4644
+ }
4645
+ function metadataStringValue(value, key) {
4646
+ if (value.type === "StringLiteral") return value.value.trim();
4647
+ if (value.type === "TemplateLiteral" && value.expressions.length === 0) {
4648
+ return value.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join("").trim();
4649
+ }
4650
+ throw new Error(`metadata.${key} must be a string literal.`);
4651
+ }
4652
+ function metadataImageSourcePath(value, importSources, sourceDir) {
4653
+ if (value.type !== "Identifier") {
4654
+ throw new Error("metadata.image must reference a default-imported PNG, JPEG, or WebP asset.");
4655
+ }
4656
+ const importSource = importSources.get(value.name);
4657
+ if (!importSource) {
4658
+ throw new Error("metadata.image must reference a default-imported PNG, JPEG, or WebP asset.");
4659
+ }
4660
+ if (!P11_SOCIAL_PREVIEW_IMAGE_EXTENSIONS.has(path.extname(importSource).toLowerCase())) {
4661
+ throw new Error("metadata.image must reference a PNG, JPEG, or WebP asset.");
4662
+ }
4663
+ return isRelativeModuleSpecifier(importSource) ? path.resolve(sourceDir, importSource) : importSource;
4664
+ }
4501
4665
  function validatePageSource(code, inputFile = "page.tsx") {
4502
4666
  validatePageAst(parsePageSource(code), inputFile);
4503
4667
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p11-core/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {