@coreframe/scripts 0.1.39 → 0.1.40

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.
@@ -1,15 +1,39 @@
1
1
  //#region assets/contracts.d.ts
2
- declare const catalogSchemaVersion: 1;
3
- declare const manifestSchemaVersion: 1;
2
+ declare const catalogSchemaVersion: 2;
3
+ declare const manifestSchemaVersion: 2;
4
+ type AssetKind = "image" | "svg" | "audio" | "video" | "lottie";
4
5
  type AssetCatalog = {
5
6
  schemaVersion: typeof catalogSchemaVersion;
6
7
  assets: Record<string, AssetCatalogEntry>;
8
+ packs?: Record<string, AssetCatalogPack>;
7
9
  };
8
- type AssetCatalogEntry = {
10
+ type AssetCatalogPack = {
11
+ assets: Record<string, AssetCatalogEntry>;
12
+ };
13
+ type AssetCatalogEntry = ImageCatalogEntry | SvgCatalogEntry | AudioCatalogEntry | VideoCatalogEntry | LottieCatalogEntry;
14
+ type ImageCatalogEntry = {
15
+ kind: "image";
9
16
  source: AssetSource;
10
17
  alt: string;
11
18
  variantWidths?: number[];
12
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
+ };
13
37
  type AssetSource = {
14
38
  digest: string;
15
39
  filename: string;
@@ -18,23 +42,96 @@ type AssetSource = {
18
42
  };
19
43
  type AssetManifest = {
20
44
  schemaVersion: typeof manifestSchemaVersion;
45
+ pack?: string;
21
46
  assets: Record<string, AssetManifestEntry>;
22
47
  };
23
- type AssetManifestEntry = AssetCatalogEntry & {
48
+ type AssetManifestEntry = ImageManifestEntry | SvgManifestEntry | AudioManifestEntry | VideoManifestEntry | LottieManifestEntry;
49
+ type ImageManifestEntry = {
50
+ kind: "image";
51
+ sourceDigest: string;
52
+ alt: string;
24
53
  width: number;
25
54
  height: number;
26
55
  variants: AssetVariant[];
27
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
+ };
28
94
  type AssetVariant = {
29
95
  format: "webp";
96
+ mediaType: "image/webp";
30
97
  width: number;
31
98
  height: number;
32
99
  path: string;
33
100
  size: number;
34
101
  hash: string;
35
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;
36
133
  type AssetProblem = {
37
- code: "CATALOG_MISSING" | "GENERATED_VARIANT_MISSING" | "INVALID_ALT" | "INVALID_ASSET_ID" | "INVALID_CATALOG" | "INVALID_MANIFEST" | "INVALID_REFERENCE" | "INVALID_SOURCE" | "INVALID_VARIANT_PATH" | "INVALID_VARIANT_WIDTHS" | "MANIFEST_MISSING" | "MANIFEST_STALE" | "REFERENCE_MISSING" | "SOURCE_UNAVAILABLE";
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";
38
135
  message: string;
39
136
  assetId?: string;
40
137
  file?: string;
@@ -48,9 +145,9 @@ type AssetStatus = {
48
145
  type AssetStatusEntry = {
49
146
  assetId: string;
50
147
  source: AssetSource;
51
- variants: AssetVariantStatus[];
148
+ outputs: AssetOutputStatus[];
52
149
  };
53
- type AssetVariantStatus = AssetVariant & {
150
+ type AssetOutputStatus = AssetOutput & {
54
151
  exists: boolean;
55
152
  };
56
153
  type AssetBuildResult = {
@@ -61,7 +158,7 @@ type AssetBuildResult = {
61
158
  };
62
159
  type AssetBuildEntry = {
63
160
  assetId: string;
64
- variants: AssetVariant[];
161
+ outputs: AssetOutput[];
65
162
  };
66
163
  type AssetPublishResult = {
67
164
  ok: boolean;
@@ -79,10 +176,12 @@ type PublishedAsset = {
79
176
  type LoadedAssetCatalog = {
80
177
  catalog: AssetCatalog;
81
178
  problems: AssetProblem[];
179
+ migrated?: boolean;
180
+ migrationRequired?: boolean;
82
181
  };
83
182
  type LoadedAssetManifest = {
84
183
  manifest: AssetManifest;
85
184
  problems: AssetProblem[];
86
185
  };
87
186
  //#endregion
88
- export { AssetBuildEntry, AssetBuildResult, AssetCatalog, AssetCatalogEntry, AssetManifest, AssetManifestEntry, AssetProblem, AssetPublishResult, AssetSource, AssetStatus, AssetStatusEntry, AssetVariant, AssetVariantStatus, LoadedAssetCatalog, LoadedAssetManifest, PublishedAsset, catalogSchemaVersion, manifestSchemaVersion };
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 };
@@ -1 +1 @@
1
- const e=1,t=1;export{e as catalogSchemaVersion,t as manifestSchemaVersion};
1
+ const e=2,t=2;export{e as catalogSchemaVersion,t as manifestSchemaVersion};
@@ -1,4 +1,4 @@
1
- import { AssetCatalogEntry, AssetProblem, AssetVariant } from "./contracts.js";
1
+ import { AssetProblem, AssetVariant, ImageCatalogEntry } from "./contracts.js";
2
2
  import { AssetsConfig } from "../config.js";
3
3
 
4
4
  //#region assets/images.d.ts
@@ -13,16 +13,16 @@ declare function buildAssetVariants({
13
13
  assetId: string;
14
14
  config: AssetsConfig;
15
15
  cwd: string;
16
- entry: AssetCatalogEntry;
16
+ entry: ImageCatalogEntry;
17
17
  sourceDimensions: {
18
18
  width: number;
19
19
  height: number;
20
20
  };
21
21
  sourcePath: string;
22
22
  }): Promise<AssetVariant[]>;
23
- declare function assetVariantWidths(entry: AssetCatalogEntry, sourceWidth: number): number[];
23
+ declare function assetVariantWidths(entry: ImageCatalogEntry, sourceWidth: number): number[];
24
24
  declare function assertVariantWidths(widths: number[] | undefined, sourceWidth: number): void;
25
- declare function variantWidthsProblem(assetId: string, entry: AssetCatalogEntry, sourceWidth: number): AssetProblem | undefined;
25
+ declare function variantWidthsProblem(assetId: string, entry: ImageCatalogEntry, sourceWidth: number): AssetProblem | undefined;
26
26
  declare function readImageDimensions(file: string): Promise<{
27
27
  width: number;
28
28
  height: number;
@@ -1 +1 @@
1
- import{catalogPath as e,resolveAssetVariantFile as t}from"./project.js";import{sha256 as n}from"./storage.js";import{mkdir as r,writeFile as i}from"node:fs/promises";import{dirname as a,extname as o}from"node:path";const s=[320,640,960,1280,1920,2560],c=s.at(-1);async function l({assetId:e,config:o,cwd:s,entry:c,sourceDimensions:l,sourcePath:d}){let f=await h();return Promise.all(u(c,l.width).map(async c=>{let u=await f(d).resize({width:c,withoutEnlargement:!0}).webp().toBuffer(),p=n(u),m=`projects/${o.projectId}/assets/${e}.${p.slice(0,12)}.${c}.webp`,h=t(s,m);await r(a(h),{recursive:!0}),await i(h,u);let g=await f(u).metadata();return{format:`webp`,width:g.width??c,height:g.height??Math.round(l.height/l.width*c),path:m,size:u.byteLength,hash:p}}))}function u(e,t){if(e.variantWidths)return e.variantWidths;let n=Math.min(Math.round(t),c);return[...s.filter(e=>e<=n),n].filter((e,t,n)=>t===0||e!==n[t-1])}function d(e,t){let n=p(e,t);if(n)throw Error(`Invalid variant widths: ${n}`)}function f(t,n,r){let i=p(n.variantWidths,r);return i?{code:`INVALID_VARIANT_WIDTHS`,message:`Asset "${t}" has invalid variantWidths: ${i}`,assetId:t,file:e,fix:`Edit ${e}, then run: coreframe assets build --id ${t}`}:void 0}function p(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 m(e){let t=await(await h())(e).metadata();if(!t.width||!t.height)throw Error(`Could not read dimensions for ${e}.`);return{width:t.width,height:t.height}}async function h(){try{return(await import(`sharp`)).default}catch{throw Error(`The assets workflow requires the optional sharp dependency.`)}}function g(e){let t={".avif":`image/avif`,".gif":`image/gif`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`,".tif":`image/tiff`,".tiff":`image/tiff`,".webp":`image/webp`}[o(e).toLowerCase()];if(!t)throw Error(`Unsupported source image type: ${o(e)||`unknown`}.`);return t}export{d as assertVariantWidths,u as assetVariantWidths,l as buildAssetVariants,g as mediaTypeFor,m as readImageDimensions,f as variantWidthsProblem};
1
+ import{catalogPath as e,resolveAssetVariantFile as t}from"./project.js";import{sha256 as n}from"./storage.js";import{mkdir as r,writeFile as i}from"node:fs/promises";import{dirname as a,extname as o}from"node:path";const s=[320,640,960,1280,1920,2560],c=s.at(-1);async function l({assetId:e,config:o,cwd:s,entry:c,sourceDimensions:l,sourcePath:d}){let f=await h();return Promise.all(u(c,l.width).map(async c=>{let u=await f(d).resize({width:c,withoutEnlargement:!0}).webp().toBuffer(),p=n(u),m=`projects/${o.projectId}/assets/${e}.${p.slice(0,12)}.${c}.webp`,h=t(s,m);await r(a(h),{recursive:!0}),await i(h,u);let g=await f(u).metadata();return{format:`webp`,mediaType:`image/webp`,width:g.width??c,height:g.height??Math.round(l.height/l.width*c),path:m,size:u.byteLength,hash:p}}))}function u(e,t){if(e.variantWidths)return e.variantWidths;let n=Math.min(Math.round(t),c);return[...s.filter(e=>e<=n),n].filter((e,t,n)=>t===0||e!==n[t-1])}function d(e,t){let n=p(e,t);if(n)throw Error(`Invalid variant widths: ${n}`)}function f(t,n,r){let i=p(n.variantWidths,r);return i?{code:`INVALID_VARIANT_WIDTHS`,message:`Asset "${t}" has invalid variantWidths: ${i}`,assetId:t,file:e,fix:`Edit ${e}, then run: coreframe assets build --id ${t}`}:void 0}function p(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 m(e){let t=await(await h())(e).metadata();if(!t.width||!t.height)throw Error(`Could not read dimensions for ${e}.`);return{width:t.width,height:t.height}}async function h(){try{return(await import(`sharp`)).default}catch{throw Error(`The assets workflow requires the optional sharp dependency.`)}}function g(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`}[o(e).toLowerCase()];if(!t)throw Error(`Unsupported raster image type: ${o(e)||`unknown`}.`);return t}export{d as assertVariantWidths,u as assetVariantWidths,l as buildAssetVariants,g as mediaTypeFor,m as readImageDimensions,f as variantWidthsProblem};
@@ -0,0 +1,42 @@
1
+ import { AssetFile, AssetKind, SvgViewBox } from "./contracts.js";
2
+
3
+ //#region assets/media.d.ts
4
+ declare function assetKindFor(file: string): AssetKind;
5
+ declare function mediaTypeForAsset(file: string, kind: AssetKind): string;
6
+ declare function inspectLottie(file: string): Promise<{
7
+ durationSeconds: number;
8
+ frameRate: number;
9
+ height: number;
10
+ width: number;
11
+ }>;
12
+ declare function inspectSvg(file: string): Promise<{
13
+ viewBox: SvgViewBox;
14
+ }>;
15
+ declare function inspectAudio(file: string): Promise<{
16
+ durationSeconds: number;
17
+ }>;
18
+ declare function inspectVideo(file: string): Promise<{
19
+ durationSeconds: number;
20
+ hasAudio: boolean;
21
+ height: number;
22
+ width: number;
23
+ }>;
24
+ declare function buildPassthroughAsset({
25
+ assetId,
26
+ cwd,
27
+ format,
28
+ mediaType,
29
+ projectId,
30
+ sourceDigest,
31
+ sourcePath
32
+ }: {
33
+ assetId: string;
34
+ cwd: string;
35
+ format: AssetFile["format"];
36
+ mediaType: AssetFile["mediaType"];
37
+ projectId: string;
38
+ sourceDigest: string;
39
+ sourcePath: string;
40
+ }): Promise<AssetFile>;
41
+ //#endregion
42
+ export { assetKindFor, buildPassthroughAsset, inspectAudio, inspectLottie, inspectSvg, inspectVideo, mediaTypeForAsset };
@@ -0,0 +1 @@
1
+ import{resolveAssetVariantFile as e}from"./project.js";import{copyFile as t,mkdir as n,readFile as r,stat as i}from"node:fs/promises";import{dirname as a,extname as o}from"node:path";import{createReadStream as s}from"node:fs";import{MP4BoxBuffer as c,createFile as l}from"mp4box";import{parseFile as u}from"music-metadata";import{SaxesParser as d}from"saxes";const f=new Set([`foreignobject`,`script`]),p=/\b(?:data|file|https?|javascript):|\/\//i,m=/url\(\s*["']?(?!#)/i;function h(e){switch(o(e).toLowerCase()){case`.svg`:return`svg`;case`.mp3`:return`audio`;case`.mp4`:return`video`;case`.json`:return`lottie`;default:return`image`}}function g(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 _(e){let t;try{t=JSON.parse(await r(e,`utf8`))}catch(e){throw Error(`Lottie source must be valid JSON: ${e.message}`)}if(!y(t))throw Error(`Lottie source must contain a JSON object.`);let{assets:n,fonts:i,fr:a,h:o,ip:s,layers:c,op:l,w:u}=t;if(!x(u)||!x(o))throw Error(`Lottie source must contain positive integer width and height values.`);if(!b(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(n!==void 0){if(!Array.isArray(n))throw Error(`Lottie assets must be an array.`);for(let e of n){if(!y(e))throw Error(`Lottie assets must contain JSON objects.`);if(`p`in e&&!v(e))throw Error(`Lottie image and data assets must be embedded as data URLs.`)}}if(y(i)&&Array.isArray(i.list)){for(let e of i.list)if(y(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 v(e){return e.e===1&&typeof e.p==`string`&&/^data:[^,]+,/i.test(e.p)&&(e.u===void 0||e.u===``)}function y(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function b(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function x(e){return Number.isSafeInteger(e)&&e>0}async function S(e){let t=await r(e,`utf8`),n=new d({xmlns:!0,fileName:e}),i=!1,a=0,o;n.on(`doctype`,()=>{throw Error(`SVG document types and entities are not allowed.`)}),n.on(`processinginstruction`,()=>{throw Error(`SVG processing instructions are not allowed.`)}),n.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=w(C(e,`viewBox`))}if(f.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(p.test(n)||m.test(n))throw Error(`SVG external resource in attribute "${t.name}" is not allowed.`)}}}),n.on(`closetag`,e=>{e.local.toLowerCase()===`style`&&--a});let s=e=>{if(a>0&&(p.test(e)||m.test(e)))throw Error(`SVG styles may not load external resources.`)};if(n.on(`text`,s),n.on(`cdata`,s),n.write(t).close(),!i||!o)throw Error(`SVG source must contain a valid, positive viewBox.`);return{viewBox:o}}function C(e,t){return Object.values(e.attributes).find(e=>e.name===t)?.value}function w(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 T(e){let t=await u(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 E(e){let t=await D(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 D(e){let t=l(),n,r;t.onReady=e=>{n=e},t.onError=e=>{r=Error(`Could not parse MP4 source: ${e}`)};let i=0;for await(let n of s(e)){let e=new Uint8Array(n),r=c.fromArrayBuffer(e.slice().buffer,i);t.appendBuffer(r),i+=e.byteLength}if(t.flush(),r)throw r;if(!n)throw Error(`Could not parse MP4 metadata.`);return n}async function O({assetId:r,cwd:o,format:s,mediaType:c,projectId:l,sourceDigest:u,sourcePath:d}){let f=`projects/${l}/assets/${r}.${u.slice(0,12)}.${s}`,p=e(o,f);await n(a(p),{recursive:!0}),await t(d,p);let{size:m}=await i(p);return{format:s,hash:u,mediaType:c,path:f,size:m}}export{h as assetKindFor,O as buildPassthroughAsset,T as inspectAudio,_ as inspectLottie,S as inspectSvg,E as inspectVideo,g as mediaTypeForAsset};
@@ -1,32 +1,54 @@
1
- import { AssetCatalog, AssetCatalogEntry, AssetManifest, AssetManifestEntry, AssetProblem, AssetSource, LoadedAssetCatalog, LoadedAssetManifest } from "./contracts.js";
1
+ import { AssetCatalog, AssetCatalogEntry, AssetFile, AssetManifest, AssetManifestEntry, AssetProblem, AssetVariant, LoadedAssetCatalog, LoadedAssetManifest } from "./contracts.js";
2
2
  import { AssetsConfig } from "../config.js";
3
3
 
4
4
  //#region assets/project.d.ts
5
5
  declare const catalogPath: string;
6
6
  declare const manifestPath: string;
7
+ declare const manifestsDir: string;
7
8
  declare const assetIdPattern: RegExp;
9
+ declare const assetPackPattern: RegExp;
8
10
  declare function readAssetCatalog(cwd?: string, {
9
- allowMissing
11
+ allowMissing,
12
+ migrate
10
13
  }?: {
11
14
  allowMissing?: boolean;
15
+ migrate?: boolean;
12
16
  }): Promise<LoadedAssetCatalog>;
13
17
  declare function readAssetManifest(cwd?: string, {
14
- allowMissing
18
+ allowMissing,
19
+ pack
15
20
  }?: {
16
21
  allowMissing?: boolean;
22
+ pack?: string;
17
23
  }): Promise<LoadedAssetManifest>;
24
+ declare function assetManifestPath(pack?: string): string;
25
+ declare function assetCatalogEntries(catalog: AssetCatalog): Array<{
26
+ assetId: string;
27
+ entry: AssetCatalogEntry;
28
+ pack?: string;
29
+ }>;
30
+ declare function assetCatalogPackNames(catalog: AssetCatalog): string[];
31
+ declare function findCatalogAsset(catalog: AssetCatalog, assetId: string): {
32
+ entry: AssetCatalogEntry;
33
+ pack?: string;
34
+ } | undefined;
35
+ declare function setCatalogAsset(catalog: AssetCatalog, assetId: string, entry: AssetCatalogEntry, pack?: string): void;
36
+ declare function listAssetManifestPacks(cwd?: string): Promise<string[]>;
37
+ declare function removeAssetManifest(cwd: string, pack: string): Promise<void>;
18
38
  declare function resolveAssetVariantFile(cwd: string, variantPath: string): string;
19
39
  declare function resolveAssetsConfig(cwd: string, provided?: AssetsConfig): Promise<AssetsConfig>;
20
- declare function validateSource(assetId: string, source: AssetSource): AssetProblem | undefined;
40
+ declare function validateSource(assetId: string, entry: AssetCatalogEntry): AssetProblem | undefined;
21
41
  declare function manifestEntryMatchesCatalog(manifest: AssetManifestEntry, catalog: AssetCatalogEntry): boolean;
42
+ declare function manifestEntryOutputs(entry: AssetManifestEntry): Array<AssetVariant | AssetFile>;
22
43
  declare function isAssetVariantPath(path: string, projectPrefix: string | undefined): boolean;
23
44
  declare function findAssetReferences(cwd: string): Promise<Array<{
24
45
  assetId: string;
25
46
  file: string;
26
47
  }>>;
27
48
  declare function writeCatalog(cwd: string, catalog: AssetCatalog): Promise<void>;
28
- declare function writeManifest(cwd: string, manifest: AssetManifest): Promise<void>;
49
+ declare function writeManifest(cwd: string, manifest: AssetManifest, pack?: string): Promise<void>;
29
50
  declare function assertAssetId(id: string): void;
51
+ declare function assertAssetPackName(pack: string): void;
30
52
  declare function formatAssetProblems(problems: AssetProblem[]): string;
31
53
  //#endregion
32
- export { assertAssetId, assetIdPattern, catalogPath, findAssetReferences, formatAssetProblems, isAssetVariantPath, manifestEntryMatchesCatalog, manifestPath, readAssetCatalog, readAssetManifest, resolveAssetVariantFile, resolveAssetsConfig, validateSource, writeCatalog, writeManifest };
54
+ export { assertAssetId, assertAssetPackName, assetCatalogEntries, assetCatalogPackNames, assetIdPattern, assetManifestPath, assetPackPattern, catalogPath, findAssetReferences, findCatalogAsset, formatAssetProblems, isAssetVariantPath, listAssetManifestPacks, manifestEntryMatchesCatalog, manifestEntryOutputs, manifestPath, manifestsDir, readAssetCatalog, readAssetManifest, removeAssetManifest, resolveAssetVariantFile, resolveAssetsConfig, setCatalogAsset, validateSource, writeCatalog, writeManifest };
@@ -1,3 +1,3 @@
1
- import{readProjectConfig as e}from"../config.js";import"./contracts.js";import{mkdir as t,readFile as n,stat as r,writeFile as i}from"node:fs/promises";import{dirname as a,join as o,resolve as s}from"node:path";import c from"nano-spawn";const l=`assets`,u=o(l,`catalog.json`),d=o(l,`manifest.json`),f=/^[a-z0-9]+(?:[/-][a-z0-9]+)*$/,p=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,m=/@asset\s+([a-z0-9][a-z0-9/-]*)/g,h=/^[0-9a-f]{64}$/;async function g(e=process.cwd(),{allowMissing:t=!1}={}){let n=await E(e,u,`catalog`,t);return n.missing&&t?{catalog:F(),problems:[]}:n.problem?{catalog:F(),problems:[n.problem]}:D(n.value)?{catalog:n.value,problems:[]}:{catalog:F(),problems:[{code:`INVALID_CATALOG`,message:`${u} has an unsupported shape or schema version.`,file:u}]}}async function _(e=process.cwd(),{allowMissing:t=!1}={}){let n=await E(e,d,`manifest`,t);return n.missing&&t?{manifest:I(),problems:[]}:n.problem?{manifest:I(),problems:[n.problem]}:O(n.value)?{manifest:n.value,problems:[]}:{manifest:I(),problems:[{code:`INVALID_MANIFEST`,message:`${d} has an unsupported shape or schema version.`,file:d}]}}function v(e,t){if(!S(t,C(t)))throw Error(`Invalid asset variant path: ${t}`);return s(e,`.coreframe/assets`,t)}async function y(t,n){let r=n??(await e({cwd:t})).assets;if(!r)throw Error(`Static assets are not configured in coreframe.config.ts.`);for(let[e,t]of Object.entries(r))if(typeof t!=`string`||!t.trim())throw Error(`Static asset configuration "${e}" must be a non-empty string.`);if(!p.test(r.projectId))throw Error(`Static asset projectId must use lowercase hyphen-separated segments.`);for(let e of[`endpoint`,`publicBaseUrl`])if(new URL(r[e]).protocol!==`https:`)throw Error(`Static asset ${e} must use HTTPS.`);return{...r,endpoint:r.endpoint.replace(/\/+$/,``),publicBaseUrl:r.publicBaseUrl.replace(/\/+$/,``)}}function b(e,t){if(!t||typeof t.filename!=`string`||!t.filename||typeof t.mediaType!=`string`||!t.mediaType||!Number.isSafeInteger(t.size)||t.size<=0||!h.test(t.digest))return{code:`INVALID_SOURCE`,message:`Asset "${e}" has invalid immutable source metadata.`,assetId:e,file:u}}function x(e,t){return e.alt===t.alt&&e.source.digest===t.source.digest&&e.source.filename===t.source.filename&&e.source.mediaType===t.source.mediaType&&e.source.size===t.source.size&&JSON.stringify(e.variantWidths)===JSON.stringify(t.variantWidths)}function S(e,t){let n=e.split(`/`);return!!(t&&e.startsWith(`${t}/`)&&n.length>=4&&!e.includes(`\\`)&&n.every(e=>e&&e!==`.`&&e!==`..`))}function C(e){let[t,n,r]=e.split(`/`);return t===`projects`&&p.test(n??``)&&r===`assets`?`projects/${n}/assets`:void 0}async function w(e){let t=[];for(let r of await T(e)){let i=s(e,r);if(!await H(i))continue;let a=await n(i,`utf8`);for(let e of a.matchAll(m))t.push({assetId:e[1],file:r})}return t}async function T(e){try{return(await c(`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 E(e,t,r,i){try{return{value:JSON.parse(await n(s(e,t),`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:`${t} does not exist.`,file:t,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:`${t} is not valid JSON: ${e.message}`,file:t}}}}function D(e){return!!(e&&typeof e==`object`&&P(e,[`schemaVersion`,`assets`])&&e.schemaVersion===1&&k(e.assets)&&Object.values(e.assets).every(A))}function O(e){return!!(e&&typeof e==`object`&&P(e,[`schemaVersion`,`assets`])&&e.schemaVersion===1&&k(e.assets)&&Object.values(e.assets).every(j))}function k(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function A(e){return k(e)?P(e,[`source`,`alt`,`variantWidths`])&&M(e.source)&&typeof e.alt==`string`&&(e.variantWidths===void 0||Array.isArray(e.variantWidths)&&e.variantWidths.every(e=>typeof e==`number`)):!1}function j(e){return k(e)?P(e,[`source`,`alt`,`variantWidths`,`width`,`height`,`variants`])&&M(e.source)&&typeof e.alt==`string`&&(e.variantWidths===void 0||Array.isArray(e.variantWidths)&&e.variantWidths.every(e=>typeof e==`number`))&&typeof e.width==`number`&&e.width>0&&typeof e.height==`number`&&e.height>0&&Array.isArray(e.variants)&&e.variants.every(N):!1}function M(e){return k(e)?P(e,[`digest`,`filename`,`mediaType`,`size`])&&typeof e.digest==`string`&&typeof e.filename==`string`&&typeof e.mediaType==`string`&&typeof e.size==`number`:!1}function N(e){return k(e)?P(e,[`format`,`width`,`height`,`path`,`size`,`hash`])&&e.format===`webp`&&typeof e.width==`number`&&typeof e.height==`number`&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.hash==`string`:!1}function P(e,t){let n=new Set(t);return Object.keys(e).every(e=>n.has(e))}function F(){return{schemaVersion:1,assets:{}}}function I(){return{schemaVersion:1,assets:{}}}async function L(e,t){await z(e,u,{...t,assets:B(t.assets)})}async function R(e,t){await z(e,d,{...t,assets:B(t.assets)})}async function z(e,n,r){let o=s(e,n);await t(a(o),{recursive:!0}),await i(o,`${JSON.stringify(r,null,2)}\n`)}function B(e){return Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t)))}function V(e){if(!f.test(e))throw Error(`Asset ID "${e}" must use lowercase path segments.`)}async function H(e){try{return await r(e),!0}catch{return!1}}function U(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(`
1
+ import{readProjectConfig as e}from"../config.js";import"./contracts.js";import{randomUUID as t}from"node:crypto";import{mkdir as n,readFile as r,readdir as i,rename as a,rm as o,stat as s,writeFile as ee}from"node:fs/promises";import{basename as c,dirname as te,extname as l,join as u,resolve as d}from"node:path";import f from"nano-spawn";const p=`assets`,m=u(p,`catalog.json`),h=u(p,`manifest.json`),g=u(p,`manifests`),_=/^[a-z0-9]+(?:[/-][a-z0-9]+)*$/,v=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,y=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,b=/@asset\s+([a-z0-9][a-z0-9/-]*)/g,x=/^[0-9a-f]{64}$/;async function S(e=process.cwd(),{allowMissing:t=!1,migrate:n=!1}={}){let r=await M(e,m,`catalog`,t);if(r.missing&&t)return{catalog:J(),problems:[]};if(r.problem)return{catalog:J(),problems:[r.problem]};if(N(r.value))return{catalog:r.value,problems:[]};if(P(r.value)){let t=F(r.value);return t.problems.length?{catalog:J(),problems:t.problems,migrationRequired:!0}:n?(await X(e,t.catalog),{catalog:t.catalog,migrated:!0,problems:[]}):{catalog:t.catalog,migrationRequired:!0,problems:[{code:`SCHEMA_MIGRATION_REQUIRED`,message:`${m} uses asset schema version 1.`,file:m,fix:`Run: coreframe assets build`}]}}return{catalog:J(),problems:[{code:`INVALID_CATALOG`,message:`${m} has an unsupported shape or schema version.`,file:m}]}}async function C(e=process.cwd(),{allowMissing:t=!1,pack:n}={}){n!==void 0&&$(n);let r=w(n),i=await M(e,r,`manifest`,t);return i.missing&&t?{manifest:Y(n),problems:[]}:i.problem?{manifest:Y(n),problems:[i.problem]}:I(i.value)&&i.value.pack===n?{manifest:i.value,problems:[]}:L(i.value)&&i.value.schemaVersion===1?{manifest:Y(n),problems:[{code:`SCHEMA_MIGRATION_REQUIRED`,message:`${r} uses asset schema version 1.`,file:r,fix:`Run: coreframe assets build`}]}:{manifest:Y(n),problems:[{code:`INVALID_MANIFEST`,message:`${r} has an unsupported shape, pack identity, or schema version.`,file:r}]}}function w(e){return e===void 0?h:u(g,`${e}.json`)}function T(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 E(e){return Object.keys(e.packs??{}).sort()}function D(e,t){return T(e).find(e=>e.assetId===t)}function ne(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 re(e=process.cwd()){try{return(await i(d(e,g),{withFileTypes:!0})).filter(e=>e.isFile()&&l(e.name)===`.json`).map(e=>c(e.name,`.json`)).filter(e=>v.test(e)).sort()}catch(e){if(e.code===`ENOENT`)return[];throw e}}async function ie(e,t){$(t),await o(d(e,w(t)),{force:!0})}function ae(e,t){if(!O(t,k(t)))throw Error(`Invalid generated asset path: ${t}`);return d(e,`.coreframe/assets`,t)}async function oe(t,n){let r=n??(await e({cwd:t})).assets;if(!r)throw Error(`Static assets are not configured in coreframe.config.ts.`);for(let[e,t]of Object.entries(r))if(typeof t!=`string`||!t.trim())throw Error(`Static asset configuration "${e}" must be a non-empty string.`);if(!y.test(r.projectId))throw Error(`Static asset projectId must use lowercase hyphen-separated segments.`);for(let e of[`endpoint`,`publicBaseUrl`])if(new URL(r[e]).protocol!==`https:`)throw Error(`Static asset ${e} must use HTTPS.`);return{...r,endpoint:r.endpoint.replace(/\/+$/,``),publicBaseUrl:r.publicBaseUrl.replace(/\/+$/,``)}}function se(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||!x.test(n.digest)||!de(t.kind).has(n.mediaType))return{code:`INVALID_SOURCE`,message:`Asset "${e}" has invalid immutable source metadata for kind "${t.kind}".`,assetId:e,file:m}}function ce(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 le(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`&&y.test(n??``)&&r===`assets`?`projects/${n}/assets`:void 0}async function A(e){let t=[];for(let n of await j(e)){let i=d(e,n);if(!await me(i))continue;let a=await r(i,`utf8`);for(let e of a.matchAll(b))t.push({assetId:e[1],file:n})}return t}async function j(e){try{return(await f(`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 M(e,t,n,i){try{return{value:JSON.parse(await r(d(e,t),`utf8`)),missing:!1}}catch(e){return e.code===`ENOENT`?{value:void 0,missing:!0,problem:i?void 0:{code:n===`catalog`?`CATALOG_MISSING`:`MANIFEST_MISSING`,message:`${t} does not exist.`,file:t,fix:n===`catalog`?`Run: coreframe assets add <file> --id <asset-id> --alt "..."`:`Run: coreframe assets build`}}:{value:void 0,missing:!1,problem:{code:n===`catalog`?`INVALID_CATALOG`:`INVALID_MANIFEST`,message:`${t} is not valid JSON: ${e.message}`,file:t}}}}function N(e){if(!(L(e)&&q(e,[`schemaVersion`,`assets`,`packs`])&&e.schemaVersion===2&&L(e.assets)&&Object.values(e.assets).every(z)&&(e.packs===void 0||L(e.packs)&&Object.entries(e.packs).every(([e,t])=>v.test(e)&&L(t)&&q(t,[`assets`])&&L(t.assets)&&Object.values(t.assets).every(z)))))return!1;let t=T(e).map(({assetId:e})=>e);return new Set(t).size===t.length}function P(e){return!!(L(e)&&q(e,[`schemaVersion`,`assets`])&&e.schemaVersion===1&&L(e.assets)&&Object.values(e.assets).every(R))}function F(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:m,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 I(e){return!!(L(e)&&q(e,[`schemaVersion`,`pack`,`assets`])&&e.schemaVersion===2&&(e.pack===void 0||typeof e.pack==`string`&&v.test(e.pack))&&L(e.assets)&&Object.values(e.assets).every(B))}function L(e){return!!(e&&typeof e==`object`&&!Array.isArray(e))}function R(e){return!!(L(e)&&q(e,[`source`,`alt`,`variantWidths`])&&V(e.source)&&typeof e.alt==`string`&&K(e.variantWidths))}function z(e){if(!L(e)||!V(e.source))return!1;switch(e.kind){case`image`:return q(e,[`kind`,`source`,`alt`,`variantWidths`])&&typeof e.alt==`string`&&K(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 B(e){if(!L(e)||typeof e.sourceDigest!=`string`||!x.test(e.sourceDigest))return!1;switch(e.kind){case`image`:return q(e,[`kind`,`sourceDigest`,`alt`,`width`,`height`,`variants`])&&typeof e.alt==`string`&&G(e.width)&&G(e.height)&&Array.isArray(e.variants)&&e.variants.every(H);case`svg`:return q(e,[`kind`,`sourceDigest`,`alt`,`viewBox`,`file`])&&typeof e.alt==`string`&&ue(e.viewBox)&&U(e.file,`svg`,`image/svg+xml`);case`audio`:return q(e,[`kind`,`sourceDigest`,`durationSeconds`,`file`])&&G(e.durationSeconds)&&U(e.file,`mp3`,`audio/mpeg`);case`video`:return q(e,[`kind`,`sourceDigest`,`width`,`height`,`durationSeconds`,`hasAudio`,`file`])&&G(e.width)&&G(e.height)&&G(e.durationSeconds)&&typeof e.hasAudio==`boolean`&&U(e.file,`mp4`,`video/mp4`);case`lottie`:return q(e,[`kind`,`sourceDigest`,`width`,`height`,`frameRate`,`durationSeconds`,`file`])&&G(e.width)&&G(e.height)&&G(e.frameRate)&&G(e.durationSeconds)&&U(e.file,`json`,`application/json`);default:return!1}}function V(e){return!!(L(e)&&q(e,[`digest`,`filename`,`mediaType`,`size`])&&typeof e.digest==`string`&&typeof e.filename==`string`&&typeof e.mediaType==`string`&&typeof e.size==`number`)}function H(e){return!!(L(e)&&q(e,[`format`,`mediaType`,`width`,`height`,`path`,`size`,`hash`])&&e.format===`webp`&&e.mediaType===`image/webp`&&G(e.width)&&G(e.height)&&W(e))}function U(e,t,n){return!!(L(e)&&q(e,[`format`,`mediaType`,`path`,`size`,`hash`])&&e.format===t&&e.mediaType===n&&W(e))}function W(e){return typeof e.path==`string`&&Number.isSafeInteger(e.size)&&e.size>0&&typeof e.hash==`string`&&x.test(e.hash)}function ue(e){return!!(L(e)&&q(e,[`minX`,`minY`,`width`,`height`])&&typeof e.minX==`number`&&typeof e.minY==`number`&&G(e.width)&&G(e.height))}function G(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function K(e){return e===void 0||Array.isArray(e)&&e.every(e=>typeof e==`number`)}function de(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,m,{...t,assets:Q(t.assets),...Object.keys(n).length>0?{packs:n}:{packs:void 0}})}async function fe(e,t,n){n!==void 0&&$(n),await Z(e,w(n),{...t,...n===void 0?{pack:void 0}:{pack:n},assets:Q(t.assets)})}async function Z(e,r,i){let s=d(e,r),c=`${s}.${process.pid}.${t()}.tmp`;await n(te(s),{recursive:!0});try{await ee(c,`${JSON.stringify(i,null,2)}\n`,{flag:`wx`}),await a(c,s)}finally{await o(c,{force:!0})}}function Q(e){return Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t)))}function pe(e){if(!_.test(e))throw Error(`Asset ID "${e}" must use lowercase path segments.`)}function $(e){if(!v.test(e))throw Error(`Asset pack "${e}" must use lowercase hyphen-separated segments.`)}async function me(e){try{return await s(e),!0}catch{return!1}}function he(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
2
  `)).join(`
3
- `)}export{V as assertAssetId,f as assetIdPattern,u as catalogPath,w as findAssetReferences,U as formatAssetProblems,S as isAssetVariantPath,x as manifestEntryMatchesCatalog,d as manifestPath,g as readAssetCatalog,_ as readAssetManifest,v as resolveAssetVariantFile,y as resolveAssetsConfig,b as validateSource,L as writeCatalog,R as writeManifest};
3
+ `)}export{pe as assertAssetId,$ as assertAssetPackName,T as assetCatalogEntries,E as assetCatalogPackNames,_ as assetIdPattern,w as assetManifestPath,v as assetPackPattern,m as catalogPath,A as findAssetReferences,D as findCatalogAsset,he as formatAssetProblems,O as isAssetVariantPath,re as listAssetManifestPacks,ce as manifestEntryMatchesCatalog,le as manifestEntryOutputs,h as manifestPath,g as manifestsDir,S as readAssetCatalog,C as readAssetManifest,ie as removeAssetManifest,ae as resolveAssetVariantFile,oe as resolveAssetsConfig,ne as setCatalogAsset,se as validateSource,X as writeCatalog,fe as writeManifest};
@@ -4,8 +4,8 @@ import { AssetCredentialStore } from "./credentials.js";
4
4
  //#region assets/storage.d.ts
5
5
  type AssetObjectStore = {
6
6
  has(bucket: string, key: string): Promise<boolean>;
7
- get(bucket: string, key: string): Promise<Uint8Array>;
8
- put(bucket: string, key: string, body: Uint8Array, contentType: string): Promise<void>;
7
+ download(bucket: string, key: string, target: string): Promise<void>;
8
+ upload(bucket: string, key: string, source: string, contentType: string): Promise<void>;
9
9
  };
10
10
  declare function assetSourceKey(digest: string): string;
11
11
  declare function createB2AssetStore({
@@ -30,6 +30,17 @@ declare function cacheAssetSource({
30
30
  digest: string;
31
31
  env?: NodeJS.ProcessEnv;
32
32
  }): Promise<string>;
33
+ declare function cacheAssetSourceFile({
34
+ cacheRoot,
35
+ digest,
36
+ env,
37
+ source
38
+ }: {
39
+ cacheRoot: string;
40
+ digest: string;
41
+ env?: NodeJS.ProcessEnv;
42
+ source: string;
43
+ }): Promise<string>;
33
44
  declare function materializeAssetSource({
34
45
  bucket,
35
46
  cacheRoot,
@@ -47,5 +58,6 @@ declare function materializeAssetSource({
47
58
  path: string;
48
59
  }>;
49
60
  declare function sha256(body: Uint8Array): string;
61
+ declare function sha256File(path: string): Promise<string>;
50
62
  //#endregion
51
- export { AssetObjectStore, assetCacheFile, assetCacheRoot, assetSourceKey, cacheAssetSource, createB2AssetStore, materializeAssetSource, sha256 };
63
+ export { AssetObjectStore, assetCacheFile, assetCacheRoot, assetSourceKey, cacheAssetSource, cacheAssetSourceFile, createB2AssetStore, materializeAssetSource, sha256, sha256File };
@@ -1 +1 @@
1
- import{resolveAssetCredentials as e}from"./credentials.js";import{mkdir as t,open as n,readFile as r,readdir as i,rename as a,rm as o,stat as s,utimes as c,writeFile as l}from"node:fs/promises";import{dirname as u,join as d,resolve as f}from"node:path";import{createHash as p,randomUUID as m}from"node:crypto";import{homedir as h,platform as g}from"node:os";import{GetObjectCommand as _,HeadObjectCommand as v,PutObjectCommand as y,S3Client as b}from"@aws-sdk/client-s3";function x(e){return N(e),`sources/v1/sha256/${e.slice(0,2)}/${e}`}function S({config:t,credentialStore:n,env:r=process.env}){let i;async function a(){if(i)return i;let a=await e({env:r,profile:t.credentialProfile,store:n});return i=new b({endpoint:t.endpoint,forcePathStyle:!0,region:t.region,credentials:{accessKeyId:a.applicationKeyId,secretAccessKey:a.applicationKey}}),i}return{async has(e,t){try{return await(await a()).send(new v({Bucket:e,Key:t})),!0}catch(e){if(e.$metadata?.httpStatusCode===404)return!1;throw e}},async get(e,t){let n=await(await a()).send(new _({Bucket:e,Key:t}));if(!n.Body)throw Error(`Backblaze B2 returned an empty body for ${e}/${t}.`);return n.Body.transformToByteArray()},async put(e,t,n,r){await(await a()).send(new y({Bucket:e,Key:t,Body:n,ContentType:r}))}}}function C(e=process.env){return e.COREFRAME_ASSETS_CACHE_DIR?f(e.COREFRAME_ASSETS_CACHE_DIR):g()===`darwin`?d(h(),`Library`,`Caches`,`coreframe`,`assets`):g()===`win32`?d(e.LOCALAPPDATA??d(h(),`AppData`,`Local`),`Coreframe`,`assets`):d(e.XDG_CACHE_HOME??d(h(),`.cache`),`coreframe`,`assets`)}function w(e,t){return N(t),d(e,`v1`,`sha256`,t.slice(0,2),t)}async function T({body:e,cacheRoot:n,digest:r,env:i=process.env}){P(e,r);let s=w(n,r);return await D(s,async()=>{if(await O(s,r)){await k(s);return}await t(u(s),{recursive:!0});let n=`${s}.${process.pid}.${m()}.tmp`;try{await l(n,e,{flag:`wx`}),await a(n,s)}finally{await o(n,{force:!0})}}),await A(n,M(i),s),s}async function E({bucket:e,cacheRoot:n,digest:r,env:i=process.env,store:s}){let c=w(n,r);if(await O(c,r))return await k(c),{fetched:!1,path:c};let d=!1;return await D(c,async()=>{if(await O(c,r)){await k(c);return}let n=await s.get(e,x(r));P(n,r),await t(u(c),{recursive:!0});let i=`${c}.${process.pid}.${m()}.tmp`;try{await l(i,n,{flag:`wx`}),await a(i,c),d=!0}finally{await o(i,{force:!0})}}),await A(n,M(i),c),{fetched:d,path:c}}async function D(e,r){await t(u(e),{recursive:!0});let i=`${e}.lock`,a=Date.now();for(;;){let e;try{e=await n(i,`wx`)}catch(e){if(e.code!==`EEXIST`)throw e;let t=await s(i).catch(()=>void 0);if(t&&Date.now()-t.mtimeMs>12e4){await o(i,{force:!0});continue}if(Date.now()-a>=3e4)throw Error(`Timed out waiting for the asset cache lock at ${i}.`);await new Promise(e=>setTimeout(e,50));continue}try{await r()}finally{await e.close(),await o(i,{force:!0})}return}}async function O(e,t){try{return F(await r(e))===t}catch{return!1}}async function k(e){let t=new Date;await c(e,t,t)}async function A(e,t,n){let r=await j(e),i=r.reduce((e,t)=>e+t.size,0);for(let e of r.sort((e,t)=>e.mtimeMs-t.mtimeMs)){if(i<=t)break;e.path!==n&&(await o(e.path,{force:!0}),i-=e.size)}}async function j(e){let t=[],n=async e=>{let r=await i(e,{withFileTypes:!0}).catch(()=>[]);for(let i of r){let r=d(e,i.name);if(i.isDirectory())await n(r);else if(!i.name.endsWith(`.lock`)&&!i.name.endsWith(`.tmp`)){let e=await s(r);t.push({path:r,size:e.size,mtimeMs:e.mtimeMs})}}};return await n(e),t}function M(e){let t=Number(e.COREFRAME_ASSETS_CACHE_MAX_BYTES);return Number.isSafeInteger(t)&&t>0?t:5368709120}function N(e){if(!/^[0-9a-f]{64}$/.test(e))throw Error(`Invalid SHA-256 asset digest: ${e}`)}function P(e,t){let n=F(e);if(n!==t)throw Error(`Asset integrity check failed: expected sha256:${t}, received sha256:${n}.`)}function F(e){return p(`sha256`).update(e).digest(`hex`)}export{w as assetCacheFile,C as assetCacheRoot,x as assetSourceKey,T as cacheAssetSource,S as createB2AssetStore,E as materializeAssetSource,F as sha256};
1
+ import{resolveAssetCredentials as e}from"./credentials.js";import{createHash as t,randomUUID as n}from"node:crypto";import{copyFile as r,mkdir as i,open 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{dirname as f,join as p,resolve as m}from"node:path";import{createReadStream as h,createWriteStream as g}from"node:fs";import{homedir as _,platform as v}from"node:os";import{pipeline as y}from"node:stream/promises";import{GetObjectCommand as b,HeadObjectCommand as x,PutObjectCommand as S,S3Client as C}from"@aws-sdk/client-s3";function w(e){return L(e),`sources/v1/sha256/${e.slice(0,2)}/${e}`}function T({config:t,credentialStore:n,env:r=process.env}){let i;async function a(){if(i)return i;let a=await e({env:r,profile:t.credentialProfile,store:n});return i=new C({endpoint:t.endpoint,forcePathStyle:!0,region:t.region,credentials:{accessKeyId:a.applicationKeyId,secretAccessKey:a.applicationKey}}),i}return{async has(e,t){try{return await(await a()).send(new x({Bucket:e,Key:t})),!0}catch(e){if(e.$metadata?.httpStatusCode===404)return!1;throw e}},async download(e,t,n){let r=await(await a()).send(new b({Bucket:e,Key:t}));if(!r.Body)throw Error(`Backblaze B2 returned an empty body for ${e}/${t}.`);await y(r.Body,g(n,{flags:`wx`}))},async upload(e,t,n,r){let{size:i}=await l(n);await(await a()).send(new S({Bucket:e,Key:t,Body:h(n),ContentLength:i,ContentType:r}))}}}function E(e=process.env){return e.COREFRAME_ASSETS_CACHE_DIR?m(e.COREFRAME_ASSETS_CACHE_DIR):v()===`darwin`?p(_(),`Library`,`Caches`,`coreframe`,`assets`):v()===`win32`?p(e.LOCALAPPDATA??p(_(),`AppData`,`Local`),`Coreframe`,`assets`):p(e.XDG_CACHE_HOME??p(_(),`.cache`),`coreframe`,`assets`)}function D(e,t){return L(t),p(e,`v1`,`sha256`,t.slice(0,2),t)}async function O({body:e,cacheRoot:t,digest:r,env:a=process.env}){R(e,r);let o=D(t,r);return await j(o,async()=>{if(await M(o,r)){await N(o);return}await i(f(o),{recursive:!0});let t=`${o}.${process.pid}.${n()}.tmp`;try{await d(t,e,{flag:`wx`}),await s(t,o)}finally{await c(t,{force:!0})}}),await P(t,I(a),o),o}async function k({cacheRoot:e,digest:t,env:a=process.env,source:o}){if(await B(o)!==t)throw Error(`Asset source does not match declared SHA-256 digest ${t}.`);let l=D(e,t);return await j(l,async()=>{if(await M(l,t)){await N(l);return}await i(f(l),{recursive:!0});let e=`${l}.${process.pid}.${n()}.tmp`;try{if(await r(o,e),await B(e)!==t)throw Error(`Asset source changed while it was being cached.`);await s(e,l)}finally{await c(e,{force:!0})}}),await P(e,I(a),l),l}async function A({bucket:e,cacheRoot:t,digest:r,env:a=process.env,store:o}){let l=D(t,r);if(await M(l,r))return await N(l),{fetched:!1,path:l};let u=!1;return await j(l,async()=>{if(await M(l,r)){await N(l);return}await i(f(l),{recursive:!0});let t=`${l}.${process.pid}.${n()}.tmp`;try{if(await o.download(e,w(r),t),await B(t)!==r)throw Error(`Asset source does not match declared SHA-256 digest ${r}.`);await s(t,l),u=!0}finally{await c(t,{force:!0})}}),await P(t,I(a),l),{fetched:u,path:l}}async function j(e,t){await i(f(e),{recursive:!0});let n=`${e}.lock`,r=Date.now();for(;;){let e;try{e=await a(n,`wx`)}catch(e){if(e.code!==`EEXIST`)throw e;let t=await l(n).catch(()=>void 0);if(t&&Date.now()-t.mtimeMs>12e4){await c(n,{force:!0});continue}if(Date.now()-r>=3e4)throw Error(`Timed out waiting for the asset cache lock at ${n}.`);await new Promise(e=>setTimeout(e,50));continue}try{await t()}finally{await e.close(),await c(n,{force:!0})}return}}async function M(e,t){try{return await B(e)===t}catch{return!1}}async function N(e){let t=new Date;await u(e,t,t)}async function P(e,t,n){let r=await F(e),i=r.reduce((e,t)=>e+t.size,0);for(let e of r.sort((e,t)=>e.mtimeMs-t.mtimeMs)){if(i<=t)break;e.path!==n&&(await c(e.path,{force:!0}),i-=e.size)}}async function F(e){let t=[],n=async e=>{let r=await o(e,{withFileTypes:!0}).catch(()=>[]);for(let i of r){let r=p(e,i.name);if(i.isDirectory())await n(r);else if(!i.name.endsWith(`.lock`)&&!i.name.endsWith(`.tmp`)){let e=await l(r);t.push({path:r,size:e.size,mtimeMs:e.mtimeMs})}}};return await n(e),t}function I(e){let t=Number(e.COREFRAME_ASSETS_CACHE_MAX_BYTES);return Number.isSafeInteger(t)&&t>0?t:5368709120}function L(e){if(!/^[0-9a-f]{64}$/.test(e))throw Error(`Invalid SHA-256 asset digest: ${e}`)}function R(e,t){let n=z(e);if(n!==t)throw Error(`Asset integrity check failed: expected sha256:${t}, received sha256:${n}.`)}function z(e){return t(`sha256`).update(e).digest(`hex`)}async function B(e){let n=t(`sha256`);for await(let t of h(e))n.update(t);return n.digest(`hex`)}export{D as assetCacheFile,E as assetCacheRoot,w as assetSourceKey,O as cacheAssetSource,k as cacheAssetSourceFile,T as createB2AssetStore,A as materializeAssetSource,z as sha256,B as sha256File};
@@ -13,17 +13,22 @@ type AssetWorkflowOptions = {
13
13
  type AddAssetOptions = AssetWorkflowOptions & {
14
14
  file: string;
15
15
  id: string;
16
- alt: string;
16
+ alt?: string;
17
+ pack?: string;
17
18
  variantWidths?: number[];
18
19
  };
19
20
  type BuildAssetsOptions = AssetWorkflowOptions & {
20
21
  id?: string;
22
+ pack?: string;
21
23
  updateManifest?: boolean;
22
24
  };
23
25
  type CheckAssetsOptions = AssetWorkflowOptions & {
24
26
  fix?: boolean;
27
+ pack?: string;
28
+ };
29
+ type PublishAssetsOptions = AssetWorkflowOptions & {
30
+ pack?: string;
25
31
  };
26
- type PublishAssetsOptions = AssetWorkflowOptions;
27
32
  declare function addAsset({
28
33
  alt,
29
34
  cacheDir,
@@ -32,6 +37,7 @@ declare function addAsset({
32
37
  env,
33
38
  file,
34
39
  id,
40
+ pack,
35
41
  store,
36
42
  variantWidths
37
43
  }: AddAssetOptions): Promise<AssetBuildResult>;
@@ -41,6 +47,7 @@ declare function buildAssets({
41
47
  cwd,
42
48
  env,
43
49
  id,
50
+ pack,
44
51
  store,
45
52
  updateManifest
46
53
  }?: BuildAssetsOptions): Promise<AssetBuildResult>;
@@ -1 +1 @@
1
- import"./contracts.js";import{assertAssetId as e,assetIdPattern as t,catalogPath as n,findAssetReferences as r,formatAssetProblems as i,isAssetVariantPath as a,manifestEntryMatchesCatalog as o,manifestPath as s,readAssetCatalog as c,readAssetManifest as l,resolveAssetVariantFile as u,resolveAssetsConfig as d,validateSource as f,writeCatalog as p,writeManifest as m}from"./project.js";import{assetCacheRoot as h,assetSourceKey as g,cacheAssetSource as _,createB2AssetStore as v,materializeAssetSource as y,sha256 as b}from"./storage.js";import{assertVariantWidths as x,assetVariantWidths as S,buildAssetVariants as C,mediaTypeFor as w,readImageDimensions as T,variantWidthsProblem as E}from"./images.js";import{mkdir as D,readFile as O,writeFile as k}from"node:fs/promises";import{basename as A,dirname as j,join as M,relative as N,resolve as P,sep as F}from"node:path";const I=M(`.coreframe/assets`,`asset-publish-report.json`);async function L({alt:t,cacheDir:n,config:r,cwd:i=process.cwd(),env:a=process.env,file:o,id:s,store:l,variantWidths:u}){if(e(s),!t.trim())throw Error(`Asset alt text must not be empty.`);let f=await d(i,r),m=l??v({config:f,env:a}),y=P(i,o),S=await O(y);x(u,(await T(y)).width);let C=b(S),E={digest:C,filename:A(o),mediaType:w(o),size:S.byteLength},D=n??h(a);await _({body:S,cacheRoot:D,digest:C,env:a});let k=g(C);if(!await m.has(f.sourceBucket,k)&&(await m.put(f.sourceBucket,k,S,E.mediaType),!await m.has(f.sourceBucket,k)))throw Error(`Backblaze B2 did not confirm source upload ${k}.`);let j=await c(i,{allowMissing:!0});return j.problems.length>0?{ok:!1,assets:[],fetched:[],problems:j.problems}:(j.catalog.assets[s]={source:E,alt:t.trim(),...u?{variantWidths:u}:{}},await p(i,j.catalog),R({cacheDir:D,config:f,cwd:i,env:a,id:s,store:m}))}async function R({cacheDir:e,config:t,cwd:r=process.cwd(),env:i=process.env,id:a,store:o,updateManifest:s=!0}={}){let u=await c(r),p=[...u.problems],_=[],b=[];if(p.length>0)return{ok:!1,assets:_,fetched:b,problems:p};let x=await d(r,t),S=o??v({config:x,env:i}),w=e??h(i),D=(await l(r,{allowMissing:!0})).manifest,O={schemaVersion:1,assets:a?{...D.assets}:{}};for(let[e,t]of Object.entries(u.catalog.assets)){if(a&&e!==a)continue;let n=f(e,t.source);if(n){p.push(n);continue}let o;try{o=await y({bucket:x.sourceBucket,cacheRoot:w,digest:t.source.digest,env:i,store:S})}catch(t){p.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 "${x.credentialProfile}".`});continue}o.fetched&&b.push(g(t.source.digest));let s=await T(o.path),c=E(e,t,s.width);if(c){p.push(c);continue}let l=await C({assetId:e,config:x,cwd:r,entry:t,sourcePath:o.path,sourceDimensions:s});O.assets[e]={...t,...s,variants:l},_.push({assetId:e,variants:l})}return a&&!u.catalog.assets[a]&&p.push({code:`REFERENCE_MISSING`,message:`Asset "${a}" is not present in ${n}.`,assetId:a,fix:`Run: coreframe assets add <file> --id ${a} --alt "..."`}),s&&p.length===0&&await m(r,O),{ok:p.length===0,assets:_,fetched:b,problems:p}}async function z(e={}){let{cwd:i=process.cwd(),fix:p=!1}=e;if(p){let t=await R(e);if(!t.ok)return{ok:!1,assets:[],problems:t.problems}}let[m,h]=await Promise.all([c(i),l(i)]),g=[...m.problems,...h.problems],_=[],v=await r(i),y=`projects/${(await d(i,e.config)).projectId}/assets`;for(let[e,r]of Object.entries(m.catalog.assets)){t.test(e)||g.push({code:`INVALID_ASSET_ID`,message:`Asset ID "${e}" must use lowercase path segments.`,assetId:e,file:n}),r.alt.trim()||g.push({code:`INVALID_ALT`,message:`Asset "${e}" is missing alt text.`,assetId:e,file:n});let c=f(e,r.source);c&&g.push(c);let l=h.manifest.assets[e];if(!l||!o(l,r)){g.push({code:`MANIFEST_STALE`,message:`Asset "${e}" is missing from the generated manifest or does not match its catalog entry.`,assetId:e,file:s,fix:`Run: coreframe assets build --id ${e}`});continue}let d=E(e,r,l.width);d&&g.push(d);let p=await Promise.all(l.variants.map(async t=>{let n=a(t.path,y),r=!1;if(n)try{let e=await O(u(i,t.path));r=e.byteLength===t.size&&b(e)===t.hash}catch{r=!1}return n||g.push({code:`INVALID_VARIANT_PATH`,message:`Asset "${e}" has an invalid generated variant path.`,assetId:e,file:t.path,fix:`Run: coreframe assets build --id ${e}`}),{...t,exists:r}})),m=S(r,l.width),v=l.variants.map(e=>e.width);!d&&(m.length!==v.length||m.some((e,t)=>e!==v[t])||p.some(e=>!e.exists))&&g.push({code:`GENERATED_VARIANT_MISSING`,message:`Asset "${e}" is missing its expected responsive variants.`,assetId:e,fix:`Run: coreframe assets build --id ${e}`}),_.push({assetId:e,source:r.source,variants:p})}for(let e of Object.keys(h.manifest.assets))m.catalog.assets[e]||g.push({code:`MANIFEST_STALE`,message:`Generated manifest asset "${e}" is not present in the catalog.`,assetId:e,file:s,fix:`Run: coreframe assets build`});for(let e of v)m.catalog.assets[e.assetId]||g.push({code:`INVALID_REFERENCE`,message:`Asset reference "${e.assetId}" is not present in ${n}.`,assetId:e.assetId,file:e.file,fix:`Run: coreframe assets add <file> --id ${e.assetId} --alt "..."`});return{ok:g.length===0,assets:_,problems:g}}async function B(e={}){let{cwd:t=process.cwd(),env:n=process.env}=e,r=[...(await z(e)).problems],i=[],a=[],o=P(t,I);if(r.length===0){let r=await d(t,e.config),o=e.store??v({config:r,env:n}),{manifest:s}=await l(t);for(let[e,n]of Object.entries(s.assets))for(let s of n.variants){let n=u(t,s.path),c={assetId:e,source:N(t,n).split(F).join(`/`),target:`${r.publicBucket}/${s.path}`,size:s.size};if(await o.has(r.publicBucket,s.path)){a.push(c);continue}await o.put(r.publicBucket,s.path,await O(n),`image/webp`),i.push(c)}}let s={ok:r.length===0,uploaded:i,skipped:a,reportPath:o,problems:r};return await D(j(o),{recursive:!0}),await k(o,`${JSON.stringify(s,null,2)}\n`),s}async function V(e,t={}){return R({...t,id:e})}function H(e,t=!1){if(t){console.log(JSON.stringify(e,null,2));return}let n=e;n.problems?.length&&console.error(i(n.problems)),console.log(n.ok===!1?`Asset workflow has problems.`:`Asset workflow complete.`)}export{L as addAsset,R as buildAssets,z as checkAssets,V as materializeAsset,H as printAssetResult,B as publishAssets};
1
+ import"./contracts.js";import{assertAssetId as e,assertAssetPackName as t,assetCatalogEntries as n,assetCatalogPackNames as r,assetIdPattern as i,assetManifestPath as a,catalogPath as o,findAssetReferences as s,findCatalogAsset as c,formatAssetProblems as l,isAssetVariantPath as u,listAssetManifestPacks as d,manifestEntryMatchesCatalog as f,manifestEntryOutputs as p,readAssetCatalog as m,readAssetManifest as h,removeAssetManifest as g,resolveAssetVariantFile as _,resolveAssetsConfig as v,setCatalogAsset as y,validateSource as b,writeCatalog as x,writeManifest as S}from"./project.js";import{assetCacheRoot as C,assetSourceKey as w,cacheAssetSourceFile as T,createB2AssetStore as E,materializeAssetSource as D,sha256File as O}from"./storage.js";import{assertVariantWidths as k,assetVariantWidths as A,buildAssetVariants as j,mediaTypeFor as M,readImageDimensions as N,variantWidthsProblem as P}from"./images.js";import{assetKindFor as F,buildPassthroughAsset as I,inspectAudio as L,inspectLottie as R,inspectSvg as z,inspectVideo as B,mediaTypeForAsset as V}from"./media.js";import{mkdir as H,stat as U,writeFile as W}from"node:fs/promises";import{basename as G,dirname as K,join as q,relative as J,resolve as Y,sep as X}from"node:path";const Z=q(`.coreframe/assets`,`asset-publish-report.json`);async function ee({alt:n,cacheDir:r,config:i,cwd:a=process.cwd(),env:o=process.env,file:s,id:c,pack:l,store:u,variantWidths:d}){e(c),l!==void 0&&t(l);let f=Y(a,s),p=F(f),h=n?.trim();if((p===`image`||p===`svg`)&&!h)throw Error(`Raster images and SVG assets require non-empty alt text.`);if(p!==`image`&&d)throw Error(`Asset kind "${p}" does not support responsive variant widths.`);await ne(p,f,d);let g=await m(a,{allowMissing:!0,migrate:!0});if(g.problems.length>0)return{ok:!1,assets:[],fetched:[],problems:g.problems};let _=await v(a,i),b=u??E({config:_,env:o}),S=await O(f),D={digest:S,filename:G(s),mediaType:p===`image`?M(s):V(s,p),size:(await U(f)).size},k=r??C(o),A=await T({cacheRoot:k,digest:S,env:o,source:f}),j=w(S);if(!await b.has(_.sourceBucket,j)&&(await b.upload(_.sourceBucket,j,A,D.mediaType),!await b.has(_.sourceBucket,j)))throw Error(`Backblaze B2 did not confirm source upload ${j}.`);return y(g.catalog,c,te({alt:h,kind:p,source:D,variantWidths:d}),l),await x(a,g.catalog),Q({cacheDir:k,config:_,cwd:a,env:o,id:g.migrated?void 0:c,pack:g.migrated?void 0:l,store:b})}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`){k(n,(await N(t)).width),M(t);return}if(e===`svg`){await z(t);return}if(e===`audio`){await L(t);return}if(e===`video`){await B(t);return}await R(t)}async function Q({cacheDir:e,config:i,cwd:a=process.cwd(),env:s=process.env,id:l,pack:u,store:f,updateManifest:_=!0}={}){u!==void 0&&t(u);let y=await m(a,{migrate:!0}),x=[...y.problems],T=[],O=[];if(x.length>0)return{ok:!1,assets:T,fetched:O,problems:x};y.migrated&&(l=void 0,u=void 0);let k=n(y.catalog),A=l?c(y.catalog,l):void 0;if(l&&!A)return x.push({code:`REFERENCE_MISSING`,message:`Asset "${l}" is not present in ${o}.`,assetId:l,fix:`Run: coreframe assets add <file> --id ${l}`}),{ok:!1,assets:T,fetched:O,problems:x};if(l&&u!==void 0&&A?.pack!==u)return x.push({code:`REFERENCE_MISSING`,message:`Asset "${l}" is not present in asset pack "${u}".`,assetId:l,fix:`Build the asset without --pack or use its owning pack.`}),{ok:!1,assets:T,fetched:O,problems:x};let j=l?A?.pack:u;if(!l&&u!==void 0&&!y.catalog.packs?.[u])return x.push({code:`REFERENCE_MISSING`,message:`Asset pack "${u}" is not present in ${o}.`,fix:`Add an asset with --pack ${u}.`}),{ok:!1,assets:T,fetched:O,problems:x};let M=k.filter(e=>(l===void 0||e.assetId===l)&&(j===void 0?u===void 0||e.pack===void 0:e.pack===j)),N=l!==void 0||u!==void 0?[j]:[void 0,...r(y.catalog)],P=await v(a,i),F=f??E({config:P,env:s}),I=e??C(s),L=new Map;for(let e of N)if(l&&_){let t=await h(a,{allowMissing:!0,pack:e});if(t.problems.length>0)return{ok:!1,assets:T,fetched:O,problems:t.problems};L.set(e,t.manifest)}else L.set(e,re(e));for(let{assetId:e,entry:t,pack:n}of M){let r=b(e,t);if(r){x.push(r);continue}let i;try{i=await D({bucket:P.sourceBucket,cacheRoot:I,digest:t.source.digest,env:s,store:F})}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 "${P.credentialProfile}".`});continue}i.fetched&&O.push(w(t.source.digest));try{let r=await ie({assetId:e,config:P,cwd:a,entry:t,sourcePath:i.path});L.get(n).assets[e]=r,T.push({assetId:e,outputs:p(r)})}catch(n){x.push({code:`INVALID_MEDIA`,message:`Asset "${e}" could not be built: ${n.message}`,assetId:e,file:o,fix:`Replace the source with a supported ${t.kind} file, then run: coreframe assets build --id ${e}`})}}if(_&&x.length===0){for(let[e,t]of L)await S(a,t,e);if(l===void 0&&u===void 0){let e=new Set(r(y.catalog));for(let t of await d(a))e.has(t)||await g(a,t)}}return{ok:x.length===0,assets:T,fetched:O,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 N(i),o=P(e,r,a.width);if(o)throw Error(o.message);let s=await j({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 I({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 z(i),file:a}:r.kind===`audio`?{kind:`audio`,sourceDigest:r.source.digest,...await L(i),file:a}:r.kind===`video`?{kind:`video`,sourceDigest:r.source.digest,...await B(i),file:a}:{kind:`lottie`,sourceDigest:r.source.digest,...await R(i),file:a}}async function $(e={}){let{cwd:l=process.cwd(),fix:g=!1,pack:y}=e;if(y!==void 0&&t(y),g){let t=await Q(e);if(!t.ok)return{ok:!1,assets:[],problems:t.problems}}let x=await m(l),S=[...x.problems],C=[];if(S.some(e=>e.code===`SCHEMA_MIGRATION_REQUIRED`))return{ok:!1,assets:C,problems:S};if(y!==void 0&&!x.catalog.packs?.[y])return S.push({code:`REFERENCE_MISSING`,message:`Asset pack "${y}" is not present in ${o}.`,fix:`Add an asset with --pack ${y}.`}),{ok:!1,assets:C,problems:S};let w=y===void 0?[void 0,...r(x.catalog)]:[y],T=new Map;for(let e of w){let t=await h(l,{pack:e});S.push(...t.problems),T.set(e,t.manifest)}if(y===void 0){let e=new Set(r(x.catalog));for(let t of await d(l))e.has(t)||S.push({code:`MANIFEST_STALE`,message:`Generated asset pack manifest "${t}" is not present in the catalog.`,file:a(t),fix:`Run: coreframe assets build`})}let E=await s(l),D=`projects/${(await v(l,e.config)).projectId}/assets`,k=n(x.catalog).filter(e=>y===void 0||e.pack===y);for(let{assetId:e,entry:t,pack:n}of k){let r=a(n);i.test(e)||S.push({code:`INVALID_ASSET_ID`,message:`Asset ID "${e}" must use lowercase path segments.`,assetId:e,file:o}),(t.kind===`image`||t.kind===`svg`)&&!t.alt.trim()&&S.push({code:`INVALID_ALT`,message:`Asset "${e}" is missing alt text.`,assetId:e,file:o});let s=b(e,t);s&&S.push(s);let c=T.get(n)?.assets[e];if(!c||!f(c,t)){S.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}`});continue}let d=await Promise.all(p(c).map(async t=>{let n=u(t.path,D),r=!1;if(n)try{let e=_(l,t.path);r=(await U(e)).size===t.size&&await O(e)===t.hash}catch{r=!1}return n||S.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}`}),{...t,exists:r}})),m=d.every(e=>e.exists);if(t.kind===`image`&&c.kind===`image`){let n=P(e,t,c.width);if(n)S.push(n);else{let e=A(t,c.width),n=c.variants.map(e=>e.width);m=m&&e.length===n.length&&e.every((e,t)=>e===n[t])}}m||S.push({code:`GENERATED_ASSET_MISSING`,message:`Asset "${e}" is missing its expected generated output.`,assetId:e,fix:`Run: coreframe assets build --id ${e}`}),C.push({assetId:e,source:t.source,outputs:d})}for(let[e,t]of T){let n=e===void 0?x.catalog.assets:x.catalog.packs?.[e]?.assets;for(let r of Object.keys(t.assets))n?.[r]||S.push({code:`MANIFEST_STALE`,message:`Generated manifest asset "${r}" is not present in its catalog collection.`,assetId:r,file:a(e),fix:`Run: coreframe assets build`})}for(let e of E){let t=c(x.catalog,e.assetId);t?t.entry.kind!==`image`&&t.entry.kind!==`svg`&&S.push({code:`INVALID_REFERENCE`,message:`Markdown image reference "${e.assetId}" cannot use asset kind "${t.entry.kind}".`,assetId:e.assetId,file:e.file}):S.push({code:`INVALID_REFERENCE`,message:`Asset reference "${e.assetId}" is not present in ${o}.`,assetId:e.assetId,file:e.file,fix:`Run: coreframe assets add <file> --id ${e.assetId}`})}return{ok:S.length===0,assets:C,problems:S}}async function ae(e={}){let{cwd:t=process.cwd(),env:n=process.env,pack:i}=e,a=[...(await $(e)).problems],o=[],s=[],c=Y(t,Z);if(a.length===0){let a=await v(t,e.config),c=e.store??E({config:a,env:n}),{catalog:l}=await m(t),u=i===void 0?[void 0,...r(l)]:[i];for(let e of u){let{manifest:n}=await h(t,{pack:e});for(let[e,r]of Object.entries(n.assets))for(let n of p(r)){let r=_(t,n.path),i={assetId:e,source:J(t,r).split(X).join(`/`),target:`${a.publicBucket}/${n.path}`,size:n.size};if(await c.has(a.publicBucket,n.path)){s.push(i);continue}await c.upload(a.publicBucket,n.path,r,n.mediaType),o.push(i)}}}let l={ok:a.length===0,uploaded:o,skipped:s,reportPath:c,problems:a};return await H(K(c),{recursive:!0}),await W(c,`${JSON.stringify(l,null,2)}\n`),l}async function oe(e,t={}){return Q({...t,id:e})}function se(e,t=!1){if(t){console.log(JSON.stringify(e,null,2));return}let n=e;n.problems?.length&&console.error(l(n.problems)),console.log(n.ok===!1?`Asset workflow has problems.`:`Asset workflow complete.`)}export{ee as addAsset,Q as buildAssets,$ as checkAssets,oe as materializeAsset,se as printAssetResult,ae as publishAssets};
package/dist/assets.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AssetBuildEntry, AssetBuildResult, AssetCatalog, AssetCatalogEntry, AssetManifest, AssetManifestEntry, AssetProblem, AssetPublishResult, AssetSource, AssetStatus, AssetStatusEntry, AssetVariant, AssetVariantStatus, LoadedAssetCatalog, LoadedAssetManifest, PublishedAsset } from "./assets/contracts.js";
2
- import { formatAssetProblems, readAssetCatalog, readAssetManifest, resolveAssetVariantFile } from "./assets/project.js";
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 "./assets/contracts.js";
2
+ import { assetCatalogEntries, assetCatalogPackNames, assetManifestPath, findCatalogAsset, formatAssetProblems, listAssetManifestPacks, manifestEntryOutputs, readAssetCatalog, readAssetManifest, resolveAssetVariantFile } from "./assets/project.js";
3
3
  import { AddAssetOptions, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetsOptions, PublishAssetsOptions, addAsset, buildAssets, checkAssets, materializeAsset, printAssetResult, publishAssets } from "./assets/workflows.js";
