@stacksjs/image 0.70.317 → 0.70.319

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app-store.js CHANGED
@@ -1 +1 @@
1
- import{mkdir}from"node:fs/promises";import process from"node:process";import{APP_STORE_MAX_SCREENSHOTS,generateAppStoreScreenshots}from"ts-images";import{loadFonts}from"./fonts";import{background,color,device,markPainter,projectFile,requireProjectFile,themed}from"./theme";export async function generateAppStoreScreenshotSet(images,root=process.cwd()){if(images.appStore?.enabled!==!0||!images.appStore.slides?.length)return{};const appStore=themed(images,images.appStore),declared=images.appStore.slides,displays=appStore.displays?.length?appStore.displays:["APP_IPHONE_67"],fonts=await loadFonts(images.fonts,root),outputDir=projectFile(appStore.outputDir??"resources/app-store/screenshots",root);await mkdir(outputDir,{recursive:!0});const shared={outputDir,titleFont:fonts.title,bodyFont:fonts.body,brand:appStore.brand,drawMark:await markPainter(appStore.mark,root),markPlate:appStore.markPlate===!1?void 0:color(appStore.markPlate),background:background(appStore.background,root),color:color(appStore.color),mutedColor:color(appStore.mutedColor),device:device(appStore.device),layout:appStore.layout,format:appStore.format??"png",quality:appStore.quality},results={};for(const display of displays){const slides=declared.filter((slide)=>!slide.displays?.length||slide.displays.includes(display)).map((slide)=>({capture:requireProjectFile(slide.capture,root,`Capture for the "${slide.headline}" slide`),headline:slide.headline,subheadline:slide.subheadline,background:background(slide.background,root)}));if(!slides.length)continue;if(slides.length>APP_STORE_MAX_SCREENSHOTS)throw Error(`[image] ${display} has ${slides.length} slides; App Store Connect accepts at most ${APP_STORE_MAX_SCREENSHOTS}`);const rendered=await generateAppStoreScreenshots({...shared,slides,displayTypes:[display]});results[display]=rendered[display]??[]}return results}
1
+ import{mkdir}from"node:fs/promises";import process from"node:process";import{APP_STORE_MAX_SCREENSHOTS,generateAppStoreScreenshots}from"ts-images";import{loadFonts}from"./fonts";import{background,color,device,markPainter,projectFile,requireProjectFile,themed}from"./theme";export async function generateAppStoreScreenshotSet(images,root=process.cwd()){if(images.appStore?.enabled!==!0||!images.appStore.slides?.length)return{};const appStore=themed(images,images.appStore),declared=images.appStore.slides,displays=appStore.displays?.length?appStore.displays:["APP_IPHONE_67"],fonts=await loadFonts(images.fonts,root),outputDir=projectFile(appStore.outputDir??"resources/app-store/screenshots",root);await mkdir(outputDir,{recursive:!0});const shared={outputDir,titleFont:fonts.title,bodyFont:fonts.body,brand:appStore.brand,drawMark:(await markPainter(appStore.mark,root))?.draw,markPlate:appStore.markPlate===!1?void 0:color(appStore.markPlate),background:background(appStore.background,root),color:color(appStore.color),mutedColor:color(appStore.mutedColor),device:device(appStore.device),layout:appStore.layout,format:appStore.format??"png",quality:appStore.quality},results={};for(const display of displays){const slides=declared.filter((slide)=>!slide.displays?.length||slide.displays.includes(display)).map((slide)=>({capture:requireProjectFile(slide.capture,root,`Capture for the "${slide.headline}" slide`),headline:slide.headline,subheadline:slide.subheadline,background:background(slide.background,root)}));if(!slides.length)continue;if(slides.length>APP_STORE_MAX_SCREENSHOTS)throw Error(`[image] ${display} has ${slides.length} slides; App Store Connect accepts at most ${APP_STORE_MAX_SCREENSHOTS}`);const rendered=await generateAppStoreScreenshots({...shared,slides,displayTypes:[display]});results[display]=rendered[display]??[]}return results}
package/dist/social.js CHANGED
@@ -1 +1 @@
1
- import{mkdir}from"node:fs/promises";import process from"node:process";import{generateSocialCards}from"ts-images";import{loadFonts}from"./fonts";import{background,color,device,markPainter,projectFile,requireProjectFile,themed}from"./theme";const PRESET_SIZES={og:{width:1200,height:630},twitter:{width:1200,height:600},square:{width:1200,height:1200},portrait:{width:1200,height:1500}};export function socialCardName(path){const trimmed=path.replace(/^\/+|\/+$/g,"");return trimmed===""?"og":trimmed.replace(/[^a-z0-9]+/gi,"-").toLowerCase()}export async function generateSocialCardSet(images,root=process.cwd()){if(images.social?.enabled!==!0)return[];const social=themed(images,images.social),fonts=await loadFonts(images.fonts,root),outputDir=projectFile(social.outputDir??"public/social",root),publicPath=`/${(social.publicPath??"/social").replace(/^\/+|\/+$/g,"")}`,presets=social.presets?.length?social.presets:["og"],format=social.format??"jpeg";await mkdir(outputDir,{recursive:!0});const drawMark=await markPainter(social.mark,root),shared={titleFont:fonts.title,bodyFont:fonts.body,brand:social.brand,drawMark,markPlate:social.markPlate===!1?void 0:color(social.markPlate),surface:background(social.background,root),color:color(social.color),mutedColor:color(social.mutedColor),accent:color(social.accent),format,quality:social.quality,presets},pages=social.pages?.length?social.pages:[{path:"/",title:social.brand??"Home"}],deviceOptions=device(social.device),results=[];for(const page of pages){const name=socialCardName(page.path),shot=page.foreground??social.foreground,files=await generateSocialCards(outputDir,{...shared,name,title:page.title,eyebrow:page.eyebrow,subtitle:page.subtitle,foreground:shot?{image:requireProjectFile(shot,root,`Product shot for ${page.path}`),radius:deviceOptions?.radius,borderColor:deviceOptions?.borderColor,shadow:deviceOptions?.shadow,scale:deviceOptions?.scale}:void 0}),urls=Object.fromEntries(Object.entries(files).map(([preset,file])=>[preset,`${publicPath}/${file.slice(file.lastIndexOf("/")+1)}`]));results.push({path:page.path,name,files,urls,width:PRESET_SIZES[presets[0]].width,height:PRESET_SIZES[presets[0]].height,title:page.title})}return results}export function socialMetaTags(card,siteUrl,format="jpeg"){const primary=`${siteUrl.replace(/\/+$/,"")}${card.urls.og??Object.values(card.urls)[0]}`,mimeType=format==="jpeg"?"image/jpeg":`image/${format}`;return[`<meta property="og:image" content="${primary}">`,`<meta property="og:image:type" content="${mimeType}">`,`<meta property="og:image:width" content="${card.width}">`,`<meta property="og:image:height" content="${card.height}">`,`<meta property="og:image:alt" content="${escapeAttribute(card.title)}">`,'<meta name="twitter:card" content="summary_large_image">',`<meta name="twitter:image" content="${primary}">`,`<meta name="twitter:image:alt" content="${escapeAttribute(card.title)}">`]}function escapeAttribute(value){return value.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
1
+ import{mkdir}from"node:fs/promises";import process from"node:process";import{generateSocialCards}from"ts-images";import{loadFonts}from"./fonts";import{background,color,device,markPainter,projectFile,requireProjectFile,themed}from"./theme";const PRESET_SIZES={og:{width:1200,height:630},twitter:{width:1200,height:600},square:{width:1200,height:1200},portrait:{width:1200,height:1500}};export function socialCardName(path){const trimmed=path.replace(/^\/+|\/+$/g,"");return trimmed===""?"og":trimmed.replace(/[^a-z0-9]+/gi,"-").toLowerCase()}export async function generateSocialCardSet(images,root=process.cwd()){if(images.social?.enabled!==!0)return[];const social=themed(images,images.social),fonts=await loadFonts(images.fonts,root),outputDir=projectFile(social.outputDir??"public/social",root),publicPath=`/${(social.publicPath??"/social").replace(/^\/+|\/+$/g,"")}`,presets=social.presets?.length?social.presets:["og"],format=social.format??"jpeg";await mkdir(outputDir,{recursive:!0});const mark=await markPainter(social.mark,root),shared={titleFont:fonts.title,bodyFont:fonts.body,brand:social.brand,drawMark:mark?.draw,markAspect:mark?.aspect,markPlate:social.markPlate===!1?null:color(social.markPlate),surface:background(social.background,root),color:color(social.color),mutedColor:color(social.mutedColor),accent:color(social.accent),format,quality:social.quality,presets},pages=social.pages?.length?social.pages:[{path:"/",title:social.brand??"Home"}],deviceOptions=device(social.device),results=[];for(const page of pages){const name=socialCardName(page.path),shot=page.foreground??social.foreground,files=await generateSocialCards(outputDir,{...shared,name,title:page.title,eyebrow:page.eyebrow,subtitle:page.subtitle,foreground:shot?{image:requireProjectFile(shot,root,`Product shot for ${page.path}`),radius:deviceOptions?.radius,borderColor:deviceOptions?.borderColor,shadow:deviceOptions?.shadow,scale:deviceOptions?.scale}:void 0}),urls=Object.fromEntries(Object.entries(files).map(([preset,file])=>[preset,`${publicPath}/${file.slice(file.lastIndexOf("/")+1)}`]));results.push({path:page.path,name,files,urls,width:PRESET_SIZES[presets[0]].width,height:PRESET_SIZES[presets[0]].height,title:page.title})}return results}export function socialMetaTags(card,siteUrl,format="jpeg"){const primary=`${siteUrl.replace(/\/+$/,"")}${card.urls.og??Object.values(card.urls)[0]}`,mimeType=format==="jpeg"?"image/jpeg":`image/${format}`;return[`<meta property="og:image" content="${primary}">`,`<meta property="og:image:type" content="${mimeType}">`,`<meta property="og:image:width" content="${card.width}">`,`<meta property="og:image:height" content="${card.height}">`,`<meta property="og:image:alt" content="${escapeAttribute(card.title)}">`,'<meta name="twitter:card" content="summary_large_image">',`<meta name="twitter:image" content="${primary}">`,`<meta name="twitter:image:alt" content="${escapeAttribute(card.title)}">`]}function escapeAttribute(value){return value.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
package/dist/theme.d.ts CHANGED
@@ -33,6 +33,9 @@ export declare function device(value: ImageDeviceConfig | undefined): {
33
33
  scale?: number
34
34
  shadow?: { blur?: number, offsetX?: number, offsetY?: number, spread?: number, color?: RGBA }
35
35
  } | undefined;
36
+ export declare function markPainter(path: string | undefined, root?: string): Promise<MarkPainter | undefined>;
37
+ /** Fold the shared palette into a generator's own, letting the generator win. */
38
+ export declare function themed<T extends ImageTheme>(images: ImagesConfig, section: T | undefined): T & ImageTheme;
36
39
  /**
37
40
  * Build a `drawMark` callback from an image path.
38
41
  *
@@ -41,9 +44,10 @@ export declare function device(value: ImageDeviceConfig | undefined): {
41
44
  * is nearly always a file already in the repository — the app icon — so the
42
45
  * callback is just a placement.
43
46
  */
44
- export declare function markPainter(path: string | undefined, root?: string): Promise<((canvas: ImageData, box: { x: number, y: number, size: number }) => void) | undefined>;
45
- /** Fold the shared palette into a generator's own, letting the generator win. */
46
- export declare function themed<T extends ImageTheme>(images: ImagesConfig, section: T | undefined): T & ImageTheme;
47
+ export declare interface MarkPainter {
48
+ draw: (canvas: ImageData, box: { x: number, y: number, size: number, width?: number }) => void
49
+ aspect: number
50
+ }
47
51
  /** The palette keys a generator inherits from the top level of the config. */
48
52
  export declare interface ImageTheme {
49
53
  background?: ImageBackgroundConfig
package/dist/theme.js CHANGED
@@ -1,2 +1,2 @@
1
1
  import{existsSync}from"node:fs";import{readFile}from"node:fs/promises";import{isAbsolute,resolve}from"node:path";import process from"node:process";import{decode,drawImage,parseColor}from"ts-images";export function projectFile(path,root=process.cwd()){return isAbsolute(path)?path:resolve(root,path)}export function requireProjectFile(path,root,describe){const resolved=projectFile(path,root);if(!existsSync(resolved))throw Error(`[image] ${describe} not found: ${path}
2
- Looked in ${resolved}. Capture it before generating \u2014 \`buddy generate:images\` frames existing captures, it does not take them.`);return resolved}export function color(value){return value===void 0?void 0:parseColor(value)}export function background(value,root=process.cwd()){if(!value)return;return{color:color(value.color),gradient:value.gradient&&{angle:value.gradient.angle,stops:value.gradient.stops.map((stop)=>({offset:stop.offset,color:parseColor(stop.color)}))},glows:value.glows?.map((glow)=>({...glow,color:parseColor(glow.color)})),image:value.image?projectFile(value.image,root):void 0}}export function device(value){if(!value)return;return{radius:value.radius,scale:value.scale,borderColor:color(value.borderColor),shadow:value.shadow===!1?void 0:{...value.shadow??{},color:color(value.shadow?.color)}}}export async function markPainter(path,root=process.cwd()){if(!path)return;const mark=await decode(new Uint8Array(await readFile(projectFile(path,root)))),ratio=mark.height>0?mark.width/mark.height:1;return(canvas,box)=>{drawImage(canvas,mark,{x:box.x,y:box.y,width:box.size*ratio,height:box.size,fit:"contain"})}}export function themed(images,section){return{...section??{},background:section?.background??images.background,color:section?.color??images.color,mutedColor:section?.mutedColor??images.mutedColor,device:section?.device??images.device,brand:section?.brand??images.brand,mark:section?.mark??images.mark}}
2
+ Looked in ${resolved}. Capture it before generating \u2014 \`buddy generate:images\` frames existing captures, it does not take them.`);return resolved}export function color(value){return value===void 0?void 0:parseColor(value)}export function background(value,root=process.cwd()){if(!value)return;return{color:color(value.color),gradient:value.gradient&&{angle:value.gradient.angle,stops:value.gradient.stops.map((stop)=>({offset:stop.offset,color:parseColor(stop.color)}))},glows:value.glows?.map((glow)=>({...glow,color:parseColor(glow.color)})),image:value.image?projectFile(value.image,root):void 0}}export function device(value){if(!value)return;return{radius:value.radius,scale:value.scale,borderColor:color(value.borderColor),shadow:value.shadow===!1?void 0:{...value.shadow??{},color:color(value.shadow?.color)}}}export async function markPainter(path,root=process.cwd()){if(!path)return;const mark=await decode(new Uint8Array(await readFile(projectFile(path,root)))),aspect=mark.height>0?mark.width/mark.height:1;return{aspect,draw:(canvas,box)=>{drawImage(canvas,mark,{x:box.x,y:box.y,width:box.width??box.size*aspect,height:box.size,fit:"contain"})}}}export function themed(images,section){return{...section??{},background:section?.background??images.background,color:section?.color??images.color,mutedColor:section?.mutedColor??images.mutedColor,device:section?.device??images.device,brand:section?.brand??images.brand,mark:section?.mark??images.mark}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/image",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.317",
5
+ "version": "0.70.319",
6
6
  "description": "Native responsive image delivery for Stacks.",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -31,8 +31,8 @@
31
31
  "prepublishOnly": "bun run build"
32
32
  },
33
33
  "dependencies": {
34
- "@stacksjs/types": "0.70.317",
35
- "ts-images": "^0.2.7"
34
+ "@stacksjs/types": "0.70.319",
35
+ "ts-images": "^0.2.8"
36
36
  },
37
37
  "devDependencies": {
38
38
  "better-dx": "^0.2.17"