@stacksjs/image 0.70.317 → 0.70.318
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 +1 -1
- package/dist/social.js +1 -1
- package/dist/theme.d.ts +7 -3
- package/dist/theme.js +1 -1
- package/package.json +3 -3
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
|
|
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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)))),
|
|
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.
|
|
5
|
+
"version": "0.70.318",
|
|
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.
|
|
35
|
-
"ts-images": "^0.2.
|
|
34
|
+
"@stacksjs/types": "0.70.318",
|
|
35
|
+
"ts-images": "^0.2.8"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"better-dx": "^0.2.17"
|