4
- export { AddAssetOptions, type AssetBuildEntry, type AssetBuildResult, type AssetCatalog, type AssetCatalogEntry, type AssetManifest, type AssetManifestEntry, type AssetProblem, type AssetPublishResult, type AssetSource, type AssetStatus, type AssetStatusEntry, type AssetVariant, type AssetVariantStatus, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetsOptions, type LoadedAssetCatalog, type LoadedAssetManifest, PublishAssetsOptions, type PublishedAsset, addAsset, buildAssets, checkAssets, formatAssetProblems, materializeAsset, printAssetResult, publishAssets, readAssetCatalog, readAssetManifest, resolveAssetVariantFile };
4
+ export { AddAssetOptions, type AssetBuildEntry, type AssetBuildResult, type AssetCatalog, type AssetCatalogEntry, type AssetCatalogPack, type AssetFile, type AssetKind, type AssetManifest, type AssetManifestEntry, type AssetOutput, type AssetOutputStatus, type AssetProblem, type AssetPublishResult, type AssetSource, type AssetStatus, type AssetStatusEntry, type AssetVariant, AssetWorkflowOptions, type AudioAssetFile, type AudioCatalogEntry, type AudioManifestEntry, BuildAssetsOptions, 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, checkAssets, findCatalogAsset, formatAssetProblems, listAssetManifestPacks, manifestEntryOutputs, materializeAsset, printAssetResult, publishAssets, readAssetCatalog, readAssetManifest, resolveAssetVariantFile };
package/dist/assets.js CHANGED
@@ -1 +1 @@
1
- import{formatAssetProblems as e,readAssetCatalog as t,readAssetManifest as n,resolveAssetVariantFile as r}from"./assets/project.js";import{addAsset as i,buildAssets as a,checkAssets as o,materializeAsset as s,printAssetResult as c,publishAssets as l}from"./assets/workflows.js";export{i as addAsset,a as buildAssets,o as checkAssets,e as formatAssetProblems,s as materializeAsset,c as printAssetResult,l as publishAssets,t as readAssetCatalog,n as readAssetManifest,r as resolveAssetVariantFile};
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"./assets/project.js";import{addAsset as u,buildAssets as d,checkAssets as f,materializeAsset as p,printAssetResult as m,publishAssets as h}from"./assets/workflows.js";export{u as addAsset,e as assetCatalogEntries,t as assetCatalogPackNames,n as assetManifestPath,d as buildAssets,f as checkAssets,r as findCatalogAsset,i as formatAssetProblems,a as listAssetManifestPacks,o as manifestEntryOutputs,p as materializeAsset,m as printAssetResult,h as publishAssets,s as readAssetCatalog,c as readAssetManifest,l as resolveAssetVariantFile};