@coreframe/scripts 0.1.33 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ //#region assets/contracts.d.ts
2
+ declare const catalogSchemaVersion: 1;
3
+ declare const manifestSchemaVersion: 1;
4
+ type AssetCatalog = {
5
+ schemaVersion: typeof catalogSchemaVersion;
6
+ assets: Record<string, AssetCatalogEntry>;
7
+ };
8
+ type AssetCatalogEntry = {
9
+ source: AssetSource;
10
+ alt: string;
11
+ variantWidths?: number[];
12
+ };
13
+ type AssetSource = {
14
+ digest: string;
15
+ filename: string;
16
+ mediaType: string;
17
+ size: number;
18
+ };
19
+ type AssetManifest = {
20
+ schemaVersion: typeof manifestSchemaVersion;
21
+ assets: Record<string, AssetManifestEntry>;
22
+ };
23
+ type AssetManifestEntry = AssetCatalogEntry & {
24
+ width: number;
25
+ height: number;
26
+ variants: AssetVariant[];
27
+ };
28
+ type AssetVariant = {
29
+ format: "webp";
30
+ width: number;
31
+ height: number;
32
+ path: string;
33
+ size: number;
34
+ hash: string;
35
+ };
36
+ 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";
38
+ message: string;
39
+ assetId?: string;
40
+ file?: string;
41
+ fix?: string;
42
+ };
43
+ type AssetStatus = {
44
+ ok: boolean;
45
+ assets: AssetStatusEntry[];
46
+ problems: AssetProblem[];
47
+ };
48
+ type AssetStatusEntry = {
49
+ assetId: string;
50
+ source: AssetSource;
51
+ variants: AssetVariantStatus[];
52
+ };
53
+ type AssetVariantStatus = AssetVariant & {
54
+ exists: boolean;
55
+ };
56
+ type AssetBuildResult = {
57
+ ok: boolean;
58
+ assets: AssetBuildEntry[];
59
+ fetched: string[];
60
+ problems: AssetProblem[];
61
+ };
62
+ type AssetBuildEntry = {
63
+ assetId: string;
64
+ variants: AssetVariant[];
65
+ };
66
+ type AssetPublishResult = {
67
+ ok: boolean;
68
+ uploaded: PublishedAsset[];
69
+ skipped: PublishedAsset[];
70
+ reportPath: string;
71
+ problems: AssetProblem[];
72
+ };
73
+ type PublishedAsset = {
74
+ assetId: string;
75
+ source: string;
76
+ target: string;
77
+ size: number;
78
+ };
79
+ type LoadedAssetCatalog = {
80
+ catalog: AssetCatalog;
81
+ problems: AssetProblem[];
82
+ };
83
+ type LoadedAssetManifest = {
84
+ manifest: AssetManifest;
85
+ problems: AssetProblem[];
86
+ };
87
+ //#endregion
88
+ export { AssetBuildEntry, AssetBuildResult, AssetCatalog, AssetCatalogEntry, AssetManifest, AssetManifestEntry, AssetProblem, AssetPublishResult, AssetSource, AssetStatus, AssetStatusEntry, AssetVariant, AssetVariantStatus, LoadedAssetCatalog, LoadedAssetManifest, PublishedAsset, catalogSchemaVersion, manifestSchemaVersion };
@@ -0,0 +1 @@
1
+ const e=1,t=1;export{e as catalogSchemaVersion,t as manifestSchemaVersion};
@@ -0,0 +1,35 @@
1
+ //#region assets/credentials.d.ts
2
+ declare const assetCredentialEnvironment: {
3
+ readonly applicationKey: "COREFRAME_ASSETS_B2_APPLICATION_KEY";
4
+ readonly applicationKeyId: "COREFRAME_ASSETS_B2_APPLICATION_KEY_ID";
5
+ };
6
+ type AssetCredentials = {
7
+ applicationKey: string;
8
+ applicationKeyId: string;
9
+ };
10
+ type AssetCredentialStore = {
11
+ get(profile: string): Promise<AssetCredentials | undefined>;
12
+ set(profile: string, credentials: AssetCredentials): Promise<void>;
13
+ };
14
+ declare function systemAssetCredentialStore(): AssetCredentialStore;
15
+ declare function resolveAssetCredentials({
16
+ env,
17
+ profile,
18
+ store
19
+ }: {
20
+ env?: NodeJS.ProcessEnv;
21
+ profile: string;
22
+ store?: AssetCredentialStore;
23
+ }): Promise<AssetCredentials>;
24
+ declare function configureAssetCredentials({
25
+ applicationKey,
26
+ applicationKeyId,
27
+ profile,
28
+ store
29
+ }: AssetCredentials & {
30
+ profile: string;
31
+ store?: AssetCredentialStore;
32
+ }): Promise<void>;
33
+ declare function assertAssetCredentialProfile(profile: string): string;
34
+ //#endregion
35
+ export { AssetCredentialStore, AssetCredentials, assertAssetCredentialProfile, assetCredentialEnvironment, configureAssetCredentials, resolveAssetCredentials, systemAssetCredentialStore };
@@ -0,0 +1 @@
1
+ import{getKeyring as e,getPassword as t,initBackend as n,setPassword as r}from"cross-keychain";const i=`dev.coreframe.assets`,a=new Set([`macos`,`native-macos`,`native-windows`,`secret-service`,`windows`]),o={applicationKey:`COREFRAME_ASSETS_B2_APPLICATION_KEY`,applicationKeyId:`COREFRAME_ASSETS_B2_APPLICATION_KEY_ID`};function s(){let o=!1;async function s(){if(o)return;await n(e=>a.has(e.id));let t=await e();if(!a.has(t.id))throw Error(`No supported operating system credential store is available. Coreframe requires macOS Keychain, Windows Credential Manager, or Linux Secret Service.`);o=!0}return{async get(e){await s();let n=await t(i,e);if(n)try{return f(JSON.parse(n))}catch{throw Error(`The Coreframe asset credential profile "${e}" is invalid. Run: coreframe assets configure ${e}`)}},async set(e,t){await s(),await r(i,e,JSON.stringify(f(t)))}}}async function c({env:e=process.env,profile:t,store:n=s()}){let r=e[o.applicationKeyId],i=e[o.applicationKey];if(r||i){if(!r||!i)throw Error(`${o.applicationKeyId} and ${o.applicationKey} must be provided together.`);return{applicationKey:i,applicationKeyId:r}}let a;try{a=await n.get(t)}catch(e){throw Error(`Coreframe could not access the operating system credential store for asset profile "${t}": ${e.message}`)}if(!a)throw Error(`Coreframe asset credential profile "${t}" is not configured. Run: coreframe assets configure ${t}`);return a}async function l({applicationKey:e,applicationKeyId:t,profile:n,store:r=s()}){d(n);try{await r.set(n,f({applicationKey:e,applicationKeyId:t}))}catch(e){throw Error(`Coreframe could not store asset profile "${n}" in the operating system credential store: ${e.message}`)}}function u(e){return d(e),e}function d(e){if(!/^[a-z0-9][a-z0-9._-]*$/.test(e))throw Error(`Credential profiles must start with a lowercase letter or number and contain only lowercase letters, numbers, dots, underscores, or hyphens.`)}function f(e){if(!e||typeof e!=`object`||typeof e.applicationKeyId!=`string`||!e.applicationKeyId.trim()||typeof e.applicationKey!=`string`||!e.applicationKey.trim())throw Error(`Asset credentials require a non-empty application key ID and application key.`);return{applicationKeyId:e.applicationKeyId.trim(),applicationKey:e.applicationKey}}export{u as assertAssetCredentialProfile,o as assetCredentialEnvironment,l as configureAssetCredentials,c as resolveAssetCredentials,s as systemAssetCredentialStore};
@@ -0,0 +1,32 @@
1
+ import { AssetCatalogEntry, AssetProblem, AssetVariant } from "./contracts.js";
2
+ import { AssetsConfig } from "../config.js";
3
+
4
+ //#region assets/images.d.ts
5
+ declare function buildAssetVariants({
6
+ assetId,
7
+ config,
8
+ cwd,
9
+ entry,
10
+ sourceDimensions,
11
+ sourcePath
12
+ }: {
13
+ assetId: string;
14
+ config: AssetsConfig;
15
+ cwd: string;
16
+ entry: AssetCatalogEntry;
17
+ sourceDimensions: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ sourcePath: string;
22
+ }): Promise<AssetVariant[]>;
23
+ declare function assetVariantWidths(entry: AssetCatalogEntry, sourceWidth: number): number[];
24
+ declare function assertVariantWidths(widths: number[] | undefined, sourceWidth: number): void;
25
+ declare function variantWidthsProblem(assetId: string, entry: AssetCatalogEntry, sourceWidth: number): AssetProblem | undefined;
26
+ declare function readImageDimensions(file: string): Promise<{
27
+ width: number;
28
+ height: number;
29
+ }>;
30
+ declare function mediaTypeFor(file: string): string;
31
+ //#endregion
32
+ export { assertVariantWidths, assetVariantWidths, buildAssetVariants, mediaTypeFor, readImageDimensions, variantWidthsProblem };
@@ -0,0 +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};
@@ -0,0 +1,32 @@
1
+ import { AssetCatalog, AssetCatalogEntry, AssetManifest, AssetManifestEntry, AssetProblem, AssetSource, LoadedAssetCatalog, LoadedAssetManifest } from "./contracts.js";
2
+ import { AssetsConfig } from "../config.js";
3
+
4
+ //#region assets/project.d.ts
5
+ declare const catalogPath: string;
6
+ declare const manifestPath: string;
7
+ declare const assetIdPattern: RegExp;
8
+ declare function readAssetCatalog(cwd?: string, {
9
+ allowMissing
10
+ }?: {
11
+ allowMissing?: boolean;
12
+ }): Promise<LoadedAssetCatalog>;
13
+ declare function readAssetManifest(cwd?: string, {
14
+ allowMissing
15
+ }?: {
16
+ allowMissing?: boolean;
17
+ }): Promise<LoadedAssetManifest>;
18
+ declare function resolveAssetVariantFile(cwd: string, variantPath: string): string;
19
+ declare function resolveAssetsConfig(cwd: string, provided?: AssetsConfig): Promise<AssetsConfig>;
20
+ declare function validateSource(assetId: string, source: AssetSource): AssetProblem | undefined;
21
+ declare function manifestEntryMatchesCatalog(manifest: AssetManifestEntry, catalog: AssetCatalogEntry): boolean;
22
+ declare function isAssetVariantPath(path: string, projectPrefix: string | undefined): boolean;
23
+ declare function findAssetReferences(cwd: string): Promise<Array<{
24
+ assetId: string;
25
+ file: string;
26
+ }>>;
27
+ declare function writeCatalog(cwd: string, catalog: AssetCatalog): Promise<void>;
28
+ declare function writeManifest(cwd: string, manifest: AssetManifest): Promise<void>;
29
+ declare function assertAssetId(id: string): void;
30
+ declare function formatAssetProblems(problems: AssetProblem[]): string;
31
+ //#endregion
32
+ export { assertAssetId, assetIdPattern, catalogPath, findAssetReferences, formatAssetProblems, isAssetVariantPath, manifestEntryMatchesCatalog, manifestPath, readAssetCatalog, readAssetManifest, resolveAssetVariantFile, resolveAssetsConfig, validateSource, writeCatalog, writeManifest };
@@ -0,0 +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(`
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};
@@ -0,0 +1,51 @@
1
+ import { AssetsConfig } from "../config.js";
2
+ import { AssetCredentialStore } from "./credentials.js";
3
+
4
+ //#region assets/storage.d.ts
5
+ type AssetObjectStore = {
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>;
9
+ };
10
+ declare function assetSourceKey(digest: string): string;
11
+ declare function createB2AssetStore({
12
+ config,
13
+ credentialStore,
14
+ env
15
+ }: {
16
+ config: AssetsConfig;
17
+ credentialStore?: AssetCredentialStore;
18
+ env?: NodeJS.ProcessEnv;
19
+ }): AssetObjectStore;
20
+ declare function assetCacheRoot(env?: NodeJS.ProcessEnv): string;
21
+ declare function assetCacheFile(cacheRoot: string, digest: string): string;
22
+ declare function cacheAssetSource({
23
+ body,
24
+ cacheRoot,
25
+ digest,
26
+ env
27
+ }: {
28
+ body: Uint8Array;
29
+ cacheRoot: string;
30
+ digest: string;
31
+ env?: NodeJS.ProcessEnv;
32
+ }): Promise<string>;
33
+ declare function materializeAssetSource({
34
+ bucket,
35
+ cacheRoot,
36
+ digest,
37
+ env,
38
+ store
39
+ }: {
40
+ bucket: string;
41
+ cacheRoot: string;
42
+ digest: string;
43
+ env?: NodeJS.ProcessEnv;
44
+ store: AssetObjectStore;
45
+ }): Promise<{
46
+ fetched: boolean;
47
+ path: string;
48
+ }>;
49
+ declare function sha256(body: Uint8Array): string;
50
+ //#endregion
51
+ export { AssetObjectStore, assetCacheFile, assetCacheRoot, assetSourceKey, cacheAssetSource, createB2AssetStore, materializeAssetSource, sha256 };
@@ -0,0 +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};
@@ -0,0 +1,52 @@
1
+ import { AssetBuildResult, AssetPublishResult, AssetStatus } from "./contracts.js";
2
+ import { AssetsConfig } from "../config.js";
3
+ import { AssetObjectStore } from "./storage.js";
4
+
5
+ //#region assets/workflows.d.ts
6
+ type AssetWorkflowOptions = {
7
+ cacheDir?: string;
8
+ config?: AssetsConfig;
9
+ cwd?: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ store?: AssetObjectStore;
12
+ };
13
+ type AddAssetOptions = AssetWorkflowOptions & {
14
+ file: string;
15
+ id: string;
16
+ alt: string;
17
+ variantWidths?: number[];
18
+ };
19
+ type BuildAssetsOptions = AssetWorkflowOptions & {
20
+ id?: string;
21
+ updateManifest?: boolean;
22
+ };
23
+ type CheckAssetsOptions = AssetWorkflowOptions & {
24
+ fix?: boolean;
25
+ };
26
+ type PublishAssetsOptions = AssetWorkflowOptions;
27
+ declare function addAsset({
28
+ alt,
29
+ cacheDir,
30
+ config,
31
+ cwd,
32
+ env,
33
+ file,
34
+ id,
35
+ store,
36
+ variantWidths
37
+ }: AddAssetOptions): Promise<AssetBuildResult>;
38
+ declare function buildAssets({
39
+ cacheDir,
40
+ config,
41
+ cwd,
42
+ env,
43
+ id,
44
+ store,
45
+ updateManifest
46
+ }?: BuildAssetsOptions): Promise<AssetBuildResult>;
47
+ declare function checkAssets(options?: CheckAssetsOptions): Promise<AssetStatus>;
48
+ declare function publishAssets(options?: PublishAssetsOptions): Promise<AssetPublishResult>;
49
+ declare function materializeAsset(assetId: string, options?: BuildAssetsOptions): Promise<AssetBuildResult>;
50
+ declare function printAssetResult(result: unknown, json?: boolean): void;
51
+ //#endregion
52
+ export { AddAssetOptions, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetsOptions, PublishAssetsOptions, addAsset, buildAssets, checkAssets, materializeAsset, printAssetResult, publishAssets };
@@ -0,0 +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};
package/dist/assets.d.ts CHANGED
@@ -1,118 +1,4 @@
1
- //#region assets.d.ts
2
- type AssetManifest = Record<string, AssetManifestEntry>;
3
- type AssetManifestEntry = {
4
- source: string;
5
- alt: string;
6
- width: number;
7
- height: number;
8
- variantWidths?: number[];
9
- variants: AssetVariant[];
10
- };
11
- type AssetVariant = {
12
- format: "webp";
13
- width: number;
14
- height: number;
15
- path: string;
16
- size: number;
17
- hash: string;
18
- };
19
- type AssetProblem = {
20
- code: "GENERATED_VARIANT_MISSING" | "INVALID_ALT" | "INVALID_ASSET_ID" | "INVALID_MANIFEST" | "INVALID_REFERENCE" | "INVALID_SOURCE_PATH" | "INVALID_VARIANT_PATH" | "INVALID_VARIANT_WIDTHS" | "LFS_BLOB_NOT_POINTER" | "LFS_NOT_TRACKED" | "MANIFEST_MISSING" | "REFERENCE_MISSING" | "SOURCE_MISSING" | "SOURCE_POINTER_MISSING_OBJECT";
21
- message: string;
22
- assetId?: string;
23
- file?: string;
24
- fix?: string;
25
- };
26
- type AssetStatus = {
27
- ok: boolean;
28
- assets: AssetStatusEntry[];
29
- problems: AssetProblem[];
30
- };
31
- type AssetStatusEntry = {
32
- assetId: string;
33
- source: string;
34
- sourceExists: boolean;
35
- lfsTracked: boolean;
36
- variants: AssetVariantStatus[];
37
- };
38
- type AssetVariantStatus = AssetVariant & {
39
- exists: boolean;
40
- };
41
- type AssetBuildResult = {
42
- ok: boolean;
43
- assets: AssetBuildEntry[];
44
- fetched: string[];
45
- problems: AssetProblem[];
46
- };
47
- type AssetBuildEntry = {
48
- assetId: string;
49
- variants: AssetVariant[];
50
- };
51
- type AssetPublishResult = {
52
- ok: boolean;
53
- uploaded: PublishedAsset[];
54
- skipped: PublishedAsset[];
55
- reportPath: string;
56
- problems: AssetProblem[];
57
- };
58
- type PublishedAsset = {
59
- assetId: string;
60
- source: string;
61
- target: string;
62
- size: number;
63
- };
64
- type AssetWorkflowOptions = {
65
- cwd?: string;
66
- json?: boolean;
67
- env?: NodeJS.ProcessEnv;
68
- };
69
- type AddAssetOptions = AssetWorkflowOptions & {
70
- file: string;
71
- id: string;
72
- alt: string;
73
- variantWidths?: number[];
74
- };
75
- type BuildAssetsOptions = AssetWorkflowOptions & {
76
- id?: string;
77
- pull?: boolean;
78
- updateManifest?: boolean;
79
- };
80
- type CheckAssetsOptions = AssetWorkflowOptions & {
81
- fix?: boolean;
82
- };
83
- type PublishAssetsOptions = AssetWorkflowOptions & {
84
- outputDir?: string;
85
- };
86
- type LoadedAssetManifest = {
87
- manifest: AssetManifest;
88
- problems: AssetProblem[];
89
- };
90
- declare function addAsset({
91
- alt,
92
- cwd,
93
- file,
94
- id,
95
- variantWidths
96
- }: AddAssetOptions): Promise<AssetBuildResult>;
97
- declare function buildAssets({
98
- cwd,
99
- id,
100
- pull,
101
- updateManifest
102
- }?: BuildAssetsOptions): Promise<AssetBuildResult>;
103
- declare function checkAssets({
104
- cwd,
105
- fix
106
- }?: CheckAssetsOptions): Promise<AssetStatus>;
107
- declare function publishAssets({
108
- cwd,
109
- env,
110
- outputDir
111
- }?: PublishAssetsOptions): Promise<AssetPublishResult>;
112
- declare function materializeAsset(assetId: string, options?: BuildAssetsOptions): Promise<AssetBuildResult>;
113
- declare function printAssetResult(result: unknown, json?: boolean): void;
114
- declare function readAssetManifest(cwd?: string): Promise<LoadedAssetManifest>;
115
- declare function resolveAssetVariantFile(cwd: string, variantPath: string): string;
116
- declare function formatAssetProblems(problems: AssetProblem[]): string;
117
- //#endregion
118
- export { AddAssetOptions, AssetBuildEntry, AssetBuildResult, AssetManifest, AssetManifestEntry, AssetProblem, AssetPublishResult, AssetStatus, AssetStatusEntry, AssetVariant, AssetVariantStatus, AssetWorkflowOptions, BuildAssetsOptions, CheckAssetsOptions, LoadedAssetManifest, PublishAssetsOptions, PublishedAsset, addAsset, buildAssets, checkAssets, formatAssetProblems, materializeAsset, printAssetResult, publishAssets, readAssetManifest, resolveAssetVariantFile };
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";
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 };
package/dist/assets.js CHANGED
@@ -1,3 +1 @@
1
- import{createHash as e}from"node:crypto";import{copyFile as t,mkdir as n,readFile as r,stat as i,writeFile as a}from"node:fs/promises";import{dirname as o,extname as s,join as c,relative as l,resolve as u,sep as d}from"node:path";import f from"nano-spawn";const p=`assets-src`,m=`assets`,h=[320,640,960,1280,1920,2560],g=h.at(-1),_=c(`assets`,`manifest.json`),v=/^[a-z0-9]+(?:[/-][a-z0-9]+)*$/,y=/@asset\s+([a-z0-9][a-z0-9/-]*)/g,b=/^version https:\/\/git-lfs.github.com\/spec\/v1\n/m,x=/^version https:\/\/git-lfs.github.com\/spec\/v1\n(?:ext-[^\n]+\n)*oid sha256:[0-9a-f]{64}\nsize [0-9]+\n?$/;async function S({alt:e,cwd:r=process.cwd(),file:i,id:a,variantWidths:s}){O(a);let c=M(a,i),l=u(r,c),d=u(r,i),f=await V(d);F(s,f.width),await n(o(l),{recursive:!0}),await t(d,l),await U(r,c);let p=await k(r);return p.manifest[a]={source:c,alt:e,width:f.width,height:f.height,...s?{variantWidths:s}:{},variants:[]},await A(r,p.manifest),C({cwd:r,id:a,pull:!1})}async function C({cwd:e=process.cwd(),id:t,pull:n=!0,updateManifest:r=!0}={}){let{manifest:i,problems:a}=await k(e),o=[],s=[];for(let[r,c]of Object.entries(i)){if(t&&r!==t)continue;let l=I(r,c);if(l){a.push(l);continue}let d=u(e,c.source);if(!await Z(d)){a.push(Q(r,c.source));continue}if(await K(d)&&(n&&(await W(e,c.source),o.push(c.source)),await K(d))){a.push({code:`SOURCE_POINTER_MISSING_OBJECT`,message:`Source asset "${c.source}" is still a Git LFS pointer after a targeted pull.`,assetId:r,file:c.source,fix:`Run: git lfs pull --include="${c.source}"`});continue}let f=await N(e,r,c);i[r]={...c,variants:f},s.push({assetId:r,variants:f})}return t&&!i[t]&&a.push({code:`REFERENCE_MISSING`,message:`Asset "${t}" is not present in ${_}.`,assetId:t,fix:`Run: coreframe assets add <file> --id ${t} --alt "..."`}),r&&a.length===0&&s.length>0&&await A(e,i),{ok:a.length===0,assets:s,fetched:o,problems:a}}async function w({cwd:e=process.cwd(),fix:t=!1}={}){if(t){let t=await C({cwd:e}),n=await w({cwd:e,fix:!1});return{...n,ok:n.ok&&t.ok,problems:[...n.problems,...t.problems]}}let n=await k(e),r=[...n.problems],i=[],a=await Y(e),o=new Map(Object.entries(n.manifest).map(([e,t])=>[t.source,e]));for(let t of await q(e))await G(e,t)&&!await J(e,t)&&r.push({code:`LFS_BLOB_NOT_POINTER`,message:`Source asset "${t}" is covered by Git LFS attributes, but its staged Git blob is not an LFS pointer.`,assetId:o.get(t),file:t,fix:`Run: git add --renormalize -- "${t}"`});for(let[a,o]of Object.entries(n.manifest)){v.test(a)||r.push({code:`INVALID_ASSET_ID`,message:`Asset ID "${a}" must use lowercase path segments.`,assetId:a}),o.alt.trim()||r.push({code:`INVALID_ALT`,message:`Asset "${a}" is missing alt text.`,assetId:a,file:o.source,fix:`Run: coreframe assets add ${o.source} --id ${a} --alt "..."`}),o.source.startsWith(`${p}/`)||r.push({code:`INVALID_SOURCE_PATH`,message:`Asset "${a}" source must be under ${p}/.`,assetId:a,file:o.source});let n=I(a,o);n&&r.push(n);let s=await Z(u(e,o.source)),c=s?await G(e,o.source):!1;s?c||r.push({code:`LFS_NOT_TRACKED`,message:`Source asset "${o.source}" is not tracked by Git LFS.`,assetId:a,file:o.source,fix:`Run: git lfs track "${o.source}" && git add .gitattributes ${o.source}`}):r.push(Q(a,o.source));let l=await Promise.all(o.variants.map(async n=>{let i=B(n.path);return i||r.push({code:`INVALID_VARIANT_PATH`,message:`Asset "${a}" variant path must be under ${m}/.`,assetId:a,file:n.path,fix:t?void 0:`Run: coreframe assets build --id ${a}`}),{...n,exists:i&&await Z(R(e,n.path))}})),d=P(o),f=o.variants.map(e=>e.width),h=d.length===f.length&&d.every((e,t)=>e===f[t]);!n&&(!h||l.some(e=>!e.exists))&&r.push({code:`GENERATED_VARIANT_MISSING`,message:`Asset "${a}" is missing its expected responsive variants.`,assetId:a,file:o.source,fix:t?void 0:`Run: coreframe assets build --id ${a}`}),i.push({assetId:a,source:o.source,sourceExists:s,lfsTracked:c,variants:l})}for(let e of a)n.manifest[e.assetId]||r.push({code:`INVALID_REFERENCE`,message:`Asset reference "${e.assetId}" is not present in ${_}.`,assetId:e.assetId,file:e.file,fix:`Run: coreframe assets add <file> --id ${e.assetId} --alt "..."`});return{ok:r.length===0,assets:i,problems:r}}async function T({cwd:e=process.cwd(),env:r=process.env,outputDir:i}={}){let s=[...(await w({cwd:e})).problems],f=[],p=[],m=u(e,i??r.COREFRAME_ASSETS_PUBLISH_DIR??`release-upload`),h=c(m,`asset-publish-report.json`);if(s.length===0){let{manifest:r}=await k(e);for(let[i,a]of Object.entries(r))for(let r of a.variants){let a=R(e,r.path),s=z(m,r.path),c={assetId:i,source:l(e,a).split(d).join(`/`),target:s,size:r.size};if(await Z(s)){p.push(c);continue}await n(o(s),{recursive:!0}),await t(a,s),f.push(c)}}let g={ok:s.length===0,uploaded:f,skipped:p,reportPath:h,problems:s};return await n(o(h),{recursive:!0}),await a(h,`${JSON.stringify(g,null,2)}\n`),g}async function E(e,t={}){return C({...t,id:e})}function D(e,t=!1){if(t){console.log(JSON.stringify(e,null,2));return}let n=e;n.problems&&n.problems.length>0&&console.error($(n.problems)),console.log(n.ok===!1?`Asset workflow has problems.`:`Asset workflow complete.`)}function O(e){if(!v.test(e))throw Error(`Asset ID "${e}" must use lowercase path segments.`)}async function k(e=process.cwd()){let t=u(e,_);if(!await Z(t))return{manifest:{},problems:[{code:`MANIFEST_MISSING`,message:`${_} does not exist.`,file:_,fix:`Run: coreframe assets add <file> --id <asset-id> --alt "..."`}]};try{return{manifest:JSON.parse(await r(t,`utf8`)),problems:[]}}catch(e){return{manifest:{},problems:[{code:`INVALID_MANIFEST`,message:`${_} is not valid JSON: ${e.message}`,file:_}]}}}async function A(e,t){let r=u(e,_);await n(o(r),{recursive:!0}),await a(r,`${JSON.stringify(j(t),null,2)}\n`)}function j(e){return Object.fromEntries(Object.entries(e).sort(([e],[t])=>e.localeCompare(t)))}function M(e,t){return c(p,`${e}${s(t).toLowerCase()}`).split(d).join(`/`)}async function N(t,r,i){let s=await H(),c=u(t,i.source);return Promise.all(P(i).map(async l=>{let u=await s(c).resize({width:l,withoutEnlargement:!0}).webp().toBuffer(),d=e(`sha256`).update(u).digest(`hex`).slice(0,12),f=`${m}/${r}.${d}.${l}.webp`,p=R(t,f);await n(o(p),{recursive:!0}),await a(p,u);let h=await s(u).metadata();return{format:`webp`,width:h.width??l,height:h.height??Math.round(i.height/i.width*l),path:f,size:u.byteLength,hash:d}}))}function P(e){if(e.variantWidths)return e.variantWidths;let t=e.width;if(!Number.isFinite(t)||t<=0)return[];let n=Math.min(Math.round(t),g);return[...h.filter(e=>e<=n),n].filter((e,t,n)=>t===0||e!==n[t-1])}function F(e,t){let n=L(e,t);if(n)throw Error(`Invalid variant widths: ${n}`)}function I(e,t){let n=L(t.variantWidths,t.width);if(n)return{code:`INVALID_VARIANT_WIDTHS`,message:`Asset "${e}" has invalid variantWidths: ${n}`,assetId:e,file:_,fix:`Edit ${_} so variantWidths contains positive, strictly ascending integer widths no larger than ${t.width}, then run: coreframe assets build --id ${e}`}}function L(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}.`}}}function R(e,t){return z(u(e,`.coreframe`),t)}function z(e,t){if(!B(t))throw Error(`Asset variant path must be under ${m}/: ${t}`);return u(e,t)}function B(e){let t=e.split(`/`);return t[0]===m&&t.length>1&&!e.includes(`\\`)&&t.every(e=>e!==``&&e!==`.`&&e!==`..`)}async function V(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.`)}}async function U(e,t){await f(`git`,[`lfs`,`track`,t],{cwd:e})}async function W(e,t){await f(`git`,[`lfs`,`pull`,`--include=${t}`],{cwd:e})}async function G(e,t){try{return(await f(`git`,[`check-attr`,`filter`,`--`,t],{cwd:e})).stdout.includes(`filter: lfs`)}catch{return!1}}async function K(e){try{let t=await r(e,`utf8`);return b.test(t)}catch{return!1}}async function q(e){return(await f(`git`,[`ls-files`,`-z`,`--`,p],{cwd:e})).stdout.split(`\0`).filter(e=>e.startsWith(`${p}/`))}async function J(e,t){try{let n=`:${t}`,r=Number((await f(`git`,[`cat-file`,`-s`,n],{cwd:e})).stdout);if(!Number.isSafeInteger(r)||r>1024)return!1;let i=(await f(`git`,[`cat-file`,`-p`,n],{cwd:e})).stdout;return x.test(i)}catch{return!1}}async function Y(e){let t=await X(e),n=[];for(let i of t){let t=u(e,i);if(!await Z(t))continue;let a=await r(t,`utf8`);for(let e of a.matchAll(y))n.push({assetId:e[1],file:i})}return n}async function X(e){return(await f(`git`,[`ls-files`,`*.md`,`*.mdx`,`*.ts`,`*.tsx`],{cwd:e})).stdout.split(/\r?\n/).map(e=>e.trim()).filter(Boolean).filter(e=>e.startsWith(`content/`)||e.startsWith(`src/`))}async function Z(e){try{return await i(e),!0}catch{return!1}}function Q(e,t){return{code:`SOURCE_MISSING`,message:`Source asset "${t}" does not exist.`,assetId:e,file:t,fix:`Run: coreframe assets add <file> --id ${e} --alt "..."`}}function $(e){return e.map(e=>[`${e.code}: ${e.message}`,e.file?` file: ${l(process.cwd(),u(e.file))}`:void 0,e.fix?` fix: ${e.fix}`:void 0].filter(Boolean).join(`
2
- `)).join(`
3
- `)}export{S as addAsset,C as buildAssets,w as checkAssets,$ as formatAssetProblems,E as materializeAsset,D as printAssetResult,T as publishAssets,k as readAssetManifest,R as resolveAssetVariantFile};
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};
package/dist/config.d.ts CHANGED
@@ -5,11 +5,21 @@ import { WorkerConfig } from "./worker-environment.js";
5
5
 
6
6
  //#region config.d.ts
7
7
  type CoreframeConfig = {
8
+ assets?: AssetsConfig;
8
9
  database?: DatabaseConfig;
9
10
  deploy?: DeployConfig;
10
11
  dev?: DevConfig;
11
12
  worker?: WorkerConfig;
12
13
  };
14
+ type AssetsConfig = {
15
+ credentialProfile: string;
16
+ endpoint: string;
17
+ projectId: string;
18
+ publicBaseUrl: string;
19
+ publicBucket: string;
20
+ region: string;
21
+ sourceBucket: string;
22
+ };
13
23
  type DevConfig = {
14
24
  platforms?: DevPlatform[];
15
25
  };
@@ -24,4 +34,4 @@ declare function readProjectConfig({
24
34
  cwd?: string;
25
35
  }): Promise<CoreframeConfig>;
26
36
  //#endregion
27
- export { CoreframeConfig, DatabaseConfig, DevConfig, defineConfig, readProjectConfig };
37
+ export { AssetsConfig, CoreframeConfig, DatabaseConfig, DevConfig, defineConfig, readProjectConfig };