feat(POSTV1-ASSET-ALL-16): 生成并部署全量复杂素材
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
This commit is contained in:
@@ -64,6 +64,7 @@ function derivedCounts(entries) {
|
||||
dynamic_images: entries.filter((entry) => entry.resourceVersion === "p0a-complex-v1" && /^DYN\d{3}-/.test(entry.assetId)).length,
|
||||
font_panel_items: entries.filter((entry) => entry.resourceVersion === "p0a-complex-v1" && /^FONT\d{3}$/.test(entry.assetId)).length,
|
||||
static_stickers: entries.filter((entry) => entry.resourceVersion === "p0a-static-v1" && /^STK\d{3,4}$/.test(entry.assetId)).length,
|
||||
text_previews: entries.filter((entry) => entry.resourceVersion === "p0a-complex-v1" && /^TEXT-PREVIEW-/.test(entry.assetId)).length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,6 +197,12 @@ function entryFor(sourcePath, assetId, resourceVersion, relativePath, mimeType)
|
||||
};
|
||||
}
|
||||
|
||||
function safeRuntimeComponent(value) {
|
||||
const normalized = value.normalize("NFKD").replaceAll(/[^A-Za-z0-9_-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
||||
if (normalized === value) return normalized;
|
||||
return `${normalized || "asset"}-${sha256(value).slice(0, 8).toLowerCase()}`;
|
||||
}
|
||||
|
||||
function dynamicMetadata(templateRoot, descriptor, field) {
|
||||
const templateDirectory = join(templateRoot, descriptor.templateId);
|
||||
const metadata = JSON.parse(readFileSync(join(templateDirectory, "metadata.json"), "utf8"));
|
||||
@@ -205,6 +212,14 @@ function dynamicMetadata(templateRoot, descriptor, field) {
|
||||
return templateDirectory;
|
||||
}
|
||||
|
||||
function textTemplateDirectory(root, templateId) {
|
||||
const family = templateId.startsWith("FLOWER") ? "花字"
|
||||
: templateId.startsWith("SIMPLE") ? "简约"
|
||||
: templateId.startsWith("TAG") ? "标签"
|
||||
: "标题";
|
||||
return join(root, family, "templates", templateId);
|
||||
}
|
||||
|
||||
export async function buildP0aRuntimeAssetPlan({ replicationRoot }) {
|
||||
const [{ compileStaticStickerCatalog }, registry] = await Promise.all([
|
||||
import("../../packages/asset-compiler/dist/index.js"),
|
||||
@@ -230,6 +245,19 @@ export async function buildP0aRuntimeAssetPlan({ replicationRoot }) {
|
||||
if (entry.sha256 !== item.sha256.toLowerCase()) throw new Error(`static_sticker_hash_invalid:${item.stable_id}`);
|
||||
return { entry, sourcePath };
|
||||
});
|
||||
const resourceKeys = new Map(resources.map((resource) => [`${resource.entry.resourceVersion}\u0000${resource.entry.assetId}`, resource]));
|
||||
const addResource = (resource) => {
|
||||
const key = `${resource.entry.resourceVersion}\u0000${resource.entry.assetId}`;
|
||||
const existing = resourceKeys.get(key);
|
||||
if (existing) {
|
||||
if (existing.entry.sha256 !== resource.entry.sha256 || existing.entry.mimeType !== resource.entry.mimeType) {
|
||||
throw new Error(`runtime_asset_duplicate_conflict:${resource.entry.assetId}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
resourceKeys.set(key, resource);
|
||||
resources.push(resource);
|
||||
};
|
||||
|
||||
const fontPackagesRoot = join(
|
||||
replicationRoot,
|
||||
@@ -244,7 +272,7 @@ export async function buildP0aRuntimeAssetPlan({ replicationRoot }) {
|
||||
const packageDirectory = oneDirectoryWithPrefix(fontPackagesRoot, assetId);
|
||||
const sourcePath = oneSupportedFont(join(packageDirectory, "font_files"));
|
||||
const extension = extname(sourcePath).toLowerCase();
|
||||
resources.push({
|
||||
addResource({
|
||||
entry: entryFor(
|
||||
sourcePath,
|
||||
assetId,
|
||||
@@ -257,33 +285,59 @@ export async function buildP0aRuntimeAssetPlan({ replicationRoot }) {
|
||||
}
|
||||
|
||||
const templateRoot = join(replicationRoot, "sticker_interactive", "单模板归档", "templates");
|
||||
for (const descriptor of registry.P0A_DYNAMIC_RUNTIME_FONT_SOURCES) {
|
||||
const templateDirectory = dynamicMetadata(templateRoot, descriptor, "fonts");
|
||||
const sourcePath = oneSupportedFont(join(templateDirectory, ...descriptor.sourceReference.split("/")));
|
||||
const extension = extname(sourcePath).toLowerCase();
|
||||
resources.push({
|
||||
entry: entryFor(
|
||||
for (const templateId of registry.P0A_DYNAMIC_STICKER_IDS) {
|
||||
const templateDirectory = join(templateRoot, templateId);
|
||||
const metadata = JSON.parse(readFileSync(join(templateDirectory, "metadata.json"), "utf8"));
|
||||
for (const sourceReference of metadata.files?.fonts ?? []) {
|
||||
const sourcePath = oneSupportedFont(join(templateDirectory, ...sourceReference.split("/")));
|
||||
const extension = extname(sourcePath).toLowerCase();
|
||||
const assetId = basename(sourceReference);
|
||||
addResource({
|
||||
entry: entryFor(
|
||||
sourcePath,
|
||||
assetId,
|
||||
registry.P0A_COMPLEX_RELEASE_VERSION,
|
||||
`${registry.P0A_COMPLEX_RELEASE_VERSION}/${assetId}${extension}`,
|
||||
fontMimeTypes.get(extension),
|
||||
),
|
||||
sourcePath,
|
||||
descriptor.assetId,
|
||||
registry.P0A_COMPLEX_RELEASE_VERSION,
|
||||
`${registry.P0A_COMPLEX_RELEASE_VERSION}/${descriptor.assetId}${extension}`,
|
||||
fontMimeTypes.get(extension),
|
||||
),
|
||||
sourcePath,
|
||||
});
|
||||
}
|
||||
for (const descriptor of registry.P0A_DYNAMIC_RUNTIME_IMAGE_SOURCES) {
|
||||
const templateDirectory = dynamicMetadata(templateRoot, descriptor, "images");
|
||||
const sourcePath = join(templateDirectory, ...descriptor.sourceReference.split("/"));
|
||||
if (!existsSync(sourcePath) || extname(sourcePath).toLowerCase() !== ".png") {
|
||||
throw new Error(`runtime_dynamic_image_invalid:${descriptor.assetId}`);
|
||||
});
|
||||
}
|
||||
resources.push({
|
||||
for (const sourceReference of metadata.files?.images ?? []) {
|
||||
const sourcePath = join(templateDirectory, ...sourceReference.split("/"));
|
||||
const extension = extname(sourcePath).toLowerCase();
|
||||
const assetId = `${templateId}-${safeRuntimeComponent(basename(sourceReference, extension))}`;
|
||||
if (!existsSync(sourcePath) || extension !== ".png") throw new Error(`runtime_dynamic_image_invalid:${assetId}`);
|
||||
addResource({
|
||||
entry: entryFor(
|
||||
sourcePath,
|
||||
assetId,
|
||||
registry.P0A_COMPLEX_RELEASE_VERSION,
|
||||
`${registry.P0A_COMPLEX_RELEASE_VERSION}/${assetId}.png`,
|
||||
"image/png",
|
||||
),
|
||||
sourcePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const textRoot = join(replicationRoot, "sticker_text", "模板", "单模板归档");
|
||||
for (const templateId of registry.P0A_TEXT_TEMPLATE_IDS) {
|
||||
const templateDirectory = textTemplateDirectory(textRoot, templateId);
|
||||
const metadata = JSON.parse(readFileSync(join(templateDirectory, "metadata.json"), "utf8"));
|
||||
const previewReference = metadata.files?.preview;
|
||||
if (typeof previewReference !== "string" || previewReference.length === 0) continue;
|
||||
const sourcePath = join(templateDirectory, ...previewReference.split("/"));
|
||||
if (!existsSync(sourcePath) || extname(sourcePath).toLowerCase() !== ".png") {
|
||||
throw new Error(`runtime_text_preview_invalid:${templateId}`);
|
||||
}
|
||||
const assetId = `TEXT-PREVIEW-${templateId}`;
|
||||
addResource({
|
||||
entry: entryFor(
|
||||
sourcePath,
|
||||
descriptor.assetId,
|
||||
assetId,
|
||||
registry.P0A_COMPLEX_RELEASE_VERSION,
|
||||
`${registry.P0A_COMPLEX_RELEASE_VERSION}/${descriptor.assetId}.png`,
|
||||
`${registry.P0A_COMPLEX_RELEASE_VERSION}/${assetId}.png`,
|
||||
"image/png",
|
||||
),
|
||||
sourcePath,
|
||||
@@ -292,12 +346,7 @@ export async function buildP0aRuntimeAssetPlan({ replicationRoot }) {
|
||||
|
||||
const manifestPath = join(replicationRoot, "sticker_web_handoff", "sticker_web_catalog_manifest.json");
|
||||
const manifest = createRuntimeAssetManifest({
|
||||
counts: {
|
||||
dynamic_fonts: registry.P0A_DYNAMIC_RUNTIME_FONT_SOURCES.length,
|
||||
dynamic_images: registry.P0A_DYNAMIC_RUNTIME_IMAGE_SOURCES.length,
|
||||
font_panel_items: registry.P0A_REQUIRED_FONT_PANEL_IDS.length,
|
||||
static_stickers: staticResult.catalog.count,
|
||||
},
|
||||
counts: derivedCounts(resources.map((resource) => resource.entry)),
|
||||
entries: resources.map((resource) => resource.entry),
|
||||
sourceManifestSha256: fileSha256(manifestPath),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user