@coreframe/assets 0.0.0 → 0.1.1

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.
@@ -0,0 +1,187 @@
1
+ //#region src/contracts.d.ts
2
+ declare const catalogSchemaVersion: 2;
3
+ declare const manifestSchemaVersion: 2;
4
+ type AssetKind = "image" | "svg" | "audio" | "video" | "lottie";
5
+ type AssetCatalog = {
6
+ schemaVersion: typeof catalogSchemaVersion;
7
+ assets: Record<string, AssetCatalogEntry>;
8
+ packs?: Record<string, AssetCatalogPack>;
9
+ };
10
+ type AssetCatalogPack = {
11
+ assets: Record<string, AssetCatalogEntry>;
12
+ };
13
+ type AssetCatalogEntry = ImageCatalogEntry | SvgCatalogEntry | AudioCatalogEntry | VideoCatalogEntry | LottieCatalogEntry;
14
+ type ImageCatalogEntry = {
15
+ kind: "image";
16
+ source: AssetSource;
17
+ alt: string;
18
+ variantWidths?: number[];
19
+ };
20
+ type SvgCatalogEntry = {
21
+ kind: "svg";
22
+ source: AssetSource;
23
+ alt: string;
24
+ };
25
+ type AudioCatalogEntry = {
26
+ kind: "audio";
27
+ source: AssetSource;
28
+ };
29
+ type VideoCatalogEntry = {
30
+ kind: "video";
31
+ source: AssetSource;
32
+ };
33
+ type LottieCatalogEntry = {
34
+ kind: "lottie";
35
+ source: AssetSource;
36
+ };
37
+ type AssetSource = {
38
+ digest: string;
39
+ filename: string;
40
+ mediaType: string;
41
+ size: number;
42
+ };
43
+ type AssetManifest = {
44
+ schemaVersion: typeof manifestSchemaVersion;
45
+ pack?: string;
46
+ assets: Record<string, AssetManifestEntry>;
47
+ };
48
+ type AssetManifestEntry = ImageManifestEntry | SvgManifestEntry | AudioManifestEntry | VideoManifestEntry | LottieManifestEntry;
49
+ type ImageManifestEntry = {
50
+ kind: "image";
51
+ sourceDigest: string;
52
+ alt: string;
53
+ width: number;
54
+ height: number;
55
+ variants: AssetVariant[];
56
+ };
57
+ type SvgManifestEntry = {
58
+ kind: "svg";
59
+ sourceDigest: string;
60
+ alt: string;
61
+ viewBox: SvgViewBox;
62
+ file: SvgAssetFile;
63
+ };
64
+ type AudioManifestEntry = {
65
+ kind: "audio";
66
+ sourceDigest: string;
67
+ durationSeconds: number;
68
+ file: AudioAssetFile;
69
+ };
70
+ type VideoManifestEntry = {
71
+ kind: "video";
72
+ sourceDigest: string;
73
+ width: number;
74
+ height: number;
75
+ durationSeconds: number;
76
+ hasAudio: boolean;
77
+ file: VideoAssetFile;
78
+ };
79
+ type LottieManifestEntry = {
80
+ kind: "lottie";
81
+ sourceDigest: string;
82
+ width: number;
83
+ height: number;
84
+ frameRate: number;
85
+ durationSeconds: number;
86
+ file: LottieAssetFile;
87
+ };
88
+ type SvgViewBox = {
89
+ minX: number;
90
+ minY: number;
91
+ width: number;
92
+ height: number;
93
+ };
94
+ type AssetVariant = {
95
+ format: "webp";
96
+ mediaType: "image/webp";
97
+ width: number;
98
+ height: number;
99
+ path: string;
100
+ size: number;
101
+ hash: string;
102
+ };
103
+ type AssetFile = SvgAssetFile | AudioAssetFile | VideoAssetFile | LottieAssetFile;
104
+ type SvgAssetFile = {
105
+ format: "svg";
106
+ mediaType: "image/svg+xml";
107
+ path: string;
108
+ size: number;
109
+ hash: string;
110
+ };
111
+ type AudioAssetFile = {
112
+ format: "mp3";
113
+ mediaType: "audio/mpeg";
114
+ path: string;
115
+ size: number;
116
+ hash: string;
117
+ };
118
+ type VideoAssetFile = {
119
+ format: "mp4";
120
+ mediaType: "video/mp4";
121
+ path: string;
122
+ size: number;
123
+ hash: string;
124
+ };
125
+ type LottieAssetFile = {
126
+ format: "json";
127
+ mediaType: "application/json";
128
+ path: string;
129
+ size: number;
130
+ hash: string;
131
+ };
132
+ type AssetOutput = AssetVariant | AssetFile;
133
+ type AssetProblem = {
134
+ code: "CATALOG_MISSING" | "GENERATED_ASSET_MISSING" | "INVALID_ALT" | "INVALID_ASSET_ID" | "INVALID_CATALOG" | "INVALID_MANIFEST" | "INVALID_MEDIA" | "INVALID_REFERENCE" | "INVALID_SOURCE" | "INVALID_VARIANT_PATH" | "INVALID_VARIANT_WIDTHS" | "MANIFEST_MISSING" | "MANIFEST_STALE" | "REFERENCE_MISSING" | "SCHEMA_MIGRATION_REQUIRED" | "SOURCE_UNAVAILABLE";
135
+ message: string;
136
+ assetId?: string;
137
+ file?: string;
138
+ fix?: string;
139
+ };
140
+ type AssetStatus = {
141
+ ok: boolean;
142
+ assets: AssetStatusEntry[];
143
+ problems: AssetProblem[];
144
+ };
145
+ type AssetStatusEntry = {
146
+ assetId: string;
147
+ source: AssetSource;
148
+ outputs: AssetOutputStatus[];
149
+ };
150
+ type AssetOutputStatus = AssetOutput & {
151
+ exists: boolean;
152
+ };
153
+ type AssetBuildResult = {
154
+ ok: boolean;
155
+ assets: AssetBuildEntry[];
156
+ fetched: string[];
157
+ problems: AssetProblem[];
158
+ };
159
+ type AssetBuildEntry = {
160
+ assetId: string;
161
+ outputs: AssetOutput[];
162
+ };
163
+ type AssetPublishResult = {
164
+ ok: boolean;
165
+ uploaded: PublishedAsset[];
166
+ skipped: PublishedAsset[];
167
+ reportPath: string;
168
+ problems: AssetProblem[];
169
+ };
170
+ type PublishedAsset = {
171
+ assetId: string;
172
+ source: string;
173
+ target: string;
174
+ size: number;
175
+ };
176
+ type LoadedAssetCatalog = {
177
+ catalog: AssetCatalog;
178
+ problems: AssetProblem[];
179
+ migrated?: boolean;
180
+ migrationRequired?: boolean;
181
+ };
182
+ type LoadedAssetManifest = {
183
+ manifest: AssetManifest;
184
+ problems: AssetProblem[];
185
+ };
186
+ //#endregion
187
+ export { AssetBuildEntry, AssetBuildResult, AssetCatalog, AssetCatalogEntry, AssetCatalogPack, AssetFile, AssetKind, AssetManifest, AssetManifestEntry, AssetOutput, AssetOutputStatus, AssetProblem, AssetPublishResult, AssetSource, AssetStatus, AssetStatusEntry, AssetVariant, AudioAssetFile, AudioCatalogEntry, AudioManifestEntry, ImageCatalogEntry, ImageManifestEntry, LoadedAssetCatalog, LoadedAssetManifest, LottieAssetFile, LottieCatalogEntry, LottieManifestEntry, PublishedAsset, SvgAssetFile, SvgCatalogEntry, SvgManifestEntry, SvgViewBox, VideoAssetFile, VideoCatalogEntry, VideoManifestEntry, catalogSchemaVersion, manifestSchemaVersion };
@@ -0,0 +1 @@
1
+ import{createReadStream as e}from"node:fs";import{access as t,copyFile as n,mkdir as r,open as i,readFile as a,readdir as o,rename as s,rm as c,stat as l,utimes as u,writeFile as d}from"node:fs/promises";import{homedir as f,platform as p}from"node:os";import m from"node:process";const h={access:t,copyFile:n,createReadStream:e,mkdir:r,open:i,readFile:a,readdir:o,rename:s,rm:c,stat:l,utimes:u,writeFile:d},g={homedir:f,platform:p},_={cwd:m.cwd.bind(m),env:m.env,pid:m.pid};export{h as fs,g as os,_ as process};
package/dist/images.js ADDED
@@ -0,0 +1 @@
1
+ import{fs as e}from"./environment.js";import{catalogPath as t,resolveAssetVariantFile as n}from"./project.js";import{sha256 as r}from"./storage.js";import{dirname as i,extname as a}from"node:path";const o=[320,640,960,1280,1920,2560],s=o.at(-1);async function c({assetId:t,config:a,cwd:o,entry:s,sourceDimensions:c,sourcePath:u}){let d=await m();return Promise.all(l(s,c.width).map(async s=>{let l=await d(u).resize({width:s,withoutEnlargement:!0}).webp().toBuffer(),f=r(l),p=`projects/${a.projectId}/assets/${t}.${f.slice(0,12)}.${s}.webp`,m=n(o,p);await e.mkdir(i(m),{recursive:!0}),await e.writeFile(m,l);let h=await d(l).metadata();return{format:`webp`,mediaType:`image/webp`,width:h.width??s,height:h.height??Math.round(c.height/c.width*s),path:p,size:l.byteLength,hash:f}}))}function l(e,t){if(e.variantWidths)return e.variantWidths;let n=Math.min(Math.round(t),s);return[...o.filter(e=>e<=n),n].filter((e,t,n)=>t===0||e!==n[t-1])}function u(e,t){let n=f(e,t);if(n)throw Error(`Invalid variant widths: ${n}`)}function d(e,n,r){let i=f(n.variantWidths,r);return i?{code:`INVALID_VARIANT_WIDTHS`,message:`Asset "${e}" has invalid variantWidths: ${i}`,assetId:e,file:t,fix:`Edit ${t}, then run: coreframe assets build --id ${e}`}:void 0}function f(e,t){if(e!==void 0){if(!Array.isArray(e)||e.length===0)return`expected a non-empty array.`;for(let[n,r]of e.entries()){if(!Number.isInteger(r)||r<=0)return`width ${String(r)} must be a positive integer.`;if(n>0&&r<=e[n-1])return`widths must be unique and strictly ascending.`;if(r>t)return`width ${r} exceeds the source width of ${t}.`}}}async function p(e){let t=await(await m())(e).metadata();if(!t.width||!t.height)throw Error(`Could not read dimensions for ${e}.`);return{width:t.width,height:t.height}}async function m(){try{return(await import(`sharp`)).default}catch{throw Error(`The assets workflow requires the optional sharp dependency.`)}}function h(e){let t={".avif":`image/avif`,".gif":`image/gif`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".png":`image/png`,".tif":`image/tiff`,".tiff":`image/tiff`,".webp":`image/webp`}[a(e).toLowerCase()];if(!t)throw Error(`Unsupported raster image type: ${a(e)||`unknown`}.`);return t}export{u as assertVariantWidths,l as assetVariantWidths,c as buildAssetVariants,h as mediaTypeFor,p as readImageDimensions,d as variantWidthsProblem};
@@ -0,0 +1,4 @@
1
+ import { AssetBuildEntry, AssetBuildResult, AssetCatalog, AssetCatalogEntry, AssetCatalogPack, AssetFile, AssetKind, AssetManifest, AssetManifestEntry, AssetOutput, AssetOutputStatus, AssetProblem, AssetPublishResult, AssetSource, AssetStatus, AssetStatusEntry, AssetVariant, AudioAssetFile, AudioCatalogEntry, AudioManifestEntry, ImageCatalogEntry, ImageManifestEntry, LoadedAssetCatalog, LoadedAssetManifest, LottieAssetFile, LottieCatalogEntry, LottieManifestEntry, PublishedAsset, SvgAssetFile, SvgCatalogEntry, SvgManifestEntry, SvgViewBox, VideoAssetFile, VideoCatalogEntry, VideoManifestEntry } from "./contracts.js";
2
+ import { assetCatalogEntries, assetCatalogPackNames, assetManifestPath, findCatalogAsset, formatAssetProblems, listAssetManifestPacks, manifestEntryOutputs, readAssetCatalog, readAssetManifest, resolveAssetVariantFile } from "./project.js";
3
+ import { AddAssetOptions, AssetManifestStatus, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetManifestsOptions, CheckAssetsOptions, PublishAssetsOptions, addAsset, buildAssets, checkAssetManifests, checkAssets, materializeAsset, printAssetResult, publishAssets } from "./workflows.js";
4
+ export { AddAssetOptions, type AssetBuildEntry, type AssetBuildResult, type AssetCatalog, type AssetCatalogEntry, type AssetCatalogPack, type AssetFile, type AssetKind, type AssetManifest, type AssetManifestEntry, AssetManifestStatus, type AssetOutput, type AssetOutputStatus, type AssetProblem, type AssetPublishResult, type AssetSource, type AssetStatus, type AssetStatusEntry, type AssetVariant, AssetWorkflowOptions, type AudioAssetFile, type AudioCatalogEntry, type AudioManifestEntry, BuildAssetsOptions, CheckAssetManifestsOptions, CheckAssetsOptions, type ImageCatalogEntry, type ImageManifestEntry, type LoadedAssetCatalog, type LoadedAssetManifest, type LottieAssetFile, type LottieCatalogEntry, type LottieManifestEntry, PublishAssetsOptions, type PublishedAsset, type SvgAssetFile, type SvgCatalogEntry, type SvgManifestEntry, type SvgViewBox, type VideoAssetFile, type VideoCatalogEntry, type VideoManifestEntry, addAsset, assetCatalogEntries, assetCatalogPackNames, assetManifestPath, buildAssets, checkAssetManifests, checkAssets, findCatalogAsset, formatAssetProblems, listAssetManifestPacks, manifestEntryOutputs, materializeAsset, printAssetResult, publishAssets, readAssetCatalog, readAssetManifest, resolveAssetVariantFile };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{assetCatalogEntries as e,assetCatalogPackNames as t,assetManifestPath as n,findCatalogAsset as r,formatAssetProblems as i,listAssetManifestPacks as a,manifestEntryOutputs as o,readAssetCatalog as s,readAssetManifest as c,resolveAssetVariantFile as l}from"./project.js";import{addAsset as u,buildAssets as d,checkAssetManifests as f,checkAssets as p,materializeAsset as m,printAssetResult as h,publishAssets as g}from"./workflows.js";export{u as addAsset,e as assetCatalogEntries,t as assetCatalogPackNames,n as assetManifestPath,d as buildAssets,f as checkAssetManifests,p as checkAssets,r as findCatalogAsset,i as formatAssetProblems,a as listAssetManifestPacks,o as manifestEntryOutputs,m as materializeAsset,h as printAssetResult,g as publishAssets,s as readAssetCatalog,c as readAssetManifest,l as resolveAssetVariantFile};
package/dist/media.js ADDED
@@ -0,0 +1 @@
1
+ import{fs as e}from"./environment.js";import{resolveAssetVariantFile as t}from"./project.js";import{dirname as n,extname as r}from"node:path";import{MP4BoxBuffer as i,createFile as a}from"mp4box";import{parseFile as o}from"music-metadata";import{SaxesParser as s}from"saxes";const c=new Set([`foreignobject`,`script`]),l=/\b(?:data|file|https?|javascript):|\/\//i,u=/url\(\s*["']?(?!#)/i;function d(e){switch(r(e).toLowerCase()){case`.svg`:return`svg`;case`.mp3`:return`audio`;case`.mp4`:return`video`;case`.json`:return`lottie`;default:return`image`}}function f(e,t){if(t===`svg`)return`image/svg+xml`;if(t===`audio`)return`audio/mpeg`;if(t===`video`)return`video/mp4`;if(t===`lottie`)return`application/json`;throw Error(`Raster image media types are resolved by the image pipeline.`)}async function p(t){let n;try{n=JSON.parse(await e.readFile(t,`utf8`))}catch(e){throw Error(`Lottie source must be valid JSON: ${e.message}`)}if(!h(n))throw Error(`Lottie source must contain a JSON object.`);let{assets:r,fonts:i,fr:a,h:o,ip:s,layers:c,op:l,w:u}=n;if(!_(u)||!_(o))throw Error(`Lottie source must contain positive integer width and height values.`);if(!g(a))throw Error(`Lottie source must contain a positive frame rate.`);if(typeof s!=`number`||!Number.isFinite(s)||typeof l!=`number`||!Number.isFinite(l)||l<=s)throw Error(`Lottie source must contain finite in and out points with op greater than ip.`);if(!Array.isArray(c))throw Error(`Lottie source must contain a layers array.`);if(r!==void 0){if(!Array.isArray(r))throw Error(`Lottie assets must be an array.`);for(let e of r){if(!h(e))throw Error(`Lottie assets must contain JSON objects.`);if(`p`in e&&!m(e))throw Error(`Lottie image and data assets must be embedded as data URLs.`)}}if(h(i)&&Array.isArray(i.list)){for(let e of i.list)if(h(e)&&typeof e.fPath==`string`&&e.fPath.trim())throw Error(`Lottie fonts must not reference external font files.`)}return{durationSeconds:(l-s)/a,frameRate:a,height:o,width:u}}function m(e){return e.e===1&&typeof e.p==`string`&&/^data:[^,]+,/i.test(e.p)&&(e.u===void 0||e.u===``)}function h(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function g(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function _(e){return Number.isSafeInteger(e)&&e>0}async function v(t){let n=await e.readFile(t,`utf8`),r=new s({xmlns:!0,fileName:t}),i=!1,a=0,o;r.on(`doctype`,()=>{throw Error(`SVG document types and entities are not allowed.`)}),r.on(`processinginstruction`,()=>{throw Error(`SVG processing instructions are not allowed.`)}),r.on(`opentag`,e=>{let t=e.local.toLowerCase();if(!i){if(i=!0,t!==`svg`)throw Error(`SVG source must have an <svg> root element.`);o=b(y(e,`viewBox`))}if(c.has(t))throw Error(`SVG <${e.local}> elements are not allowed.`);t===`style`&&(a+=1);for(let t of Object.values(e.attributes)){let e=t.name.toLowerCase(),n=t.value.trim();if(!(e===`xmlns`||e.startsWith(`xmlns:`))){if(t.local.toLowerCase().startsWith(`on`))throw Error(`SVG event handler attribute "${t.name}" is not allowed.`);if((e===`href`||e===`xlink:href`)&&n&&!n.startsWith(`#`))throw Error(`SVG external reference "${t.name}" is not allowed.`);if(l.test(n)||u.test(n))throw Error(`SVG external resource in attribute "${t.name}" is not allowed.`)}}}),r.on(`closetag`,e=>{e.local.toLowerCase()===`style`&&--a});let d=e=>{if(a>0&&(l.test(e)||u.test(e)))throw Error(`SVG styles may not load external resources.`)};if(r.on(`text`,d),r.on(`cdata`,d),r.write(n).close(),!i||!o)throw Error(`SVG source must contain a valid, positive viewBox.`);return{viewBox:o}}function y(e,t){return Object.values(e.attributes).find(e=>e.name===t)?.value}function b(e){let t=e?.trim().split(/[\s,]+/).map(Number);if(!t||t.length!==4||t.some(e=>!Number.isFinite(e))||t[2]<=0||t[3]<=0)throw Error(`SVG source must contain a valid, positive viewBox.`);return{minX:t[0],minY:t[1],width:t[2],height:t[3]}}async function x(e){let t=await o(e,{duration:!0,skipCovers:!0}),n=t.format.codec?.toLowerCase()??``;if(!n.includes(`layer 3`)&&!n.includes(`mp3`))throw Error(`Audio source must contain MPEG Layer III audio.`);let r=t.format.duration;if(!r||!Number.isFinite(r)||r<=0)throw Error(`Could not determine the MP3 duration.`);return{durationSeconds:r}}async function S(e){let t=await C(e);if(!t.isProgressive)throw Error(`MP4 source must place its moov atom before media data.`);if(t.videoTracks.length!==1)throw Error(`MP4 source must contain exactly one video track.`);if(t.audioTracks.length>1)throw Error(`MP4 source may contain at most one audio track.`);if(t.subtitleTracks.length||t.metadataTracks.length||t.otherTracks.length)throw Error(`MP4 source may contain only H.264 video and optional AAC audio.`);let n=t.videoTracks[0];if(!/^avc[13]\./i.test(n.codec))throw Error(`MP4 video codec "${n.codec}" is not H.264/AVC.`);let r=t.audioTracks[0];if(r&&!/^mp4a\.40\.2$/i.test(r.codec))throw Error(`MP4 audio codec "${r.codec}" is not AAC-LC.`);let i=n.video?.width??n.track_width,a=n.video?.height??n.track_height,o=t.duration/t.timescale;if(!Number.isFinite(i)||i<=0||!Number.isFinite(a)||a<=0||!Number.isFinite(o)||o<=0)throw Error(`Could not determine MP4 dimensions and duration.`);return{durationSeconds:o,hasAudio:!!r,height:Math.round(a),width:Math.round(i)}}async function C(t){let n=a(),r,o;n.onReady=e=>{r=e},n.onError=e=>{o=Error(`Could not parse MP4 source: ${e}`)};let s=0;for await(let r of e.createReadStream(t)){let e=new Uint8Array(r),t=i.fromArrayBuffer(e.slice().buffer,s);n.appendBuffer(t),s+=e.byteLength}if(n.flush(),o)throw o;if(!r)throw Error(`Could not parse MP4 metadata.`);return r}async function w({assetId:r,cwd:i,format:a,mediaType:o,projectId:s,sourceDigest:c,sourcePath:l}){let u=`projects/${s}/assets/${r}.${c.slice(0,12)}.${a}`,d=t(i,u);await e.mkdir(n(d),{recursive:!0}),await e.copyFile(l,d);let{size:f}=await e.stat(d);return{format:a,hash:c,mediaType:o,path:u,size:f}}export{d as assetKindFor,w as buildPassthroughAsset,x as inspectAudio,p as inspectLottie,v as inspectSvg,S as inspectVideo,f as mediaTypeForAsset};
@@ -0,0 +1,35 @@
1
+ import { AssetCatalog, AssetCatalogEntry, AssetFile, AssetManifestEntry, AssetProblem, AssetVariant, LoadedAssetCatalog, LoadedAssetManifest } from "./contracts.js";
2
+ import { AssetsConfig } from "@coreframe/config";
3
+
4
+ //#region src/project.d.ts
5
+ declare function readAssetCatalog(cwd?: string, {
6
+ allowMissing,
7
+ migrate
8
+ }?: {
9
+ allowMissing?: boolean;
10
+ migrate?: boolean;
11
+ }): Promise<LoadedAssetCatalog>;
12
+ declare function readAssetManifest(cwd?: string, {
13
+ allowMissing,
14
+ pack
15
+ }?: {
16
+ allowMissing?: boolean;
17
+ pack?: string;
18
+ }): Promise<LoadedAssetManifest>;
19
+ declare function assetManifestPath(pack?: string): string;
20
+ declare function assetCatalogEntries(catalog: AssetCatalog): Array<{
21
+ assetId: string;
22
+ entry: AssetCatalogEntry;
23
+ pack?: string;
24
+ }>;
25
+ declare function assetCatalogPackNames(catalog: AssetCatalog): string[];
26
+ declare function findCatalogAsset(catalog: AssetCatalog, assetId: string): {
27
+ entry: AssetCatalogEntry;
28
+ pack?: string;
29
+ } | undefined;
30
+ declare function listAssetManifestPacks(cwd?: string): Promise<string[]>;
31
+ declare function resolveAssetVariantFile(cwd: string, variantPath: string): string;
32
+ declare function manifestEntryOutputs(entry: AssetManifestEntry): Array<AssetVariant | AssetFile>;
33
+ declare function formatAssetProblems(problems: AssetProblem[]): string;
34
+ //#endregion
35
+ export { assetCatalogEntries, assetCatalogPackNames, assetManifestPath, findCatalogAsset, formatAssetProblems, listAssetManifestPacks, manifestEntryOutputs, readAssetCatalog, readAssetManifest, resolveAssetVariantFile };
@@ -0,0 +1,3 @@
1
+ import{fs as e,process as t}from"./environment.js";import{randomUUID as n}from"node:crypto";import{basename as r,dirname as i,extname as a,join as o,resolve as s}from"node:path";import{readProjectConfig as c}from"@coreframe/config";import l from"nano-spawn";const u=`assets`,d=o(u,`catalog.json`),f=o(u,`manifest.json`),p=o(u,`manifests`),m=/^[a-z0-9]+(?:[/-][a-z0-9]+)*$/,h=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,g=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,ee=/@asset\s+([a-z0-9][a-z0-9/-]*)/g,_=/^[0-9a-f]{64}$/;async function te(e=t.cwd(),{allowMissing:n=!1,migrate:r=!1}={}){let i=await A(e,d,`catalog`,n);if(i.missing&&n)return{catalog:J(),problems:[]};if(i.problem)return{catalog:J(),problems:[i.problem]};if(j(i.value))return{catalog:i.value,problems:[]};if(M(i.value)){let t=N(i.value);return t.problems.length?{catalog:J(),problems:t.problems,migrationRequired:!0}:r?(await X(e,t.catalog),{catalog:t.catalog,migrated:!0,problems:[]}):{catalog:t.catalog,migrationRequired:!0,problems:[{code:`SCHEMA_MIGRATION_REQUIRED`,message:`${d} uses asset schema version 1.`,file:d,fix:`Run: coreframe assets build`}]}}return{catalog:J(),problems:[{code:`INVALID_CATALOG`,message:`${d} has an unsupported shape or schema version.`,file:d}]}}async function ne(e=t.cwd(),{allowMissing:n=!1,pack:r}={}){r!==void 0&&$(r);let i=v(r),a=await A(e,i,`manifest`,n);return a.missing&&n?{manifest:Y(r),problems:[]}:a.problem?{manifest:Y(r),problems:[a.problem]}:P(a.value)&&a.value.pack===r?{manifest:a.value,problems:[]}:F(a.value)&&a.value.schemaVersion===1?{manifest:Y(r),problems:[{code:`SCHEMA_MIGRATION_REQUIRED`,message:`${i} uses asset schema version 1.`,file:i,fix:`Run: coreframe assets build`}]}:{manifest:Y(r),problems:[{code:`INVALID_MANIFEST`,message:`${i} has an unsupported shape, pack identity, or schema version.`,file:i}]}}function v(e){return e===void 0?f:o(p,`${e}.json`)}function y(e){return[...Object.entries(e.assets).map(([e,t])=>({assetId:e,entry:t})),...Object.entries(e.packs??{}).flatMap(([e,t])=>Object.entries(t.assets).map(([t,n])=>({assetId:t,entry:n,pack:e})))]}function b(e){return Object.keys(e.packs??{}).sort()}function x(e,t){return y(e).find(e=>e.assetId===t)}function re(e,t,n,r){r!==void 0&&$(r),delete e.assets[t];for(let n of Object.values(e.packs??{}))delete n.assets[t];if(r===void 0){e.assets[t]=n;return}e.packs??={},e.packs[r]??={assets:{}},e.packs[r].assets[t]=n}async function S(n=t.cwd()){try{return(await e.readdir(s(n,p),{withFileTypes:!0})).filter(e=>e.isFile()&&a(e.name)===`.json`).map(e=>r(e.name,`.json`)).filter(e=>h.test(e)).sort()}catch(e){if(e.code===`ENOENT`)return[];throw e}}async function C(t,n){$(n),await e.rm(s(t,v(n)),{force:!0})}function w(e,t){if(!O(t,k(t)))throw Error(`Invalid generated asset path: ${t}`);return s(e,`.coreframe/assets`,t)}async function ie(e,t){let n=t??(await c({cwd:e})).assets;if(!n)throw Error(`Static assets are not configured in coreframe.config.ts.`);for(let[e,t]of Object.entries(n))if(typeof t!=`string`||!t.trim())throw Error(`Static asset configuration "${e}" must be a non-empty string.`);if(!g.test(n.projectId))throw Error(`Static asset projectId must use lowercase hyphen-separated segments.`);for(let e of[`endpoint`,`publicBaseUrl`])if(new URL(n[e]).protocol!==`https:`)throw Error(`Static asset ${e} must use HTTPS.`);return{...n,endpoint:n.endpoint.replace(/\/+$/,``),publicBaseUrl:n.publicBaseUrl.replace(/\/+$/,``)}}function T(e,t){let{source:n}=t;if(!n||typeof n.filename!=`string`||!n.filename||typeof n.mediaType!=`string`||!n.mediaType||!Number.isSafeInteger(n.size)||n.size<=0||!_.test(n.digest)||!K(t.kind).has(n.mediaType))return{code:`INVALID_SOURCE`,message:`Asset "${e}" has invalid immutable source metadata for kind "${t.kind}".`,assetId:e,file:d}}function E(e,t){return e.kind!==t.kind||e.sourceDigest!==t.source.digest?!1:e.kind===`image`&&t.kind===`image`||e.kind===`svg`&&t.kind===`svg`?e.alt===t.alt:e.kind===`audio`||e.kind===`video`||e.kind===`lottie`}function D(e){return e.kind===`image`?e.variants:[e.file]}function O(e,t){let n=e.split(`/`);return!!(t&&e.startsWith(`${t}/`)&&n.length>=4&&!e.includes(`\\`)&&n.every(e=>e&&e!==`.`&&e!==`..`))}function k(e){let[t,n,r]=e.split(`/`);return t===`projects`&&g.test(n??``)&&r===`assets`?`projects/${n}/assets`:void 0}async function ae(t){let n=[];for(let r of await oe(t)){let i=s(t,r);if(!await le(i))continue;let a=await e.readFile(i,`utf8`);for(let e of a.matchAll(ee))n.push({assetId:e[1],file:r})}return n}async function oe(e){try{return(await l(`git`,[`ls-files`,`*.md`,`*.mdx`,`*.ts`,`*.tsx`],{cwd:e})).stdout.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.startsWith(`content/`)||e.startsWith(`src/`))}catch{return[]}}async function A(t,n,r,i){try{return{value:JSON.parse(await e.readFile(s(t,n),`utf8`)),missing:!1}}catch(e){return e.code===`ENOENT`?{value:void 0,missing:!0,problem:i?void 0:{code:r===`catalog`?`CATALOG_MISSING`:`MANIFEST_MISSING`,message:`${n} does not exist.`,file:n,fix:r===`catalog`?`Run: coreframe assets add <file> --id <asset-id> --alt "..."`:`Run: coreframe assets build`}}:{value:void 0,missing:!1,problem:{code:r===`catalog`?`INVALID_CATALOG`:`INVALID_MANIFEST`,message:`${n} is not valid JSON: ${e.message}`,file:n}}}}function j(e){if(!(F(e)&&q(e,[`schemaVersion`,`assets`,`packs`])&&e.schemaVersion===2&&F(e.assets)&&Object.values(e.assets).every(L)&&(e.packs===void 0||F(e.packs)&&Object.entries(e.packs).every(([e,t])=>h.test(e)&&F(t)&&q(t,[`assets`])&&F(t.assets)&&Object.values(t.assets).every(L)))))return!1;let t=y(e).map(({assetId:e})=>e);return new Set(t).size===t.length}function M(e){return!!(F(e)&&q(e,[`schemaVersion`,`assets`])&&e.schemaVersion===1&&F(e.assets)&&Object.values(e.assets).every(I))}function N(e){let t=J(),n=[];for(let[r,i]of Object.entries(e.assets)){if(i.source.mediaType===`image/svg+xml`){if(i.variantWidths){n.push({code:`INVALID_VARIANT_WIDTHS`,message:`SVG asset "${r}" cannot migrate with raster variantWidths.`,assetId:r,file:d,fix:`Remove variantWidths from "${r}", then run: coreframe assets build`});continue}t.assets[r]={kind:`svg`,source:i.source,alt:i.alt};continue}t.assets[r]={kind:`image`,source:i.source,alt:i.alt,...i.variantWidths?{variantWidths:i.variantWidths}:{}}}return{catalog:t,problems:n}}function P(e){return!!(F(e)&&q(e,[`schemaVersion`,`pack`,`assets`])&&e.schemaVersion===2&&(e.pack===void 0||typeof e.pack==`string`&&h.test(e.pack))&&F(e.assets)&&Object.values(e.assets).every(R))}function F(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function I(e){return!!(F(e)&&q(e,[`source`,`alt`,`variantWidths`])&&z(e.source)&&typeof e.alt==`string`&&G(e.variantWidths))}function L(e){if(!F(e)||!z(e.source))return!1;switch(e.kind){case`image`:return q(e,[`kind`,`source`,`alt`,`variantWidths`])&&typeof e.alt==`string`&&G(e.variantWidths);case`svg`:return q(e,[`kind`,`source`,`alt`])&&typeof e.alt==`string`;case`audio`:case`video`:case`lottie`:return q(e,[`kind`,`source`]);default:return!1}}function R(e){if(!F(e)||typeof e.sourceDigest!=`string`||!_.test(e.sourceDigest))return!1;switch(e.kind){case`image`:return q(e,[`kind`,`sourceDigest`,`alt`,`width`,`height`,`variants`])&&typeof e.alt==`string`&&W(e.width)&&W(e.height)&&Array.isArray(e.variants)&&e.variants.every(B);case`svg`:return q(e,[`kind`,`sourceDigest`,`alt`,`viewBox`,`file`])&&typeof e.alt==`string`&&U(e.viewBox)&&V(e.file,`svg`,`image/svg+xml`);case`audio`:return q(e,[`kind`,`sourceDigest`,`durationSeconds`,`file`])&&W(e.durationSeconds)&&V(e.file,`mp3`,`audio/mpeg`);case`video`:return q(e,[`kind`,`sourceDigest`,`width`,`height`,`durationSeconds`,`hasAudio`,`file`])&&W(e.width)&&W(e.height)&&W(e.durationSeconds)&&typeof e.hasAudio==`boolean`&&V(e.file,`mp4`,`video/mp4`);case`lottie`:return q(e,[`kind`,`sourceDigest`,`width`,`height`,`frameRate`,`durationSeconds`,`file`])&&W(e.width)&&W(e.height)&&W(e.frameRate)&&W(e.durationSeconds)&&V(e.file,`json`,`application/json`);default:return!1}}function z(e){return!!(F(e)&&q(e,[`digest`,`filename`,`mediaType`,`size`])&&typeof e.digest==`string`&&typeof e.filename==`string`&&typeof e.mediaType==`string`&&typeof e.size==`number`)}function B(e){return!!(F(e)&&q(e,[`format`,`mediaType`,`width`,`height`,`path`,`size`,`hash`])&&e.format===`webp`&&e.mediaType===`image/webp`&&W(e.width)&&W(e.height)&&H(e))}function V(e,t,n){return!!(F(e)&&q(e,[`format`,`mediaType`,`path`,`size`,`hash`])&&e.format===t&&e.mediaType===n&&H(e))}function H(e){return typeof e.path==`string`&&Number.isSafeInteger(e.size)&&e.size>0&&typeof e.hash==`string`&&_.test(e.hash)}function U(e){return!!(F(e)&&q(e,[`minX`,`minY`,`width`,`height`])&&typeof e.minX==`number`&&typeof e.minY==`number`&&W(e.width)&&W(e.height))}function W(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function G(e){return e===void 0||Array.isArray(e)&&e.every(e=>typeof e==`number`)}function K(e){switch(e){case`image`:return new Set([`image/avif`,`image/gif`,`image/jpeg`,`image/png`,`image/tiff`,`image/webp`]);case`svg`:return new Set([`image/svg+xml`]);case`audio`:return new Set([`audio/mpeg`]);case`video`:return new Set([`video/mp4`]);case`lottie`:return new Set([`application/json`])}}function q(e,t){let n=new Set(t);return Object.keys(e).every(e=>n.has(e))}function J(){return{schemaVersion:2,assets:{}}}function Y(e){return{schemaVersion:2,...e===void 0?{}:{pack:e},assets:{}}}async function X(e,t){let n=Object.fromEntries(Object.entries(t.packs??{}).filter(([,e])=>Object.keys(e.assets).length>0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>[e,{assets:Q(t.assets)}]));await Z(e,d,{...t,assets:Q(t.assets),...Object.keys(n).length>0?{packs:n}:{packs:void 0}})}async function se(e,t,n){n!==void 0&&$(n),await Z(e,v(n),{...t,...n===void 0?{pack:void 0}:{pack:n},assets:Q(t.assets)})}async function Z(r,a,o){let c=s(r,a),l=`${c}.${t.pid}.${n()}.tmp`;await e.mkdir(i(c),{recursive:!0});try{await e.writeFile(l,`${JSON.stringify(o,null,2)}\n`,{flag:`wx`}),await e.rename(l,c)}finally{await e.rm(l,{force:!0})}}function Q(e){return Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t)))}function ce(e){if(!m.test(e))throw Error(`Asset ID "${e}" must use lowercase path segments.`)}function $(e){if(!h.test(e))throw Error(`Asset pack "${e}" must use lowercase hyphen-separated segments.`)}async function le(t){try{return await e.stat(t),!0}catch{return!1}}function ue(e){return e.map(e=>[`${e.code}: ${e.message}`,e.file?` file: ${e.file}`:void 0,e.fix?` fix: ${e.fix}`:void 0].filter(Boolean).join(`
2
+ `)).join(`
3
+ `)}export{ce as assertAssetId,$ as assertAssetPackName,y as assetCatalogEntries,b as assetCatalogPackNames,m as assetIdPattern,v as assetManifestPath,h as assetPackPattern,d as catalogPath,ae as findAssetReferences,x as findCatalogAsset,ue as formatAssetProblems,O as isAssetVariantPath,S as listAssetManifestPacks,E as manifestEntryMatchesCatalog,D as manifestEntryOutputs,f as manifestPath,p as manifestsDir,te as readAssetCatalog,ne as readAssetManifest,C as removeAssetManifest,w as resolveAssetVariantFile,ie as resolveAssetsConfig,re as setCatalogAsset,T as validateSource,X as writeCatalog,se as writeManifest};
@@ -0,0 +1,7 @@
1
+ import { AssetsConfig } from "@coreframe/config";
2
+ import { PrivateObjectStore } from "@coreframe/object-storage";
3
+
4
+ //#region src/storage.d.ts
5
+ type AssetObjectStore = PrivateObjectStore;
6
+ //#endregion
7
+ export { AssetObjectStore };
@@ -0,0 +1 @@
1
+ import{fs as e,os as t,process as n}from"./environment.js";import{createHash as r,randomUUID as i}from"node:crypto";import{dirname as a,join as o,resolve as s}from"node:path";import{createB2PrivateObjectStore as c}from"@coreframe/object-storage";function l(e){return x(e),`sources/v1/sha256/${e.slice(0,2)}/${e}`}function u({config:e,credentialStore:t,env:r=n.env}){return c({config:{credentialProfile:e.credentialProfile,endpoint:e.endpoint,region:e.region},credentialStore:t,env:r})}function d(e=n.env){return e.COREFRAME_ASSETS_CACHE_DIR?s(e.COREFRAME_ASSETS_CACHE_DIR):t.platform()===`darwin`?o(t.homedir(),`Library`,`Caches`,`coreframe`,`assets`):t.platform()===`win32`?o(e.LOCALAPPDATA??o(t.homedir(),`AppData`,`Local`),`Coreframe`,`assets`):o(e.XDG_CACHE_HOME??o(t.homedir(),`.cache`),`coreframe`,`assets`)}function f(e,t){return x(t),o(e,`v1`,`sha256`,t.slice(0,2),t)}async function p({cacheRoot:t,digest:r,env:o=n.env,source:s}){if(await C(s)!==r)throw Error(`Asset source does not match declared SHA-256 digest ${r}.`);let c=f(t,r);return await h(c,async()=>{if(await g(c,r)){await _(c);return}await e.mkdir(a(c),{recursive:!0});let t=`${c}.${n.pid}.${i()}.tmp`;try{if(await e.copyFile(s,t),await C(t)!==r)throw Error(`Asset source changed while it was being cached.`);await e.rename(t,c)}finally{await e.rm(t,{force:!0})}}),await v(t,b(o),c),c}async function m({bucket:t,cacheRoot:r,digest:o,env:s=n.env,store:c}){let u=f(r,o);if(await g(u,o))return await _(u),{fetched:!1,path:u};let d=!1;return await h(u,async()=>{if(await g(u,o)){await _(u);return}await e.mkdir(a(u),{recursive:!0});let r=`${u}.${n.pid}.${i()}.tmp`;try{if(await c.download(t,l(o),r),await C(r)!==o)throw Error(`Asset source does not match declared SHA-256 digest ${o}.`);await e.rename(r,u),d=!0}finally{await e.rm(r,{force:!0})}}),await v(r,b(s),u),{fetched:d,path:u}}async function h(t,n){await e.mkdir(a(t),{recursive:!0});let r=`${t}.lock`,i=Date.now();for(;;){let t;try{t=await e.open(r,`wx`)}catch(t){if(t.code!==`EEXIST`)throw t;let n=await e.stat(r).catch(()=>void 0);if(n&&Date.now()-n.mtimeMs>12e4){await e.rm(r,{force:!0});continue}if(Date.now()-i>=3e4)throw Error(`Timed out waiting for the asset cache lock at ${r}.`);await new Promise(e=>setTimeout(e,50));continue}try{await n()}finally{await t.close(),await e.rm(r,{force:!0})}return}}async function g(e,t){try{return await C(e)===t}catch{return!1}}async function _(t){let n=new Date;await e.utimes(t,n,n)}async function v(t,n,r){let i=await y(t),a=i.reduce((e,t)=>e+t.size,0);for(let t of i.sort((e,t)=>e.mtimeMs-t.mtimeMs)){if(a<=n)break;t.path!==r&&(await e.rm(t.path,{force:!0}),a-=t.size)}}async function y(t){let n=[],r=async t=>{let i=await e.readdir(t,{withFileTypes:!0}).catch(()=>[]);for(let a of i){let i=o(t,a.name);if(a.isDirectory())await r(i);else if(!a.name.endsWith(`.lock`)&&!a.name.endsWith(`.tmp`)){let t=await e.stat(i);n.push({path:i,size:t.size,mtimeMs:t.mtimeMs})}}};return await r(t),n}function b(e){let t=Number(e.COREFRAME_ASSETS_CACHE_MAX_BYTES);return Number.isSafeInteger(t)&&t>0?t:5368709120}function x(e){if(!/^[0-9a-f]{64}$/.test(e))throw Error(`Invalid SHA-256 asset digest: ${e}`)}function S(e){return r(`sha256`).update(e).digest(`hex`)}async function C(t){let n=r(`sha256`);for await(let r of e.createReadStream(t))n.update(r);return n.digest(`hex`)}export{f as assetCacheFile,d as assetCacheRoot,l as assetSourceKey,p as cacheAssetSourceFile,u as createB2AssetStore,m as materializeAssetSource,S as sha256,C as sha256File};
@@ -0,0 +1,69 @@
1
+ import { AssetBuildResult, AssetProblem, AssetPublishResult, AssetStatus } from "./contracts.js";
2
+ import { AssetObjectStore } from "./storage.js";
3
+ import { AssetsConfig } from "@coreframe/config";
4
+
5
+ //#region src/workflows.d.ts
6
+ type AssetWorkflowOptions = {
7
+ cacheDir?: string;
8
+ config?: AssetsConfig;
9
+ cwd?: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ store?: AssetObjectStore;
12
+ };
13
+ type AddAssetOptions = AssetWorkflowOptions & {
14
+ file: string;
15
+ id: string;
16
+ alt?: string;
17
+ pack?: string;
18
+ variantWidths?: number[];
19
+ };
20
+ type BuildAssetsOptions = AssetWorkflowOptions & {
21
+ id?: string;
22
+ pack?: string;
23
+ updateManifest?: boolean;
24
+ };
25
+ type CheckAssetsOptions = AssetWorkflowOptions & {
26
+ fix?: boolean;
27
+ pack?: string;
28
+ };
29
+ type CheckAssetManifestsOptions = AssetWorkflowOptions & {
30
+ pack?: string;
31
+ };
32
+ type PublishAssetsOptions = AssetWorkflowOptions & {
33
+ pack?: string;
34
+ };
35
+ type AssetManifestStatus = {
36
+ ok: boolean;
37
+ problems: AssetProblem[];
38
+ requiresFullBuild: boolean;
39
+ staleAssetIds: string[];
40
+ };
41
+ declare function addAsset({
42
+ alt,
43
+ cacheDir,
44
+ config,
45
+ cwd,
46
+ env,
47
+ file,
48
+ id,
49
+ pack,
50
+ store,
51
+ variantWidths
52
+ }: AddAssetOptions): Promise<AssetBuildResult>;
53
+ declare function buildAssets({
54
+ cacheDir,
55
+ config,
56
+ cwd,
57
+ env,
58
+ id,
59
+ pack,
60
+ store,
61
+ updateManifest
62
+ }?: BuildAssetsOptions): Promise<AssetBuildResult>;
63
+ declare function checkAssetManifests(options?: CheckAssetManifestsOptions): Promise<AssetManifestStatus>;
64
+ declare function checkAssets(options?: CheckAssetsOptions): Promise<AssetStatus>;
65
+ declare function publishAssets(options?: PublishAssetsOptions): Promise<AssetPublishResult>;
66
+ declare function materializeAsset(assetId: string, options?: BuildAssetsOptions): Promise<AssetBuildResult>;
67
+ declare function printAssetResult(result: unknown, json?: boolean): void;
68
+ //#endregion
69
+ export { AddAssetOptions, AssetManifestStatus, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetManifestsOptions, CheckAssetsOptions, PublishAssetsOptions, addAsset, buildAssets, checkAssetManifests, checkAssets, materializeAsset, printAssetResult, publishAssets };
@@ -0,0 +1 @@
1
+ import{fs as e,process as t}from"./environment.js";import{assertAssetId as n,assertAssetPackName as r,assetCatalogEntries as i,assetCatalogPackNames as a,assetIdPattern as o,assetManifestPath as s,catalogPath as c,findAssetReferences as l,findCatalogAsset as u,formatAssetProblems as d,isAssetVariantPath as f,listAssetManifestPacks as p,manifestEntryMatchesCatalog as m,manifestEntryOutputs as h,readAssetCatalog as g,readAssetManifest as _,removeAssetManifest as v,resolveAssetVariantFile as y,resolveAssetsConfig as b,setCatalogAsset as x,validateSource as S,writeCatalog as C,writeManifest as w}from"./project.js";import{assetCacheRoot as T,assetSourceKey as E,cacheAssetSourceFile as D,createB2AssetStore as O,materializeAssetSource as k,sha256File as A}from"./storage.js";import{assertVariantWidths as j,assetVariantWidths as M,buildAssetVariants as N,mediaTypeFor as P,readImageDimensions as F,variantWidthsProblem as I}from"./images.js";import{assetKindFor as L,buildPassthroughAsset as R,inspectAudio as z,inspectLottie as B,inspectSvg as V,inspectVideo as H,mediaTypeForAsset as U}from"./media.js";import{basename as W,dirname as G,join as K,relative as q,resolve as J,sep as Y}from"node:path";const X=K(`.coreframe/assets`,`asset-publish-report.json`);async function ee({alt:i,cacheDir:a,config:o,cwd:s=t.cwd(),env:c=t.env,file:l,id:u,pack:d,store:f,variantWidths:p}){n(u),d!==void 0&&r(d);let m=J(s,l),h=L(m),_=i?.trim();if((h===`image`||h===`svg`)&&!_)throw Error(`Raster images and SVG assets require non-empty alt text.`);if(h!==`image`&&p)throw Error(`Asset kind "${h}" does not support responsive variant widths.`);await ne(h,m,p);let v=await g(s,{allowMissing:!0,migrate:!0});if(v.problems.length>0)return{ok:!1,assets:[],fetched:[],problems:v.problems};let y=await b(s,o),S=f??O({config:y,env:c}),w=await A(m),k={digest:w,filename:W(l),mediaType:h===`image`?P(l):U(l,h),size:(await e.stat(m)).size},j=a??T(c),M=await D({cacheRoot:j,digest:w,env:c,source:m}),N=E(w);if(!await S.has(y.sourceBucket,N)&&(await S.upload(y.sourceBucket,N,M,k.mediaType),!await S.has(y.sourceBucket,N)))throw Error(`Backblaze B2 did not confirm source upload ${N}.`);return x(v.catalog,u,te({alt:_,kind:h,source:k,variantWidths:p}),d),await C(s,v.catalog),Z({cacheDir:j,config:y,cwd:s,env:c,id:v.migrated?void 0:u,pack:v.migrated?void 0:d,store:S})}function te({alt:e,kind:t,source:n,variantWidths:r}){return t===`image`?{kind:t,source:n,alt:e,...r?{variantWidths:r}:{}}:t===`svg`?{kind:t,source:n,alt:e}:{kind:t,source:n}}async function ne(e,t,n){if(e===`image`){j(n,(await F(t)).width),P(t);return}if(e===`svg`){await V(t);return}if(e===`audio`){await z(t);return}if(e===`video`){await H(t);return}await B(t)}async function Z({cacheDir:e,config:n,cwd:o=t.cwd(),env:s=t.env,id:l,pack:d,store:f,updateManifest:m=!0}={}){d!==void 0&&r(d);let y=await g(o,{migrate:!0}),x=[...y.problems],C=[],D=[];if(x.length>0)return{ok:!1,assets:C,fetched:D,problems:x};y.migrated&&(l=void 0,d=void 0);let A=i(y.catalog),j=l?u(y.catalog,l):void 0;if(l&&!j)return x.push({code:`REFERENCE_MISSING`,message:`Asset "${l}" is not present in ${c}.`,assetId:l,fix:`Run: coreframe assets add <file> --id ${l}`}),{ok:!1,assets:C,fetched:D,problems:x};if(l&&d!==void 0&&j?.pack!==d)return x.push({code:`REFERENCE_MISSING`,message:`Asset "${l}" is not present in asset pack "${d}".`,assetId:l,fix:`Build the asset without --pack or use its owning pack.`}),{ok:!1,assets:C,fetched:D,problems:x};let M=l?j?.pack:d;if(!l&&d!==void 0&&!y.catalog.packs?.[d])return x.push({code:`REFERENCE_MISSING`,message:`Asset pack "${d}" is not present in ${c}.`,fix:`Add an asset with --pack ${d}.`}),{ok:!1,assets:C,fetched:D,problems:x};let N=A.filter(e=>(l===void 0||e.assetId===l)&&(M===void 0?d===void 0||e.pack===void 0:e.pack===M)),P=l!==void 0||d!==void 0?[M]:[void 0,...a(y.catalog)],F=await b(o,n),I=f??O({config:F,env:s}),L=e??T(s),R=new Map;for(let e of P)if(l&&m){let t=await _(o,{allowMissing:!0,pack:e});if(t.problems.length>0)return{ok:!1,assets:C,fetched:D,problems:t.problems};R.set(e,t.manifest)}else R.set(e,re(e));for(let{assetId:e,entry:t,pack:n}of N){let r=S(e,t);if(r){x.push(r);continue}let i;try{i=await k({bucket:F.sourceBucket,cacheRoot:L,digest:t.source.digest,env:s,store:I})}catch(t){x.push({code:`SOURCE_UNAVAILABLE`,message:`Asset "${e}" source could not be materialized: ${t.message}`,assetId:e,fix:`Check the B2 asset configuration and credential profile "${F.credentialProfile}".`});continue}i.fetched&&D.push(E(t.source.digest));try{let r=await ie({assetId:e,config:F,cwd:o,entry:t,sourcePath:i.path});R.get(n).assets[e]=r,C.push({assetId:e,outputs:h(r)})}catch(n){x.push({code:`INVALID_MEDIA`,message:`Asset "${e}" could not be built: ${n.message}`,assetId:e,file:c,fix:`Replace the source with a supported ${t.kind} file, then run: coreframe assets build --id ${e}`})}}if(m&&x.length===0){for(let[e,t]of R)await w(o,t,e);if(l===void 0&&d===void 0){let e=new Set(a(y.catalog));for(let t of await p(o))e.has(t)||await v(o,t)}}return{ok:x.length===0,assets:C,fetched:D,problems:x}}function re(e){return{schemaVersion:2,...e===void 0?{}:{pack:e},assets:{}}}async function ie({assetId:e,config:t,cwd:n,entry:r,sourcePath:i}){if(r.kind===`image`){let a=await F(i),o=I(e,r,a.width);if(o)throw Error(o.message);let s=await N({assetId:e,config:t,cwd:n,entry:r,sourcePath:i,sourceDimensions:a});return{kind:`image`,sourceDigest:r.source.digest,alt:r.alt,...a,variants:s}}let a=await R({assetId:e,cwd:n,format:r.kind===`svg`?`svg`:r.kind===`audio`?`mp3`:r.kind===`video`?`mp4`:`json`,mediaType:r.source.mediaType,projectId:t.projectId,sourceDigest:r.source.digest,sourcePath:i});return r.kind===`svg`?{kind:`svg`,sourceDigest:r.source.digest,alt:r.alt,...await V(i),file:a}:r.kind===`audio`?{kind:`audio`,sourceDigest:r.source.digest,...await z(i),file:a}:r.kind===`video`?{kind:`video`,sourceDigest:r.source.digest,...await H(i),file:a}:{kind:`lottie`,sourceDigest:r.source.digest,...await B(i),file:a}}async function Q(e={}){let{cwd:n=t.cwd(),pack:o}=e;o!==void 0&&r(o);let l=await g(n),u=[...l.problems],d=new Set,v=u.length>0;if(u.some(e=>e.code===`SCHEMA_MIGRATION_REQUIRED`))return{ok:!1,catalog:l.catalog,expectedPrefix:``,manifests:new Map,problems:u,requiresFullBuild:!0,selectedEntries:[],staleAssetIds:[]};if(o!==void 0&&!l.catalog.packs?.[o])return u.push({code:`REFERENCE_MISSING`,message:`Asset pack "${o}" is not present in ${c}.`,fix:`Add an asset with --pack ${o}.`}),{ok:!1,catalog:l.catalog,expectedPrefix:``,manifests:new Map,problems:u,requiresFullBuild:!0,selectedEntries:[],staleAssetIds:[]};let y=o===void 0?[void 0,...a(l.catalog)]:[o],x=new Map;for(let e of y){let t=await _(n,{pack:e});u.push(...t.problems),v||=t.problems.length>0,x.set(e,t.manifest)}if(o===void 0){let e=new Set(a(l.catalog));for(let t of await p(n))e.has(t)||(u.push({code:`MANIFEST_STALE`,message:`Generated asset pack manifest "${t}" is not present in the catalog.`,file:s(t),fix:`Run: coreframe assets build`}),v=!0)}let S=`projects/${(await b(n,e.config)).projectId}/assets`,C=i(l.catalog).filter(e=>o===void 0||e.pack===o);for(let{assetId:e,entry:t,pack:n}of C){let r=s(n),i=x.get(n)?.assets[e];if(!i||!m(i,t)){u.push({code:`MANIFEST_STALE`,message:`Asset "${e}" is missing from the generated manifest or does not match its catalog entry.`,assetId:e,file:r,fix:`Run: coreframe assets build --id ${e}`}),d.add(e);continue}if(t.kind===`image`&&i.kind===`image`){let n=I(e,t,i.width);if(n)u.push(n),d.add(e);else{let n=M(t,i.width),a=i.variants.map(e=>e.width);(n.length!==a.length||!n.every((e,t)=>e===a[t]))&&(u.push({code:`MANIFEST_STALE`,message:`Asset "${e}" generated variants do not match its catalog widths.`,assetId:e,file:r,fix:`Run: coreframe assets build --id ${e}`}),d.add(e))}}for(let t of h(i))f(t.path,S)||(u.push({code:`INVALID_VARIANT_PATH`,message:`Asset "${e}" has an invalid generated output path.`,assetId:e,file:t.path,fix:`Run: coreframe assets build --id ${e}`}),d.add(e))}for(let[e,t]of x){let n=e===void 0?l.catalog.assets:l.catalog.packs?.[e]?.assets;for(let r of Object.keys(t.assets))n?.[r]||(u.push({code:`MANIFEST_STALE`,message:`Generated manifest asset "${r}" is not present in its catalog collection.`,assetId:r,file:s(e),fix:`Run: coreframe assets build`}),v=!0)}return{ok:u.length===0,catalog:l.catalog,expectedPrefix:S,manifests:x,problems:u,requiresFullBuild:v,selectedEntries:C,staleAssetIds:[...d].sort()}}async function ae(e={}){let{ok:t,problems:n,requiresFullBuild:r,staleAssetIds:i}=await Q(e);return{ok:t,problems:n,requiresFullBuild:r,staleAssetIds:i}}async function $(n={}){let{cwd:i=t.cwd(),fix:a=!1,pack:s}=n;if(s!==void 0&&r(s),a){let e=await Z(n);if(!e.ok)return{ok:!1,assets:[],problems:e.problems}}let d=await Q(n),{catalog:p,expectedPrefix:g,manifests:_,selectedEntries:v}=d,b=[...d.problems],x=[];if(b.some(e=>e.code===`SCHEMA_MIGRATION_REQUIRED`)||s!==void 0&&!p.packs?.[s])return{ok:!1,assets:x,problems:b};let C=await l(i);for(let{assetId:t,entry:n,pack:r}of v){o.test(t)||b.push({code:`INVALID_ASSET_ID`,message:`Asset ID "${t}" must use lowercase path segments.`,assetId:t,file:c}),(n.kind===`image`||n.kind===`svg`)&&!n.alt.trim()&&b.push({code:`INVALID_ALT`,message:`Asset "${t}" is missing alt text.`,assetId:t,file:c});let a=S(t,n);a&&b.push(a);let s=_.get(r)?.assets[t];if(!s||!m(s,n))continue;let l=await Promise.all(h(s).map(async t=>{let n=f(t.path,g),r=!1;if(n)try{let n=y(i,t.path);r=(await e.stat(n)).size===t.size&&await A(n)===t.hash}catch{r=!1}return{...t,exists:r}}));l.every(e=>e.exists)||b.push({code:`GENERATED_ASSET_MISSING`,message:`Asset "${t}" is missing its expected generated output.`,assetId:t,fix:`Run: coreframe assets build --id ${t}`}),x.push({assetId:t,source:n.source,outputs:l})}for(let e of C){let t=u(p,e.assetId);t?t.entry.kind!==`image`&&t.entry.kind!==`svg`&&b.push({code:`INVALID_REFERENCE`,message:`Markdown image reference "${e.assetId}" cannot use asset kind "${t.entry.kind}".`,assetId:e.assetId,file:e.file}):b.push({code:`INVALID_REFERENCE`,message:`Asset reference "${e.assetId}" is not present in ${c}.`,assetId:e.assetId,file:e.file,fix:`Run: coreframe assets add <file> --id ${e.assetId}`})}return{ok:b.length===0,assets:x,problems:b}}async function oe(n={}){let{cwd:r=t.cwd(),env:i=t.env,pack:o}=n,s=[...(await $(n)).problems],c=[],l=[],u=J(r,X);if(s.length===0){let e=await b(r,n.config),t=n.store??O({config:e,env:i}),{catalog:s}=await g(r),u=o===void 0?[void 0,...a(s)]:[o];for(let n of u){let{manifest:i}=await _(r,{pack:n});for(let[n,a]of Object.entries(i.assets))for(let i of h(a)){let a=y(r,i.path),o={assetId:n,source:q(r,a).split(Y).join(`/`),target:`${e.publicBucket}/${i.path}`,size:i.size};if(await t.has(e.publicBucket,i.path)){l.push(o);continue}await t.upload(e.publicBucket,i.path,a,i.mediaType),c.push(o)}}}let d={ok:s.length===0,uploaded:c,skipped:l,reportPath:u,problems:s};return await e.mkdir(G(u),{recursive:!0}),await e.writeFile(u,`${JSON.stringify(d,null,2)}\n`),d}async function se(e,t={}){return Z({...t,id:e})}function ce(e,t=!1){if(t){console.log(JSON.stringify(e,null,2));return}let n=e;n.problems?.length&&console.error(d(n.problems)),console.log(n.ok===!1?`Asset workflow has problems.`:`Asset workflow complete.`)}export{ee as addAsset,Z as buildAssets,ae as checkAssetManifests,$ as checkAssets,se as materializeAsset,ce as printAssetResult,oe as publishAssets};
package/package.json CHANGED
@@ -1,13 +1,51 @@
1
1
  {
2
2
  "name": "@coreframe/assets",
3
- "version": "0.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "0.1.1",
4
+ "description": "Asset processing and object-storage workflows for Coreframe apps",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/airlock-labs/coreframe.git",
8
+ "directory": "node/assets"
9
+ },
10
+ "files": [
11
+ "dist/"
12
+ ],
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "mp4box": "^2.4.1",
22
+ "music-metadata": "^11.14.0",
23
+ "nano-spawn": "^2.1.0",
24
+ "saxes": "^6.0.0",
25
+ "@coreframe/config": "^0.1.0",
26
+ "@coreframe/lib": "^0.2.0",
27
+ "@coreframe/object-storage": "^0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^26.1.0",
31
+ "sharp": "^0.34.5",
32
+ "tsdown": "0.22.3",
33
+ "typescript": "6.0.3",
34
+ "vitest": "^4.1.9"
8
35
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "All rights reserved",
12
- "type": "commonjs"
13
- }
36
+ "peerDependencies": {
37
+ "sharp": "^0.34.5"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "sharp": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "scripts": {
45
+ "build": "tsdown",
46
+ "test": "vitest run --reporter=minimal",
47
+ "typecheck": "tsc --noEmit -p tsconfig.json",
48
+ "format": "oxlint --fix && oxfmt",
49
+ "lint": "oxlint"
50
+ }
51
+ }
package/readme.md DELETED
@@ -1 +0,0 @@
1
- Coming soon...