@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.
- package/dist/index.js +167 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
}
|