@pro-laico/payload-fonts 0.0.0
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/LICENSE.md +22 -0
- package/README.md +19 -0
- package/dist/access/authd.d.ts +19 -0
- package/dist/access/authd.d.ts.map +1 -0
- package/dist/access/authd.js +18 -0
- package/dist/access/authd.js.map +1 -0
- package/dist/collections/font.d.ts +29 -0
- package/dist/collections/font.d.ts.map +1 -0
- package/dist/collections/font.js +327 -0
- package/dist/collections/font.js.map +1 -0
- package/dist/collections/fontOptimized.d.ts +20 -0
- package/dist/collections/fontOptimized.d.ts.map +1 -0
- package/dist/collections/fontOptimized.js +111 -0
- package/dist/collections/fontOptimized.js.map +1 -0
- package/dist/collections/fontOriginal.d.ts +25 -0
- package/dist/collections/fontOriginal.d.ts.map +1 -0
- package/dist/collections/fontOriginal.js +49 -0
- package/dist/collections/fontOriginal.js.map +1 -0
- package/dist/components/DevFonts.d.ts +52 -0
- package/dist/components/DevFonts.d.ts.map +1 -0
- package/dist/components/DevFonts.js +75 -0
- package/dist/components/DevFonts.js.map +1 -0
- package/dist/components/admin/FontOriginalUpload.d.ts +15 -0
- package/dist/components/admin/FontOriginalUpload.d.ts.map +1 -0
- package/dist/components/admin/FontOriginalUpload.js +22 -0
- package/dist/components/admin/FontOriginalUpload.js.map +1 -0
- package/dist/components/admin/FontOriginalUpload.scss +9 -0
- package/dist/endpoints/exportFonts.d.ts +49 -0
- package/dist/endpoints/exportFonts.d.ts.map +1 -0
- package/dist/endpoints/exportFonts.js +162 -0
- package/dist/endpoints/exportFonts.js.map +1 -0
- package/dist/extractFonts.d.ts +20 -0
- package/dist/extractFonts.d.ts.map +1 -0
- package/dist/extractFonts.js +18 -0
- package/dist/extractFonts.js.map +1 -0
- package/dist/fields/font.d.ts +14 -0
- package/dist/fields/font.d.ts.map +1 -0
- package/dist/fields/font.js +37 -0
- package/dist/fields/font.js.map +1 -0
- package/dist/globals/fontSet.d.ts +15 -0
- package/dist/globals/fontSet.d.ts.map +1 -0
- package/dist/globals/fontSet.js +25 -0
- package/dist/globals/fontSet.js.map +1 -0
- package/dist/hooks/optimizeFont.d.ts +56 -0
- package/dist/hooks/optimizeFont.d.ts.map +1 -0
- package/dist/hooks/optimizeFont.js +185 -0
- package/dist/hooks/optimizeFont.js.map +1 -0
- package/dist/hooks/optimizeFromOriginals.d.ts +43 -0
- package/dist/hooks/optimizeFromOriginals.d.ts.map +1 -0
- package/dist/hooks/optimizeFromOriginals.js +313 -0
- package/dist/hooks/optimizeFromOriginals.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/activeFonts.d.ts +64 -0
- package/dist/lib/activeFonts.d.ts.map +1 -0
- package/dist/lib/activeFonts.js +139 -0
- package/dist/lib/activeFonts.js.map +1 -0
- package/dist/lib/families.d.ts +38 -0
- package/dist/lib/families.d.ts.map +1 -0
- package/dist/lib/families.js +44 -0
- package/dist/lib/families.js.map +1 -0
- package/dist/lib/mergeConfig.d.ts +28 -0
- package/dist/lib/mergeConfig.d.ts.map +1 -0
- package/dist/lib/mergeConfig.js +73 -0
- package/dist/lib/mergeConfig.js.map +1 -0
- package/dist/lib/refs.d.ts +8 -0
- package/dist/lib/refs.d.ts.map +1 -0
- package/dist/lib/refs.js +11 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/lib/uploadBytes.d.ts +26 -0
- package/dist/lib/uploadBytes.d.ts.map +1 -0
- package/dist/lib/uploadBytes.js +98 -0
- package/dist/lib/uploadBytes.js.map +1 -0
- package/dist/plugin.d.ts +57 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +93 -0
- package/dist/plugin.js.map +1 -0
- package/dist/scripts/cli.d.ts +3 -0
- package/dist/scripts/cli.d.ts.map +1 -0
- package/dist/scripts/cli.js +13 -0
- package/dist/scripts/cli.js.map +1 -0
- package/dist/scripts/downloadFonts.d.ts +37 -0
- package/dist/scripts/downloadFonts.d.ts.map +1 -0
- package/dist/scripts/downloadFonts.js +216 -0
- package/dist/scripts/downloadFonts.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The font *families* (slots) the plugin exposes — `sans / serif / mono / display` by default,
|
|
3
|
+
* but fully replaceable/extendable via `fontsPlugin({ families })`. A family flows through the whole
|
|
4
|
+
* stack: it's an option on the `font` collection's `family` field, a relationship slot on the
|
|
5
|
+
* `fontSet` global, a key in the `/api/fonts/export` JSON, a generated `next/font/local` export
|
|
6
|
+
* (`font<Key>`), and a CSS family variable (`--font-set<Key>`). Centralised here so every consumer
|
|
7
|
+
* resolves the SAME list and naming.
|
|
8
|
+
*/
|
|
9
|
+
/** A family as the consumer declares it. Only `key` is required; the rest default by convention. */
|
|
10
|
+
export interface FontFamilyConfig {
|
|
11
|
+
/** Stable identifier. Used as the `family` value, the `fontSet` slot name, the export JSON key,
|
|
12
|
+
* and (capitalised) the generated `font<Key>` export + `--font-set<Key>` CSS variable. */
|
|
13
|
+
key: string;
|
|
14
|
+
/** Admin label for the `family` radio option and the `fontSet` slot. @default capitalised `key` */
|
|
15
|
+
label?: string;
|
|
16
|
+
/** CSS fallback stack appended after the served family in the family variable (dev preview /
|
|
17
|
+
* custom serving). @default `'ui-sans-serif, system-ui, sans-serif'` */
|
|
18
|
+
fallback?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A family with every field resolved — what the internals actually consume. */
|
|
21
|
+
export interface ResolvedFontFamily {
|
|
22
|
+
key: string;
|
|
23
|
+
label: string;
|
|
24
|
+
fallback: string;
|
|
25
|
+
}
|
|
26
|
+
/** The built-in families, used when `fontsPlugin` is called without `families`. */
|
|
27
|
+
export declare const DEFAULT_FONT_FAMILIES: ResolvedFontFamily[];
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a `families` option to a complete list — applying the convention defaults (label =
|
|
30
|
+
* capitalised key, fallback = generic sans stack). Passing `undefined` yields {@link
|
|
31
|
+
* DEFAULT_FONT_FAMILIES}; passing `[]` is the caller's explicit choice of no families.
|
|
32
|
+
*/
|
|
33
|
+
export declare const resolveFontFamilies: (families?: FontFamilyConfig[]) => ResolvedFontFamily[];
|
|
34
|
+
/** Generated `next/font/local` export name for a family key (`sans` → `fontSans`). */
|
|
35
|
+
export declare const familyExportName: (key: string) => string;
|
|
36
|
+
/** CSS family-variable suffix for a family key (`sans` → `Sans`, used as `<prefix>Sans`). */
|
|
37
|
+
export declare const familyVarSuffix: (key: string) => string;
|
|
38
|
+
//# sourceMappingURL=families.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"families.d.ts","sourceRoot":"","sources":["../../src/lib/families.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oGAAoG;AACpG,MAAM,WAAW,gBAAgB;IAC/B;+FAC2F;IAC3F,GAAG,EAAE,MAAM,CAAA;IACX,mGAAmG;IACnG,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;6EACyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAOD,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,EAAE,kBAAkB,EAKrD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,gBAAgB,EAAE,KAAG,kBAAkB,EAG6B,CAAA;AAEnH,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,MAA2B,CAAA;AAE1E,6FAA6F;AAC7F,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,MAAkB,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The font *families* (slots) the plugin exposes — `sans / serif / mono / display` by default,
|
|
3
|
+
* but fully replaceable/extendable via `fontsPlugin({ families })`. A family flows through the whole
|
|
4
|
+
* stack: it's an option on the `font` collection's `family` field, a relationship slot on the
|
|
5
|
+
* `fontSet` global, a key in the `/api/fonts/export` JSON, a generated `next/font/local` export
|
|
6
|
+
* (`font<Key>`), and a CSS family variable (`--font-set<Key>`). Centralised here so every consumer
|
|
7
|
+
* resolves the SAME list and naming.
|
|
8
|
+
*/ /** A family as the consumer declares it. Only `key` is required; the rest default by convention. */ const cap = (s)=>s.charAt(0).toUpperCase() + s.slice(1);
|
|
9
|
+
/** Generic CSS fallback for a family with none declared. */ const DEFAULT_FALLBACK = 'ui-sans-serif, system-ui, sans-serif';
|
|
10
|
+
/** The built-in families, used when `fontsPlugin` is called without `families`. */ export const DEFAULT_FONT_FAMILIES = [
|
|
11
|
+
{
|
|
12
|
+
key: 'sans',
|
|
13
|
+
label: 'Sans',
|
|
14
|
+
fallback: 'ui-sans-serif, system-ui, sans-serif'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: 'serif',
|
|
18
|
+
label: 'Serif',
|
|
19
|
+
fallback: 'ui-serif, Georgia, serif'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'mono',
|
|
23
|
+
label: 'Mono',
|
|
24
|
+
fallback: 'ui-monospace, SFMono-Regular, monospace'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'display',
|
|
28
|
+
label: 'Display',
|
|
29
|
+
fallback: 'ui-serif, Georgia, serif'
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a `families` option to a complete list — applying the convention defaults (label =
|
|
34
|
+
* capitalised key, fallback = generic sans stack). Passing `undefined` yields {@link
|
|
35
|
+
* DEFAULT_FONT_FAMILIES}; passing `[]` is the caller's explicit choice of no families.
|
|
36
|
+
*/ export const resolveFontFamilies = (families)=>families === undefined ? DEFAULT_FONT_FAMILIES : families.map((r)=>({
|
|
37
|
+
key: r.key,
|
|
38
|
+
label: r.label ?? cap(r.key),
|
|
39
|
+
fallback: r.fallback ?? DEFAULT_FALLBACK
|
|
40
|
+
}));
|
|
41
|
+
/** Generated `next/font/local` export name for a family key (`sans` → `fontSans`). */ export const familyExportName = (key)=>`font${cap(key)}`;
|
|
42
|
+
/** CSS family-variable suffix for a family key (`sans` → `Sans`, used as `<prefix>Sans`). */ export const familyVarSuffix = (key)=>cap(key);
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=families.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/families.ts"],"sourcesContent":["/**\n * The font *families* (slots) the plugin exposes — `sans / serif / mono / display` by default,\n * but fully replaceable/extendable via `fontsPlugin({ families })`. A family flows through the whole\n * stack: it's an option on the `font` collection's `family` field, a relationship slot on the\n * `fontSet` global, a key in the `/api/fonts/export` JSON, a generated `next/font/local` export\n * (`font<Key>`), and a CSS family variable (`--font-set<Key>`). Centralised here so every consumer\n * resolves the SAME list and naming.\n */\n\n/** A family as the consumer declares it. Only `key` is required; the rest default by convention. */\nexport interface FontFamilyConfig {\n /** Stable identifier. Used as the `family` value, the `fontSet` slot name, the export JSON key,\n * and (capitalised) the generated `font<Key>` export + `--font-set<Key>` CSS variable. */\n key: string\n /** Admin label for the `family` radio option and the `fontSet` slot. @default capitalised `key` */\n label?: string\n /** CSS fallback stack appended after the served family in the family variable (dev preview /\n * custom serving). @default `'ui-sans-serif, system-ui, sans-serif'` */\n fallback?: string\n}\n\n/** A family with every field resolved — what the internals actually consume. */\nexport interface ResolvedFontFamily {\n key: string\n label: string\n fallback: string\n}\n\nconst cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1)\n\n/** Generic CSS fallback for a family with none declared. */\nconst DEFAULT_FALLBACK = 'ui-sans-serif, system-ui, sans-serif'\n\n/** The built-in families, used when `fontsPlugin` is called without `families`. */\nexport const DEFAULT_FONT_FAMILIES: ResolvedFontFamily[] = [\n { key: 'sans', label: 'Sans', fallback: 'ui-sans-serif, system-ui, sans-serif' },\n { key: 'serif', label: 'Serif', fallback: 'ui-serif, Georgia, serif' },\n { key: 'mono', label: 'Mono', fallback: 'ui-monospace, SFMono-Regular, monospace' },\n { key: 'display', label: 'Display', fallback: 'ui-serif, Georgia, serif' },\n]\n\n/**\n * Resolve a `families` option to a complete list — applying the convention defaults (label =\n * capitalised key, fallback = generic sans stack). Passing `undefined` yields {@link\n * DEFAULT_FONT_FAMILIES}; passing `[]` is the caller's explicit choice of no families.\n */\nexport const resolveFontFamilies = (families?: FontFamilyConfig[]): ResolvedFontFamily[] =>\n families === undefined\n ? DEFAULT_FONT_FAMILIES\n : families.map((r) => ({ key: r.key, label: r.label ?? cap(r.key), fallback: r.fallback ?? DEFAULT_FALLBACK }))\n\n/** Generated `next/font/local` export name for a family key (`sans` → `fontSans`). */\nexport const familyExportName = (key: string): string => `font${cap(key)}`\n\n/** CSS family-variable suffix for a family key (`sans` → `Sans`, used as `<prefix>Sans`). */\nexport const familyVarSuffix = (key: string): string => cap(key)\n"],"names":["cap","s","charAt","toUpperCase","slice","DEFAULT_FALLBACK","DEFAULT_FONT_FAMILIES","key","label","fallback","resolveFontFamilies","families","undefined","map","r","familyExportName","familyVarSuffix"],"mappings":"AAAA;;;;;;;CAOC,GAED,kGAAkG,GAmBlG,MAAMA,MAAM,CAACC,IAAsBA,EAAEC,MAAM,CAAC,GAAGC,WAAW,KAAKF,EAAEG,KAAK,CAAC;AAEvE,0DAA0D,GAC1D,MAAMC,mBAAmB;AAEzB,iFAAiF,GACjF,OAAO,MAAMC,wBAA8C;IACzD;QAAEC,KAAK;QAAQC,OAAO;QAAQC,UAAU;IAAuC;IAC/E;QAAEF,KAAK;QAASC,OAAO;QAASC,UAAU;IAA2B;IACrE;QAAEF,KAAK;QAAQC,OAAO;QAAQC,UAAU;IAA0C;IAClF;QAAEF,KAAK;QAAWC,OAAO;QAAWC,UAAU;IAA2B;CAC1E,CAAA;AAED;;;;CAIC,GACD,OAAO,MAAMC,sBAAsB,CAACC,WAClCA,aAAaC,YACTN,wBACAK,SAASE,GAAG,CAAC,CAACC,IAAO,CAAA;YAAEP,KAAKO,EAAEP,GAAG;YAAEC,OAAOM,EAAEN,KAAK,IAAIR,IAAIc,EAAEP,GAAG;YAAGE,UAAUK,EAAEL,QAAQ,IAAIJ;QAAiB,CAAA,GAAG;AAEnH,oFAAoF,GACpF,OAAO,MAAMU,mBAAmB,CAACR,MAAwB,CAAC,IAAI,EAAEP,IAAIO,MAAM,CAAA;AAE1E,2FAA2F,GAC3F,OAAO,MAAMS,kBAAkB,CAACT,MAAwBP,IAAIO,KAAI"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CollectionConfig, GlobalConfig } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Additively merge two Payload hooks objects: each per-phase array in `extra` is
|
|
4
|
+
* appended to the corresponding array in `base`. Phases present in only one of the
|
|
5
|
+
* two are preserved as-is. User hooks always run AFTER base hooks within a phase.
|
|
6
|
+
*
|
|
7
|
+
* Vendored (with `mergeCollection` / `mergeGlobal` below) so this package carries no
|
|
8
|
+
* dependency on a shared `core` utility package — it's ~40 lines and stable.
|
|
9
|
+
*/
|
|
10
|
+
export declare const mergeHooks: <T>(base: T, extra?: T) => T;
|
|
11
|
+
/**
|
|
12
|
+
* Deep-merge a partial override onto a base `CollectionConfig` without clobbering the
|
|
13
|
+
* nested config a top-level spread would replace. Top-level keys replace, but:
|
|
14
|
+
* - `access` / `admin` are shallow-merged (override keys win, base keys kept);
|
|
15
|
+
* - `fields` are APPENDED (base fields first, then override fields);
|
|
16
|
+
* - `upload` is shallow-merged when both sides are objects (so a partial
|
|
17
|
+
* `upload: { staticDir }` keeps the base `mimeTypes` whitelist);
|
|
18
|
+
* - `hooks` are merged per-phase via {@link mergeHooks} (override hooks run after base).
|
|
19
|
+
*
|
|
20
|
+
* Returns `base` unchanged when `override` is undefined.
|
|
21
|
+
*/
|
|
22
|
+
export declare const mergeCollection: (base: CollectionConfig, override?: Partial<CollectionConfig>) => CollectionConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Deep-merge a partial override onto a base `GlobalConfig`, mirroring {@link mergeCollection}
|
|
25
|
+
* (globals have no `upload`). Returns `base` unchanged when `override` is undefined.
|
|
26
|
+
*/
|
|
27
|
+
export declare const mergeGlobal: (base: GlobalConfig, override?: Partial<GlobalConfig>) => GlobalConfig;
|
|
28
|
+
//# sourceMappingURL=mergeConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeConfig.d.ts","sourceRoot":"","sources":["../../src/lib/mergeConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAG,CAOlD,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,gBAAgB,EAAE,WAAW,OAAO,CAAC,gBAAgB,CAAC,KAAG,gBAcrF,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,YAAY,EAAE,WAAW,OAAO,CAAC,YAAY,CAAC,KAAG,YAUzE,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additively merge two Payload hooks objects: each per-phase array in `extra` is
|
|
3
|
+
* appended to the corresponding array in `base`. Phases present in only one of the
|
|
4
|
+
* two are preserved as-is. User hooks always run AFTER base hooks within a phase.
|
|
5
|
+
*
|
|
6
|
+
* Vendored (with `mergeCollection` / `mergeGlobal` below) so this package carries no
|
|
7
|
+
* dependency on a shared `core` utility package — it's ~40 lines and stable.
|
|
8
|
+
*/ export const mergeHooks = (base, extra)=>{
|
|
9
|
+
if (!extra) return base;
|
|
10
|
+
const b = base;
|
|
11
|
+
const e = extra;
|
|
12
|
+
const out = {
|
|
13
|
+
...b
|
|
14
|
+
};
|
|
15
|
+
for (const key of Object.keys(e))out[key] = [
|
|
16
|
+
...b[key] ?? [],
|
|
17
|
+
...e[key] ?? []
|
|
18
|
+
];
|
|
19
|
+
return out;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Deep-merge a partial override onto a base `CollectionConfig` without clobbering the
|
|
23
|
+
* nested config a top-level spread would replace. Top-level keys replace, but:
|
|
24
|
+
* - `access` / `admin` are shallow-merged (override keys win, base keys kept);
|
|
25
|
+
* - `fields` are APPENDED (base fields first, then override fields);
|
|
26
|
+
* - `upload` is shallow-merged when both sides are objects (so a partial
|
|
27
|
+
* `upload: { staticDir }` keeps the base `mimeTypes` whitelist);
|
|
28
|
+
* - `hooks` are merged per-phase via {@link mergeHooks} (override hooks run after base).
|
|
29
|
+
*
|
|
30
|
+
* Returns `base` unchanged when `override` is undefined.
|
|
31
|
+
*/ export const mergeCollection = (base, override)=>override ? {
|
|
32
|
+
...base,
|
|
33
|
+
...override,
|
|
34
|
+
access: {
|
|
35
|
+
...base.access,
|
|
36
|
+
...override.access
|
|
37
|
+
},
|
|
38
|
+
admin: {
|
|
39
|
+
...base.admin,
|
|
40
|
+
...override.admin
|
|
41
|
+
},
|
|
42
|
+
fields: [
|
|
43
|
+
...base.fields,
|
|
44
|
+
...override.fields ?? []
|
|
45
|
+
],
|
|
46
|
+
upload: override.upload && typeof override.upload === 'object' && typeof base.upload === 'object' ? {
|
|
47
|
+
...base.upload,
|
|
48
|
+
...override.upload
|
|
49
|
+
} : override.upload ?? base.upload,
|
|
50
|
+
hooks: override.hooks ? mergeHooks(base.hooks ?? {}, override.hooks) : base.hooks
|
|
51
|
+
} : base;
|
|
52
|
+
/**
|
|
53
|
+
* Deep-merge a partial override onto a base `GlobalConfig`, mirroring {@link mergeCollection}
|
|
54
|
+
* (globals have no `upload`). Returns `base` unchanged when `override` is undefined.
|
|
55
|
+
*/ export const mergeGlobal = (base, override)=>override ? {
|
|
56
|
+
...base,
|
|
57
|
+
...override,
|
|
58
|
+
access: {
|
|
59
|
+
...base.access,
|
|
60
|
+
...override.access
|
|
61
|
+
},
|
|
62
|
+
admin: {
|
|
63
|
+
...base.admin,
|
|
64
|
+
...override.admin
|
|
65
|
+
},
|
|
66
|
+
fields: [
|
|
67
|
+
...base.fields,
|
|
68
|
+
...override.fields ?? []
|
|
69
|
+
],
|
|
70
|
+
hooks: override.hooks ? mergeHooks(base.hooks ?? {}, override.hooks) : base.hooks
|
|
71
|
+
} : base;
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=mergeConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/mergeConfig.ts"],"sourcesContent":["import type { CollectionConfig, GlobalConfig } from 'payload'\n\n/**\n * Additively merge two Payload hooks objects: each per-phase array in `extra` is\n * appended to the corresponding array in `base`. Phases present in only one of the\n * two are preserved as-is. User hooks always run AFTER base hooks within a phase.\n *\n * Vendored (with `mergeCollection` / `mergeGlobal` below) so this package carries no\n * dependency on a shared `core` utility package — it's ~40 lines and stable.\n */\nexport const mergeHooks = <T>(base: T, extra?: T): T => {\n if (!extra) return base\n const b = base as unknown as Record<string, unknown[] | undefined>\n const e = extra as unknown as Record<string, unknown[] | undefined>\n const out: Record<string, unknown[]> = { ...b } as Record<string, unknown[]>\n for (const key of Object.keys(e)) out[key] = [...(b[key] ?? []), ...(e[key] ?? [])]\n return out as unknown as T\n}\n\n/**\n * Deep-merge a partial override onto a base `CollectionConfig` without clobbering the\n * nested config a top-level spread would replace. Top-level keys replace, but:\n * - `access` / `admin` are shallow-merged (override keys win, base keys kept);\n * - `fields` are APPENDED (base fields first, then override fields);\n * - `upload` is shallow-merged when both sides are objects (so a partial\n * `upload: { staticDir }` keeps the base `mimeTypes` whitelist);\n * - `hooks` are merged per-phase via {@link mergeHooks} (override hooks run after base).\n *\n * Returns `base` unchanged when `override` is undefined.\n */\nexport const mergeCollection = (base: CollectionConfig, override?: Partial<CollectionConfig>): CollectionConfig =>\n override\n ? {\n ...base,\n ...override,\n access: { ...base.access, ...override.access },\n admin: { ...base.admin, ...override.admin },\n fields: [...base.fields, ...(override.fields ?? [])],\n upload:\n override.upload && typeof override.upload === 'object' && typeof base.upload === 'object'\n ? { ...base.upload, ...override.upload }\n : (override.upload ?? base.upload),\n hooks: override.hooks ? mergeHooks(base.hooks ?? {}, override.hooks) : base.hooks,\n }\n : base\n\n/**\n * Deep-merge a partial override onto a base `GlobalConfig`, mirroring {@link mergeCollection}\n * (globals have no `upload`). Returns `base` unchanged when `override` is undefined.\n */\nexport const mergeGlobal = (base: GlobalConfig, override?: Partial<GlobalConfig>): GlobalConfig =>\n override\n ? {\n ...base,\n ...override,\n access: { ...base.access, ...override.access },\n admin: { ...base.admin, ...override.admin },\n fields: [...base.fields, ...(override.fields ?? [])],\n hooks: override.hooks ? mergeHooks(base.hooks ?? {}, override.hooks) : base.hooks,\n }\n : base\n"],"names":["mergeHooks","base","extra","b","e","out","key","Object","keys","mergeCollection","override","access","admin","fields","upload","hooks","mergeGlobal"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,aAAa,CAAIC,MAASC;IACrC,IAAI,CAACA,OAAO,OAAOD;IACnB,MAAME,IAAIF;IACV,MAAMG,IAAIF;IACV,MAAMG,MAAiC;QAAE,GAAGF,CAAC;IAAC;IAC9C,KAAK,MAAMG,OAAOC,OAAOC,IAAI,CAACJ,GAAIC,GAAG,CAACC,IAAI,GAAG;WAAKH,CAAC,CAACG,IAAI,IAAI,EAAE;WAAOF,CAAC,CAACE,IAAI,IAAI,EAAE;KAAE;IACnF,OAAOD;AACT,EAAC;AAED;;;;;;;;;;CAUC,GACD,OAAO,MAAMI,kBAAkB,CAACR,MAAwBS,WACtDA,WACI;QACE,GAAGT,IAAI;QACP,GAAGS,QAAQ;QACXC,QAAQ;YAAE,GAAGV,KAAKU,MAAM;YAAE,GAAGD,SAASC,MAAM;QAAC;QAC7CC,OAAO;YAAE,GAAGX,KAAKW,KAAK;YAAE,GAAGF,SAASE,KAAK;QAAC;QAC1CC,QAAQ;eAAIZ,KAAKY,MAAM;eAAMH,SAASG,MAAM,IAAI,EAAE;SAAE;QACpDC,QACEJ,SAASI,MAAM,IAAI,OAAOJ,SAASI,MAAM,KAAK,YAAY,OAAOb,KAAKa,MAAM,KAAK,WAC7E;YAAE,GAAGb,KAAKa,MAAM;YAAE,GAAGJ,SAASI,MAAM;QAAC,IACpCJ,SAASI,MAAM,IAAIb,KAAKa,MAAM;QACrCC,OAAOL,SAASK,KAAK,GAAGf,WAAWC,KAAKc,KAAK,IAAI,CAAC,GAAGL,SAASK,KAAK,IAAId,KAAKc,KAAK;IACnF,IACAd,KAAI;AAEV;;;CAGC,GACD,OAAO,MAAMe,cAAc,CAACf,MAAoBS,WAC9CA,WACI;QACE,GAAGT,IAAI;QACP,GAAGS,QAAQ;QACXC,QAAQ;YAAE,GAAGV,KAAKU,MAAM;YAAE,GAAGD,SAASC,MAAM;QAAC;QAC7CC,OAAO;YAAE,GAAGX,KAAKW,KAAK;YAAE,GAAGF,SAASE,KAAK;QAAC;QAC1CC,QAAQ;eAAIZ,KAAKY,MAAM;eAAMH,SAASG,MAAM,IAAI,EAAE;SAAE;QACpDE,OAAOL,SAASK,KAAK,GAAGf,WAAWC,KAAKc,KAAK,IAAI,CAAC,GAAGL,SAASK,KAAK,IAAId,KAAKc,KAAK;IACnF,IACAd,KAAI"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a Payload relationship/upload value to its id, whether it's populated (a doc with
|
|
3
|
+
* `id`) or just the raw id (string/number). Returns `undefined` for null/empty. Shared by every
|
|
4
|
+
* place that reads a slot value at `depth: 0` (the export endpoint, the optimize reconcile hook,
|
|
5
|
+
* the active-fonts resolver), so the unwrapping rule lives in exactly one spot.
|
|
6
|
+
*/
|
|
7
|
+
export declare const refId: (ref: unknown) => string | number | undefined;
|
|
8
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/lib/refs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,MAAM,GAAG,MAAM,GAAG,SAGtD,CAAA"}
|
package/dist/lib/refs.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a Payload relationship/upload value to its id, whether it's populated (a doc with
|
|
3
|
+
* `id`) or just the raw id (string/number). Returns `undefined` for null/empty. Shared by every
|
|
4
|
+
* place that reads a slot value at `depth: 0` (the export endpoint, the optimize reconcile hook,
|
|
5
|
+
* the active-fonts resolver), so the unwrapping rule lives in exactly one spot.
|
|
6
|
+
*/ export const refId = (ref)=>{
|
|
7
|
+
if (ref && typeof ref === 'object') return ref.id;
|
|
8
|
+
return ref ?? undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/refs.ts"],"sourcesContent":["/**\n * Resolve a Payload relationship/upload value to its id, whether it's populated (a doc with\n * `id`) or just the raw id (string/number). Returns `undefined` for null/empty. Shared by every\n * place that reads a slot value at `depth: 0` (the export endpoint, the optimize reconcile hook,\n * the active-fonts resolver), so the unwrapping rule lives in exactly one spot.\n */\nexport const refId = (ref: unknown): string | number | undefined => {\n if (ref && typeof ref === 'object') return (ref as { id?: string | number }).id\n return (ref as string | number | null | undefined) ?? undefined\n}\n"],"names":["refId","ref","id","undefined"],"mappings":"AAAA;;;;;CAKC,GACD,OAAO,MAAMA,QAAQ,CAACC;IACpB,IAAIA,OAAO,OAAOA,QAAQ,UAAU,OAAO,AAACA,IAAiCC,EAAE;IAC/E,OAAO,AAACD,OAA8CE;AACxD,EAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Payload } from 'payload';
|
|
2
|
+
/** The readable slice of any upload doc: a local filename and/or a URL (plus the
|
|
3
|
+
* cloud-storage `prefix` when the adapter stores one). */
|
|
4
|
+
export type UploadDoc = {
|
|
5
|
+
filename?: string | null;
|
|
6
|
+
url?: string | null;
|
|
7
|
+
prefix?: string | null;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Read an upload document's bytes regardless of storage adapter. In order:
|
|
11
|
+
* 1. the local file under the collection's `staticDir` (local-disk storage),
|
|
12
|
+
* 2. the collection's storage-adapter `upload.handlers`, invoked directly (cloud storage —
|
|
13
|
+
* no HTTP and no access control, exactly like Payload's own file route post-auth), or
|
|
14
|
+
* 3. the URL the doc reports, fetched — an absolute cloud URL as-is, a relative one (Payload's
|
|
15
|
+
* access-controlled file route) resolved against `serverURL`.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when nothing yields the bytes.
|
|
18
|
+
*
|
|
19
|
+
* `opts.headers` are forwarded to the URL fetch — needed only when step 3 hits Payload's own
|
|
20
|
+
* access-controlled file route: a caller running inside an authenticated request passes its
|
|
21
|
+
* `cookie` so the route authorizes the read instead of returning 403.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readUploadBytes(payload: Payload, slug: string, doc: UploadDoc, opts?: {
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
}): Promise<Buffer | null>;
|
|
26
|
+
//# sourceMappingURL=uploadBytes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploadBytes.d.ts","sourceRoot":"","sources":["../../src/lib/uploadBytes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,OAAO,EAAuB,MAAM,SAAS,CAAA;AAE3E;2DAC2D;AAC3D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAqDjG;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,SAAS,EACd,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC9C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BxB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createLocalReq } from "payload";
|
|
4
|
+
/** Resolve a collection's on-disk `staticDir` (absolute), defaulting to its slug. */ const resolveStaticDir = (payload, slug)=>{
|
|
5
|
+
const collections = payload.collections;
|
|
6
|
+
const dir = collections?.[slug]?.config?.upload?.staticDir;
|
|
7
|
+
const base = dir?.length ? dir : slug;
|
|
8
|
+
return path.isAbsolute(base) ? base : path.resolve(process.cwd(), base);
|
|
9
|
+
};
|
|
10
|
+
const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
11
|
+
/**
|
|
12
|
+
* Read the bytes through the collection's storage-adapter `upload.handlers` (Vercel Blob, S3, …).
|
|
13
|
+
* This is the same server-side path Payload's own file route takes after its access check —
|
|
14
|
+
* invoked directly, not over HTTP, so it needs no origin, cookie, or open read access.
|
|
15
|
+
*
|
|
16
|
+
* Only a 200 with a non-empty body counts as success. Cloud storage is eventually consistent on
|
|
17
|
+
* read-after-write: fetching a blob moments after it was (re)uploaded can miss — the Vercel Blob
|
|
18
|
+
* adapter surfaces that as an ok-looking **204 with an empty body** (its `head()` sees the new
|
|
19
|
+
* metadata but the CDN fetch behind it isn't propagated yet). That's transient, so retry briefly
|
|
20
|
+
* before giving up. Without this, a seed run subsets 0-byte "fonts" and fails downstream.
|
|
21
|
+
*/ async function readViaStorageHandlers(payload, slug, doc) {
|
|
22
|
+
const collections = payload.collections;
|
|
23
|
+
const handlers = collections?.[slug]?.config?.upload?.handlers;
|
|
24
|
+
if (!handlers?.length || !doc.filename) return null;
|
|
25
|
+
const ATTEMPTS = 4;
|
|
26
|
+
const RETRY_DELAY_MS = 1500;
|
|
27
|
+
try {
|
|
28
|
+
const req = await createLocalReq({}, payload);
|
|
29
|
+
for(let attempt = 0; attempt < ATTEMPTS; attempt++){
|
|
30
|
+
if (attempt > 0) await sleep(RETRY_DELAY_MS);
|
|
31
|
+
for (const handler of handlers){
|
|
32
|
+
const res = await handler(req, {
|
|
33
|
+
doc,
|
|
34
|
+
params: {
|
|
35
|
+
collection: slug,
|
|
36
|
+
filename: doc.filename,
|
|
37
|
+
prefix: doc.prefix ?? undefined
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (!(res instanceof Response)) continue;
|
|
41
|
+
if (res.status !== 200) continue; // 204/304/4xx — not (yet) readable through this path
|
|
42
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
43
|
+
if (bytes.byteLength > 0) return bytes;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// fall through to the URL fetch
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read an upload document's bytes regardless of storage adapter. In order:
|
|
53
|
+
* 1. the local file under the collection's `staticDir` (local-disk storage),
|
|
54
|
+
* 2. the collection's storage-adapter `upload.handlers`, invoked directly (cloud storage —
|
|
55
|
+
* no HTTP and no access control, exactly like Payload's own file route post-auth), or
|
|
56
|
+
* 3. the URL the doc reports, fetched — an absolute cloud URL as-is, a relative one (Payload's
|
|
57
|
+
* access-controlled file route) resolved against `serverURL`.
|
|
58
|
+
*
|
|
59
|
+
* Returns `null` when nothing yields the bytes.
|
|
60
|
+
*
|
|
61
|
+
* `opts.headers` are forwarded to the URL fetch — needed only when step 3 hits Payload's own
|
|
62
|
+
* access-controlled file route: a caller running inside an authenticated request passes its
|
|
63
|
+
* `cookie` so the route authorizes the read instead of returning 403.
|
|
64
|
+
*/ export async function readUploadBytes(payload, slug, doc, opts = {}) {
|
|
65
|
+
if (doc.filename) {
|
|
66
|
+
// Resolve under staticDir and confirm the result stays inside it — guards against
|
|
67
|
+
// path-traversal segments or an absolute path escaping the dir.
|
|
68
|
+
const base = path.resolve(resolveStaticDir(payload, slug));
|
|
69
|
+
const filePath = path.resolve(base, doc.filename);
|
|
70
|
+
if ((filePath === base || filePath.startsWith(base + path.sep)) && fs.existsSync(filePath)) {
|
|
71
|
+
return fs.readFileSync(filePath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const viaHandlers = await readViaStorageHandlers(payload, slug, doc);
|
|
75
|
+
if (viaHandlers) return viaHandlers;
|
|
76
|
+
if (typeof doc.url === 'string' && doc.url) {
|
|
77
|
+
const isAbsolute = /^https?:\/\//i.test(doc.url);
|
|
78
|
+
const base = (payload.config?.serverURL || '').replace(/\/$/, '');
|
|
79
|
+
const target = isAbsolute ? doc.url : base ? `${base}${doc.url.startsWith('/') ? '' : '/'}${doc.url}` : '';
|
|
80
|
+
if (/^https?:\/\//i.test(target)) {
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetch(target, {
|
|
83
|
+
headers: opts.headers,
|
|
84
|
+
signal: AbortSignal.timeout(15_000)
|
|
85
|
+
});
|
|
86
|
+
if (res.status === 200) {
|
|
87
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
88
|
+
if (bytes.byteLength > 0) return bytes;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// fall through to "not found"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=uploadBytes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/uploadBytes.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\n\nimport { createLocalReq, type Payload, type PayloadRequest } from 'payload'\n\n/** The readable slice of any upload doc: a local filename and/or a URL (plus the\n * cloud-storage `prefix` when the adapter stores one). */\nexport type UploadDoc = { filename?: string | null; url?: string | null; prefix?: string | null }\n\ntype UploadHandler = (\n req: PayloadRequest,\n args: { doc: unknown; headers?: Headers; params: { collection: string; filename: string; prefix?: string } },\n // biome-ignore lint/suspicious/noConfusingVoidType: mirrors Payload's upload handler signature, which returns void\n) => Promise<Response | void> | Response | void\n\n/** Resolve a collection's on-disk `staticDir` (absolute), defaulting to its slug. */\nconst resolveStaticDir = (payload: Payload, slug: string): string => {\n const collections = payload.collections as Record<string, { config?: { upload?: { staticDir?: string } } }>\n const dir = collections?.[slug]?.config?.upload?.staticDir\n const base = dir?.length ? dir : slug\n return path.isAbsolute(base) ? base : path.resolve(process.cwd(), base)\n}\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\n/**\n * Read the bytes through the collection's storage-adapter `upload.handlers` (Vercel Blob, S3, …).\n * This is the same server-side path Payload's own file route takes after its access check —\n * invoked directly, not over HTTP, so it needs no origin, cookie, or open read access.\n *\n * Only a 200 with a non-empty body counts as success. Cloud storage is eventually consistent on\n * read-after-write: fetching a blob moments after it was (re)uploaded can miss — the Vercel Blob\n * adapter surfaces that as an ok-looking **204 with an empty body** (its `head()` sees the new\n * metadata but the CDN fetch behind it isn't propagated yet). That's transient, so retry briefly\n * before giving up. Without this, a seed run subsets 0-byte \"fonts\" and fails downstream.\n */\nasync function readViaStorageHandlers(payload: Payload, slug: string, doc: UploadDoc): Promise<Buffer | null> {\n const collections = payload.collections as Record<string, { config?: { upload?: { handlers?: UploadHandler[] } } }>\n const handlers = collections?.[slug]?.config?.upload?.handlers\n if (!handlers?.length || !doc.filename) return null\n const ATTEMPTS = 4\n const RETRY_DELAY_MS = 1500\n try {\n const req = await createLocalReq({}, payload)\n for (let attempt = 0; attempt < ATTEMPTS; attempt++) {\n if (attempt > 0) await sleep(RETRY_DELAY_MS)\n for (const handler of handlers) {\n const res = await handler(req, { doc, params: { collection: slug, filename: doc.filename, prefix: doc.prefix ?? undefined } })\n if (!(res instanceof Response)) continue\n if (res.status !== 200) continue // 204/304/4xx — not (yet) readable through this path\n const bytes = Buffer.from(await res.arrayBuffer())\n if (bytes.byteLength > 0) return bytes\n }\n }\n } catch {\n // fall through to the URL fetch\n }\n return null\n}\n\n/**\n * Read an upload document's bytes regardless of storage adapter. In order:\n * 1. the local file under the collection's `staticDir` (local-disk storage),\n * 2. the collection's storage-adapter `upload.handlers`, invoked directly (cloud storage —\n * no HTTP and no access control, exactly like Payload's own file route post-auth), or\n * 3. the URL the doc reports, fetched — an absolute cloud URL as-is, a relative one (Payload's\n * access-controlled file route) resolved against `serverURL`.\n *\n * Returns `null` when nothing yields the bytes.\n *\n * `opts.headers` are forwarded to the URL fetch — needed only when step 3 hits Payload's own\n * access-controlled file route: a caller running inside an authenticated request passes its\n * `cookie` so the route authorizes the read instead of returning 403.\n */\nexport async function readUploadBytes(\n payload: Payload,\n slug: string,\n doc: UploadDoc,\n opts: { headers?: Record<string, string> } = {},\n): Promise<Buffer | null> {\n if (doc.filename) {\n // Resolve under staticDir and confirm the result stays inside it — guards against\n // path-traversal segments or an absolute path escaping the dir.\n const base = path.resolve(resolveStaticDir(payload, slug))\n const filePath = path.resolve(base, doc.filename)\n if ((filePath === base || filePath.startsWith(base + path.sep)) && fs.existsSync(filePath)) {\n return fs.readFileSync(filePath)\n }\n }\n\n const viaHandlers = await readViaStorageHandlers(payload, slug, doc)\n if (viaHandlers) return viaHandlers\n\n if (typeof doc.url === 'string' && doc.url) {\n const isAbsolute = /^https?:\\/\\//i.test(doc.url)\n const base = (payload.config?.serverURL || '').replace(/\\/$/, '')\n const target = isAbsolute ? doc.url : base ? `${base}${doc.url.startsWith('/') ? '' : '/'}${doc.url}` : ''\n if (/^https?:\\/\\//i.test(target)) {\n try {\n const res = await fetch(target, { headers: opts.headers, signal: AbortSignal.timeout(15_000) })\n if (res.status === 200) {\n const bytes = Buffer.from(await res.arrayBuffer())\n if (bytes.byteLength > 0) return bytes\n }\n } catch {\n // fall through to \"not found\"\n }\n }\n }\n return null\n}\n"],"names":["fs","path","createLocalReq","resolveStaticDir","payload","slug","collections","dir","config","upload","staticDir","base","length","isAbsolute","resolve","process","cwd","sleep","ms","Promise","setTimeout","readViaStorageHandlers","doc","handlers","filename","ATTEMPTS","RETRY_DELAY_MS","req","attempt","handler","res","params","collection","prefix","undefined","Response","status","bytes","Buffer","from","arrayBuffer","byteLength","readUploadBytes","opts","filePath","startsWith","sep","existsSync","readFileSync","viaHandlers","url","test","serverURL","replace","target","fetch","headers","signal","AbortSignal","timeout"],"mappings":"AAAA,OAAOA,QAAQ,UAAS;AACxB,OAAOC,UAAU,YAAW;AAE5B,SAASC,cAAc,QAA2C,UAAS;AAY3E,mFAAmF,GACnF,MAAMC,mBAAmB,CAACC,SAAkBC;IAC1C,MAAMC,cAAcF,QAAQE,WAAW;IACvC,MAAMC,MAAMD,aAAa,CAACD,KAAK,EAAEG,QAAQC,QAAQC;IACjD,MAAMC,OAAOJ,KAAKK,SAASL,MAAMF;IACjC,OAAOJ,KAAKY,UAAU,CAACF,QAAQA,OAAOV,KAAKa,OAAO,CAACC,QAAQC,GAAG,IAAIL;AACpE;AAEA,MAAMM,QAAQ,CAACC,KAAe,IAAIC,QAAQ,CAACL,UAAYM,WAAWN,SAASI;AAE3E;;;;;;;;;;CAUC,GACD,eAAeG,uBAAuBjB,OAAgB,EAAEC,IAAY,EAAEiB,GAAc;IAClF,MAAMhB,cAAcF,QAAQE,WAAW;IACvC,MAAMiB,WAAWjB,aAAa,CAACD,KAAK,EAAEG,QAAQC,QAAQc;IACtD,IAAI,CAACA,UAAUX,UAAU,CAACU,IAAIE,QAAQ,EAAE,OAAO;IAC/C,MAAMC,WAAW;IACjB,MAAMC,iBAAiB;IACvB,IAAI;QACF,MAAMC,MAAM,MAAMzB,eAAe,CAAC,GAAGE;QACrC,IAAK,IAAIwB,UAAU,GAAGA,UAAUH,UAAUG,UAAW;YACnD,IAAIA,UAAU,GAAG,MAAMX,MAAMS;YAC7B,KAAK,MAAMG,WAAWN,SAAU;gBAC9B,MAAMO,MAAM,MAAMD,QAAQF,KAAK;oBAAEL;oBAAKS,QAAQ;wBAAEC,YAAY3B;wBAAMmB,UAAUF,IAAIE,QAAQ;wBAAES,QAAQX,IAAIW,MAAM,IAAIC;oBAAU;gBAAE;gBAC5H,IAAI,CAAEJ,CAAAA,eAAeK,QAAO,GAAI;gBAChC,IAAIL,IAAIM,MAAM,KAAK,KAAK,UAAS,qDAAqD;gBACtF,MAAMC,QAAQC,OAAOC,IAAI,CAAC,MAAMT,IAAIU,WAAW;gBAC/C,IAAIH,MAAMI,UAAU,GAAG,GAAG,OAAOJ;YACnC;QACF;IACF,EAAE,OAAM;IACN,gCAAgC;IAClC;IACA,OAAO;AACT;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeK,gBACpBtC,OAAgB,EAChBC,IAAY,EACZiB,GAAc,EACdqB,OAA6C,CAAC,CAAC;IAE/C,IAAIrB,IAAIE,QAAQ,EAAE;QAChB,kFAAkF;QAClF,gEAAgE;QAChE,MAAMb,OAAOV,KAAKa,OAAO,CAACX,iBAAiBC,SAASC;QACpD,MAAMuC,WAAW3C,KAAKa,OAAO,CAACH,MAAMW,IAAIE,QAAQ;QAChD,IAAI,AAACoB,CAAAA,aAAajC,QAAQiC,SAASC,UAAU,CAAClC,OAAOV,KAAK6C,GAAG,CAAA,KAAM9C,GAAG+C,UAAU,CAACH,WAAW;YAC1F,OAAO5C,GAAGgD,YAAY,CAACJ;QACzB;IACF;IAEA,MAAMK,cAAc,MAAM5B,uBAAuBjB,SAASC,MAAMiB;IAChE,IAAI2B,aAAa,OAAOA;IAExB,IAAI,OAAO3B,IAAI4B,GAAG,KAAK,YAAY5B,IAAI4B,GAAG,EAAE;QAC1C,MAAMrC,aAAa,gBAAgBsC,IAAI,CAAC7B,IAAI4B,GAAG;QAC/C,MAAMvC,OAAO,AAACP,CAAAA,QAAQI,MAAM,EAAE4C,aAAa,EAAC,EAAGC,OAAO,CAAC,OAAO;QAC9D,MAAMC,SAASzC,aAAaS,IAAI4B,GAAG,GAAGvC,OAAO,GAAGA,OAAOW,IAAI4B,GAAG,CAACL,UAAU,CAAC,OAAO,KAAK,MAAMvB,IAAI4B,GAAG,EAAE,GAAG;QACxG,IAAI,gBAAgBC,IAAI,CAACG,SAAS;YAChC,IAAI;gBACF,MAAMxB,MAAM,MAAMyB,MAAMD,QAAQ;oBAAEE,SAASb,KAAKa,OAAO;oBAAEC,QAAQC,YAAYC,OAAO,CAAC;gBAAQ;gBAC7F,IAAI7B,IAAIM,MAAM,KAAK,KAAK;oBACtB,MAAMC,QAAQC,OAAOC,IAAI,CAAC,MAAMT,IAAIU,WAAW;oBAC/C,IAAIH,MAAMI,UAAU,GAAG,GAAG,OAAOJ;gBACnC;YACF,EAAE,OAAM;YACN,8BAA8B;YAChC;QACF;IACF;IACA,OAAO;AACT"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CollectionConfig, GlobalConfig, Plugin } from 'payload';
|
|
2
|
+
import { type Charset } from './hooks/optimizeFont';
|
|
3
|
+
import { type FontFamilyConfig } from './lib/families';
|
|
4
|
+
export interface FontsPluginOptions {
|
|
5
|
+
/** When false, the plugin is a no-op. Defaults to true. */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Characters the subsetter keeps when building the served WOFF2 files: a preset name
|
|
9
|
+
* (`'latin'` — ASCII + Latin-1 + common punctuation, the default; or `'latin-ext'`) or an
|
|
10
|
+
* explicit string of characters to retain.
|
|
11
|
+
*/
|
|
12
|
+
charset?: Charset;
|
|
13
|
+
/**
|
|
14
|
+
* The font *families* (slots) the plugin exposes end-to-end — the `family` options on the `font`
|
|
15
|
+
* collection, the relationship slots on the `fontSet` global, the keys in the export JSON, and
|
|
16
|
+
* (capitalised) the generated `font<Key>` / `--font-set<Key>` names. Defaults to the built-in
|
|
17
|
+
* `sans / serif / mono / display`. Pass your own list to replace, extend, drop, or reorder them
|
|
18
|
+
* — e.g. `[{ key: 'sans' }, { key: 'display' }, { key: 'brand', fallback: 'Georgia, serif' }]`.
|
|
19
|
+
* Each entry is `{ key, label?, fallback? }`; only `key` is required.
|
|
20
|
+
*/
|
|
21
|
+
families?: FontFamilyConfig[];
|
|
22
|
+
/** Merged onto the visible `font` (typeface) collection. */
|
|
23
|
+
fontOverrides?: Partial<CollectionConfig>;
|
|
24
|
+
/**
|
|
25
|
+
* Merged onto the hidden `fontOriginal` upload collection (the raw files editors upload
|
|
26
|
+
* into) — e.g. `upload: { staticDir }`.
|
|
27
|
+
*/
|
|
28
|
+
fontOriginalOverrides?: Partial<CollectionConfig>;
|
|
29
|
+
/** Merged onto the hidden `fontOptimized` upload collection (the served WOFF2s). */
|
|
30
|
+
fontOptimizedOverrides?: Partial<CollectionConfig>;
|
|
31
|
+
/**
|
|
32
|
+
* Register the `fontSet` global — the active sans/serif/mono/display selection that the export
|
|
33
|
+
* endpoint (and your frontend) reads. **On by default**, since it's the only place to mark which
|
|
34
|
+
* typefaces are active; set `false` only if you drive that selection some other way (without it
|
|
35
|
+
* the export endpoint has nothing to resolve and returns no fonts).
|
|
36
|
+
*/
|
|
37
|
+
includeFontSet?: boolean;
|
|
38
|
+
/** Merged onto the `fontSet` global when {@link includeFontSet} is true. */
|
|
39
|
+
fontSetOverrides?: Partial<GlobalConfig>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `@pro-laico/payload-fonts` — three collections, standard Payload primitives:
|
|
43
|
+
*
|
|
44
|
+
* - `font` (visible): the typeface. Editors drop files into `upload` fields backed by
|
|
45
|
+
* `fontOriginal` (a `variable` group or a `weights` array). Seed it by uploading the raw files
|
|
46
|
+
* to `fontOriginal` and referencing them from the typeface's slots.
|
|
47
|
+
* - `fontOriginal` (hidden): the raw uploaded files. Register it with your storage adapter's
|
|
48
|
+
* client-uploads instance to keep big fonts off the request body in production.
|
|
49
|
+
* - `fontOptimized` (hidden): the subsetted WOFF2 the site serves, built from the originals by
|
|
50
|
+
* the `font` save hook. Always server-side stored.
|
|
51
|
+
*
|
|
52
|
+
* Plus the `fontSet` global (the active selection — on by default) and the `GET /api/fonts/export`
|
|
53
|
+
* endpoint the `payload-fonts-download` CLI reads to write fonts for `next/font/local`.
|
|
54
|
+
*/
|
|
55
|
+
export declare const fontsPlugin: (opts?: FontsPluginOptions) => Plugin;
|
|
56
|
+
export default fontsPlugin;
|
|
57
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAU,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAO7E,OAAO,EAAE,KAAK,OAAO,EAAkB,MAAM,sBAAsB,CAAA;AAEnE,OAAO,EAAE,KAAK,gBAAgB,EAAuB,MAAM,gBAAgB,CAAA;AAE3E,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC7B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACzC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACjD,oFAAoF;IACpF,sBAAsB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAClD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CACzC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,GACrB,OAAM,kBAAuB,KAAG,MAwEhC,CAAA;AAEH,eAAe,WAAW,CAAA"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createFontCollection } from "./collections/font.js";
|
|
2
|
+
import { createFontOptimizedCollection, FONT_OPTIMIZED_SLUG } from "./collections/fontOptimized.js";
|
|
3
|
+
import { createFontOriginalCollection, FONT_ORIGINAL_SLUG } from "./collections/fontOriginal.js";
|
|
4
|
+
import { exportFontsEndpoint } from "./endpoints/exportFonts.js";
|
|
5
|
+
import { createFontSetGlobal, FONT_SET_SLUG } from "./globals/fontSet.js";
|
|
6
|
+
import { probeSubsetter } from "./hooks/optimizeFont.js";
|
|
7
|
+
import { mergeCollection, mergeGlobal } from "./lib/mergeConfig.js";
|
|
8
|
+
import { resolveFontFamilies } from "./lib/families.js";
|
|
9
|
+
/**
|
|
10
|
+
* `@pro-laico/payload-fonts` — three collections, standard Payload primitives:
|
|
11
|
+
*
|
|
12
|
+
* - `font` (visible): the typeface. Editors drop files into `upload` fields backed by
|
|
13
|
+
* `fontOriginal` (a `variable` group or a `weights` array). Seed it by uploading the raw files
|
|
14
|
+
* to `fontOriginal` and referencing them from the typeface's slots.
|
|
15
|
+
* - `fontOriginal` (hidden): the raw uploaded files. Register it with your storage adapter's
|
|
16
|
+
* client-uploads instance to keep big fonts off the request body in production.
|
|
17
|
+
* - `fontOptimized` (hidden): the subsetted WOFF2 the site serves, built from the originals by
|
|
18
|
+
* the `font` save hook. Always server-side stored.
|
|
19
|
+
*
|
|
20
|
+
* Plus the `fontSet` global (the active selection — on by default) and the `GET /api/fonts/export`
|
|
21
|
+
* endpoint the `payload-fonts-download` CLI reads to write fonts for `next/font/local`.
|
|
22
|
+
*/ export const fontsPlugin = (opts = {})=>(config)=>{
|
|
23
|
+
const { enabled = true, charset, families, fontOverrides, fontOriginalOverrides, fontOptimizedOverrides, includeFontSet = true, fontSetOverrides } = opts;
|
|
24
|
+
if (!enabled) return config;
|
|
25
|
+
const familyKeys = resolveFontFamilies(families).map((r)=>r.key);
|
|
26
|
+
const collections = [
|
|
27
|
+
...config.collections ?? [],
|
|
28
|
+
mergeCollection(createFontCollection({
|
|
29
|
+
charset,
|
|
30
|
+
families,
|
|
31
|
+
originalSlug: FONT_ORIGINAL_SLUG,
|
|
32
|
+
optimizedSlug: FONT_OPTIMIZED_SLUG
|
|
33
|
+
}), fontOverrides),
|
|
34
|
+
mergeCollection(createFontOriginalCollection(), fontOriginalOverrides),
|
|
35
|
+
mergeCollection(createFontOptimizedCollection({
|
|
36
|
+
fontSlug: 'font',
|
|
37
|
+
originalSlug: FONT_ORIGINAL_SLUG
|
|
38
|
+
}), fontOptimizedOverrides)
|
|
39
|
+
];
|
|
40
|
+
const globals = includeFontSet ? [
|
|
41
|
+
...config.globals ?? [],
|
|
42
|
+
mergeGlobal(createFontSetGlobal({
|
|
43
|
+
families
|
|
44
|
+
}), fontSetOverrides)
|
|
45
|
+
] : config.globals;
|
|
46
|
+
const endpoints = [
|
|
47
|
+
...config.endpoints ?? [],
|
|
48
|
+
exportFontsEndpoint({
|
|
49
|
+
fontSetGlobalSlug: FONT_SET_SLUG,
|
|
50
|
+
fontOptimizedSlug: FONT_OPTIMIZED_SLUG,
|
|
51
|
+
families: familyKeys
|
|
52
|
+
})
|
|
53
|
+
];
|
|
54
|
+
return {
|
|
55
|
+
...config,
|
|
56
|
+
collections,
|
|
57
|
+
globals,
|
|
58
|
+
endpoints,
|
|
59
|
+
// Stash the resolved slugs + families so decoupled tooling (e.g. @pro-laico/payload-dev-tools)
|
|
60
|
+
// can discover the plugin and read them from just `payload.config` — no import.
|
|
61
|
+
custom: {
|
|
62
|
+
...config.custom,
|
|
63
|
+
payloadFonts: {
|
|
64
|
+
options: opts,
|
|
65
|
+
fontSlug: fontOverrides?.slug ?? 'font',
|
|
66
|
+
fontOriginalSlug: FONT_ORIGINAL_SLUG,
|
|
67
|
+
fontOptimizedSlug: FONT_OPTIMIZED_SLUG,
|
|
68
|
+
fontSetSlug: includeFontSet ? FONT_SET_SLUG : null,
|
|
69
|
+
familyKeys,
|
|
70
|
+
exportPath: '/fonts/export'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onInit: async (payload)=>{
|
|
74
|
+
await config.onInit?.(payload);
|
|
75
|
+
// Dev-only probe for the most common deployment mistake: a bundler inlined the subsetter's
|
|
76
|
+
// wasm/native assets, which otherwise only surfaces on the first font save. Importing
|
|
77
|
+
// `subset-font` is not enough — harfbuzz reads its wasm lazily on the first subset call —
|
|
78
|
+
// so probeSubsetter runs a real (garbage-buffer) subset and reports only load failures.
|
|
79
|
+
// Not awaited (never slows boot) and never runs in production, where the same failure is
|
|
80
|
+
// still caught loudly on the first font save.
|
|
81
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
82
|
+
void probeSubsetter().then((err)=>{
|
|
83
|
+
if (err) {
|
|
84
|
+
payload.logger.error(`[payload-fonts] The font subsetter failed to load — uploaded fonts will NOT be subsetted or served. In Next.js add \`serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']\` to your next.config. (${err instanceof Error ? err.message : err})`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export default fontsPlugin;
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionConfig, Config, GlobalConfig, Plugin } from 'payload'\n\nimport { createFontCollection } from './collections/font'\nimport { createFontOptimizedCollection, FONT_OPTIMIZED_SLUG } from './collections/fontOptimized'\nimport { createFontOriginalCollection, FONT_ORIGINAL_SLUG } from './collections/fontOriginal'\nimport { exportFontsEndpoint } from './endpoints/exportFonts'\nimport { createFontSetGlobal, FONT_SET_SLUG } from './globals/fontSet'\nimport { type Charset, probeSubsetter } from './hooks/optimizeFont'\nimport { mergeCollection, mergeGlobal } from './lib/mergeConfig'\nimport { type FontFamilyConfig, resolveFontFamilies } from './lib/families'\n\nexport interface FontsPluginOptions {\n /** When false, the plugin is a no-op. Defaults to true. */\n enabled?: boolean\n /**\n * Characters the subsetter keeps when building the served WOFF2 files: a preset name\n * (`'latin'` — ASCII + Latin-1 + common punctuation, the default; or `'latin-ext'`) or an\n * explicit string of characters to retain.\n */\n charset?: Charset\n /**\n * The font *families* (slots) the plugin exposes end-to-end — the `family` options on the `font`\n * collection, the relationship slots on the `fontSet` global, the keys in the export JSON, and\n * (capitalised) the generated `font<Key>` / `--font-set<Key>` names. Defaults to the built-in\n * `sans / serif / mono / display`. Pass your own list to replace, extend, drop, or reorder them\n * — e.g. `[{ key: 'sans' }, { key: 'display' }, { key: 'brand', fallback: 'Georgia, serif' }]`.\n * Each entry is `{ key, label?, fallback? }`; only `key` is required.\n */\n families?: FontFamilyConfig[]\n /** Merged onto the visible `font` (typeface) collection. */\n fontOverrides?: Partial<CollectionConfig>\n /**\n * Merged onto the hidden `fontOriginal` upload collection (the raw files editors upload\n * into) — e.g. `upload: { staticDir }`.\n */\n fontOriginalOverrides?: Partial<CollectionConfig>\n /** Merged onto the hidden `fontOptimized` upload collection (the served WOFF2s). */\n fontOptimizedOverrides?: Partial<CollectionConfig>\n /**\n * Register the `fontSet` global — the active sans/serif/mono/display selection that the export\n * endpoint (and your frontend) reads. **On by default**, since it's the only place to mark which\n * typefaces are active; set `false` only if you drive that selection some other way (without it\n * the export endpoint has nothing to resolve and returns no fonts).\n */\n includeFontSet?: boolean\n /** Merged onto the `fontSet` global when {@link includeFontSet} is true. */\n fontSetOverrides?: Partial<GlobalConfig>\n}\n\n/**\n * `@pro-laico/payload-fonts` — three collections, standard Payload primitives:\n *\n * - `font` (visible): the typeface. Editors drop files into `upload` fields backed by\n * `fontOriginal` (a `variable` group or a `weights` array). Seed it by uploading the raw files\n * to `fontOriginal` and referencing them from the typeface's slots.\n * - `fontOriginal` (hidden): the raw uploaded files. Register it with your storage adapter's\n * client-uploads instance to keep big fonts off the request body in production.\n * - `fontOptimized` (hidden): the subsetted WOFF2 the site serves, built from the originals by\n * the `font` save hook. Always server-side stored.\n *\n * Plus the `fontSet` global (the active selection — on by default) and the `GET /api/fonts/export`\n * endpoint the `payload-fonts-download` CLI reads to write fonts for `next/font/local`.\n */\nexport const fontsPlugin =\n (opts: FontsPluginOptions = {}): Plugin =>\n (config: Config): Config => {\n const {\n enabled = true,\n charset,\n families,\n fontOverrides,\n fontOriginalOverrides,\n fontOptimizedOverrides,\n includeFontSet = true,\n fontSetOverrides,\n } = opts\n if (!enabled) return config\n\n const familyKeys = resolveFontFamilies(families).map((r) => r.key)\n\n const collections = [\n ...(config.collections ?? []),\n mergeCollection(\n createFontCollection({ charset, families, originalSlug: FONT_ORIGINAL_SLUG, optimizedSlug: FONT_OPTIMIZED_SLUG }),\n fontOverrides,\n ),\n mergeCollection(createFontOriginalCollection(), fontOriginalOverrides),\n mergeCollection(createFontOptimizedCollection({ fontSlug: 'font', originalSlug: FONT_ORIGINAL_SLUG }), fontOptimizedOverrides),\n ]\n\n const globals = includeFontSet\n ? [...(config.globals ?? []), mergeGlobal(createFontSetGlobal({ families }), fontSetOverrides)]\n : config.globals\n const endpoints = [\n ...(config.endpoints ?? []),\n exportFontsEndpoint({ fontSetGlobalSlug: FONT_SET_SLUG, fontOptimizedSlug: FONT_OPTIMIZED_SLUG, families: familyKeys }),\n ]\n\n return {\n ...config,\n collections,\n globals,\n endpoints,\n // Stash the resolved slugs + families so decoupled tooling (e.g. @pro-laico/payload-dev-tools)\n // can discover the plugin and read them from just `payload.config` — no import.\n custom: {\n ...config.custom,\n payloadFonts: {\n options: opts,\n fontSlug: fontOverrides?.slug ?? 'font',\n fontOriginalSlug: FONT_ORIGINAL_SLUG,\n fontOptimizedSlug: FONT_OPTIMIZED_SLUG,\n fontSetSlug: includeFontSet ? FONT_SET_SLUG : null,\n familyKeys,\n exportPath: '/fonts/export',\n },\n },\n onInit: async (payload) => {\n await config.onInit?.(payload)\n // Dev-only probe for the most common deployment mistake: a bundler inlined the subsetter's\n // wasm/native assets, which otherwise only surfaces on the first font save. Importing\n // `subset-font` is not enough — harfbuzz reads its wasm lazily on the first subset call —\n // so probeSubsetter runs a real (garbage-buffer) subset and reports only load failures.\n // Not awaited (never slows boot) and never runs in production, where the same failure is\n // still caught loudly on the first font save.\n if (process.env.NODE_ENV !== 'production') {\n void probeSubsetter().then((err) => {\n if (err) {\n payload.logger.error(\n `[payload-fonts] The font subsetter failed to load — uploaded fonts will NOT be subsetted or served. In Next.js add \\`serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']\\` to your next.config. (${err instanceof Error ? err.message : err})`,\n )\n }\n })\n }\n },\n }\n }\n\nexport default fontsPlugin\n"],"names":["createFontCollection","createFontOptimizedCollection","FONT_OPTIMIZED_SLUG","createFontOriginalCollection","FONT_ORIGINAL_SLUG","exportFontsEndpoint","createFontSetGlobal","FONT_SET_SLUG","probeSubsetter","mergeCollection","mergeGlobal","resolveFontFamilies","fontsPlugin","opts","config","enabled","charset","families","fontOverrides","fontOriginalOverrides","fontOptimizedOverrides","includeFontSet","fontSetOverrides","familyKeys","map","r","key","collections","originalSlug","optimizedSlug","fontSlug","globals","endpoints","fontSetGlobalSlug","fontOptimizedSlug","custom","payloadFonts","options","slug","fontOriginalSlug","fontSetSlug","exportPath","onInit","payload","process","env","NODE_ENV","then","err","logger","error","Error","message"],"mappings":"AAEA,SAASA,oBAAoB,QAAQ,wBAAoB;AACzD,SAASC,6BAA6B,EAAEC,mBAAmB,QAAQ,iCAA6B;AAChG,SAASC,4BAA4B,EAAEC,kBAAkB,QAAQ,gCAA4B;AAC7F,SAASC,mBAAmB,QAAQ,6BAAyB;AAC7D,SAASC,mBAAmB,EAAEC,aAAa,QAAQ,uBAAmB;AACtE,SAAuBC,cAAc,QAAQ,0BAAsB;AACnE,SAASC,eAAe,EAAEC,WAAW,QAAQ,uBAAmB;AAChE,SAAgCC,mBAAmB,QAAQ,oBAAgB;AAwC3E;;;;;;;;;;;;;CAaC,GACD,OAAO,MAAMC,cACX,CAACC,OAA2B,CAAC,CAAC,GAC9B,CAACC;QACC,MAAM,EACJC,UAAU,IAAI,EACdC,OAAO,EACPC,QAAQ,EACRC,aAAa,EACbC,qBAAqB,EACrBC,sBAAsB,EACtBC,iBAAiB,IAAI,EACrBC,gBAAgB,EACjB,GAAGT;QACJ,IAAI,CAACE,SAAS,OAAOD;QAErB,MAAMS,aAAaZ,oBAAoBM,UAAUO,GAAG,CAAC,CAACC,IAAMA,EAAEC,GAAG;QAEjE,MAAMC,cAAc;eACdb,OAAOa,WAAW,IAAI,EAAE;YAC5BlB,gBACET,qBAAqB;gBAAEgB;gBAASC;gBAAUW,cAAcxB;gBAAoByB,eAAe3B;YAAoB,IAC/GgB;YAEFT,gBAAgBN,gCAAgCgB;YAChDV,gBAAgBR,8BAA8B;gBAAE6B,UAAU;gBAAQF,cAAcxB;YAAmB,IAAIgB;SACxG;QAED,MAAMW,UAAUV,iBACZ;eAAKP,OAAOiB,OAAO,IAAI,EAAE;YAAGrB,YAAYJ,oBAAoB;gBAAEW;YAAS,IAAIK;SAAkB,GAC7FR,OAAOiB,OAAO;QAClB,MAAMC,YAAY;eACZlB,OAAOkB,SAAS,IAAI,EAAE;YAC1B3B,oBAAoB;gBAAE4B,mBAAmB1B;gBAAe2B,mBAAmBhC;gBAAqBe,UAAUM;YAAW;SACtH;QAED,OAAO;YACL,GAAGT,MAAM;YACTa;YACAI;YACAC;YACA,+FAA+F;YAC/F,gFAAgF;YAChFG,QAAQ;gBACN,GAAGrB,OAAOqB,MAAM;gBAChBC,cAAc;oBACZC,SAASxB;oBACTiB,UAAUZ,eAAeoB,QAAQ;oBACjCC,kBAAkBnC;oBAClB8B,mBAAmBhC;oBACnBsC,aAAanB,iBAAiBd,gBAAgB;oBAC9CgB;oBACAkB,YAAY;gBACd;YACF;YACAC,QAAQ,OAAOC;gBACb,MAAM7B,OAAO4B,MAAM,GAAGC;gBACtB,2FAA2F;gBAC3F,sFAAsF;gBACtF,0FAA0F;gBAC1F,wFAAwF;gBACxF,yFAAyF;gBACzF,8CAA8C;gBAC9C,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;oBACzC,KAAKtC,iBAAiBuC,IAAI,CAAC,CAACC;wBAC1B,IAAIA,KAAK;4BACPL,QAAQM,MAAM,CAACC,KAAK,CAClB,CAAC,8MAA8M,EAAEF,eAAeG,QAAQH,IAAII,OAAO,GAAGJ,IAAI,CAAC,CAAC;wBAEhQ;oBACF;gBACF;YACF;QACF;IACF,EAAC;AAEH,eAAepC,YAAW"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/scripts/cli.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runDownloadFonts } from "./downloadFonts.js";
|
|
3
|
+
// `--verbose` / `-v` prints the full underlying error on failure; without it, failures show
|
|
4
|
+
// only the short message (the usual local case).
|
|
5
|
+
const verbose = process.argv.slice(2).some((arg)=>arg === '--verbose' || arg === '-v');
|
|
6
|
+
runDownloadFonts({
|
|
7
|
+
verbose
|
|
8
|
+
}).catch((err)=>{
|
|
9
|
+
console.error(err);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/scripts/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runDownloadFonts } from './downloadFonts.js'\n\n// `--verbose` / `-v` prints the full underlying error on failure; without it, failures show\n// only the short message (the usual local case).\nconst verbose = process.argv.slice(2).some((arg) => arg === '--verbose' || arg === '-v')\n\nrunDownloadFonts({ verbose }).catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"names":["runDownloadFonts","verbose","process","argv","slice","some","arg","catch","err","console","error","exit"],"mappings":";AACA,SAASA,gBAAgB,QAAQ,qBAAoB;AAErD,4FAA4F;AAC5F,iDAAiD;AACjD,MAAMC,UAAUC,QAAQC,IAAI,CAACC,KAAK,CAAC,GAAGC,IAAI,CAAC,CAACC,MAAQA,QAAQ,eAAeA,QAAQ;AAEnFN,iBAAiB;IAAEC;AAAQ,GAAGM,KAAK,CAAC,CAACC;IACnCC,QAAQC,KAAK,CAACF;IACdN,QAAQS,IAAI,CAAC;AACf"}
|