fontdue-js 3.0.0-alpha8 → 3.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/CHANGELOG.md +23 -0
- package/README.md +253 -1
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CartQuery.graphql.js +9 -3
- package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartStateUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerIDQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
- package/dist/__generated__/CollectionAa_Query.graphql.js +9 -3
- package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/FontFamiliesQuery.graphql.js +9 -3
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.d.ts +20 -0
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.js +80 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.d.ts +18 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.js +56 -0
- package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
- package/dist/__generated__/PrecartAddToCartMutation.graphql.js +9 -3
- package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalCartQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalContainerQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalContainerQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalIndexQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersSlugQuery.graphql.js +9 -3
- package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +1 -8
- package/dist/__generated__/useFontStyle_fontStyle.graphql.d.ts +2 -1
- package/dist/__generated__/useFontStyle_fontStyle.graphql.js +8 -2
- package/dist/__tests__/createFontdueFetch.test.js +276 -0
- package/dist/__tests__/imageLoader.test.js +62 -0
- package/dist/__tests__/metricFallback.test.js +74 -0
- package/dist/__tests__/networkFetch.test.js +188 -0
- package/dist/__tests__/nextAdapter.test.js +273 -18
- package/dist/__tests__/preview.test.js +217 -0
- package/dist/__tests__/previewServer.test.js +118 -0
- package/dist/__tests__/previewState.test.js +63 -0
- package/dist/__tests__/serverConfig.test.js +62 -0
- package/dist/components/BuyButton/index.d.ts +2 -2
- package/dist/components/BuyButton/index.js +3 -3
- package/dist/components/Cart/CartOrder.js +9 -1
- package/dist/components/Cart/orderTracking.js +8 -15
- package/dist/components/CharacterViewer/index.d.ts +2 -2
- package/dist/components/CharacterViewer/index.js +20 -11
- package/dist/components/ConfigContext.d.ts +21 -2
- package/dist/components/ConfigContext.js +12 -2
- package/dist/components/ConnectionErrorToolbar.d.ts +1 -0
- package/dist/components/ConnectionErrorToolbar.js +106 -0
- package/dist/components/FontStyle/index.d.ts +2 -0
- package/dist/components/FontStyle/index.js +4 -2
- package/dist/components/FontdueAdminToolbar/index.d.ts +2 -0
- package/dist/components/FontdueAdminToolbar/index.js +299 -0
- package/dist/components/FontdueAdminToolbar/previewState.d.ts +7 -0
- package/dist/components/FontdueAdminToolbar/previewState.js +58 -0
- package/dist/components/FontdueContextProvider/index.js +4 -2
- package/dist/components/FontdueProvider/index.js +6 -1
- package/dist/components/FontdueProvider/index.server.d.ts +1 -0
- package/dist/components/FontdueProvider/index.server.js +10 -0
- package/dist/components/NewsletterSignup/index.d.ts +2 -2
- package/dist/components/NewsletterSignup/index.js +2 -2
- package/dist/components/Root/index.js +16 -2
- package/dist/components/TestFontsForm/index.d.ts +2 -2
- package/dist/components/TestFontsForm/index.js +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.js +2 -2
- package/dist/components/TypeTester/index.js +3 -1
- package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
- package/dist/components/TypeTester/useTypeTesterStyler.js +70 -20
- package/dist/components/TypeTesters/index.d.ts +2 -2
- package/dist/components/TypeTesters/index.js +3 -3
- package/dist/components/elements/StoreModalUnifiedCheckout.js +8 -0
- package/dist/components/useFontStyle.d.ts +8 -0
- package/dist/components/useFontStyle.js +14 -4
- package/dist/corsError.d.ts +1 -5
- package/dist/corsError.js +23 -13
- package/dist/data/unicodeNamesUrl.d.ts +2 -0
- package/dist/data/unicodeNamesUrl.js +18 -0
- package/dist/data/unicodeNamesVersion.d.ts +1 -0
- package/dist/data/unicodeNamesVersion.js +4 -0
- package/dist/fallbackFontData.d.ts +2 -0
- package/dist/fallbackFontData.js +10 -0
- package/dist/fontdue.css +231 -4
- package/dist/loadFontdueProviderQuery.d.ts +2 -1
- package/dist/loadFontdueProviderQuery.js +5 -2
- package/dist/metricFallback.d.ts +48 -0
- package/dist/metricFallback.js +98 -0
- package/dist/next/config.d.ts +1 -5
- package/dist/next/config.js +14 -1
- package/dist/next/image-loader.js +22 -3
- package/dist/next/index.d.ts +1 -2
- package/dist/next/index.js +14 -6
- package/dist/next/registerSingleTenantResolver.d.ts +1 -0
- package/dist/next/registerSingleTenantResolver.js +35 -0
- package/dist/next/revalidate.js +1 -1
- package/dist/next/tenant.d.ts +10 -2
- package/dist/next/tenant.js +111 -16
- package/dist/preview/constants.d.ts +9 -0
- package/dist/preview/constants.js +117 -0
- package/dist/preview/index.d.ts +53 -0
- package/dist/preview/index.js +190 -0
- package/dist/preview/server.d.ts +20 -0
- package/dist/preview/server.js +89 -0
- package/dist/relay/environment.d.ts +8 -0
- package/dist/relay/environment.js +81 -25
- package/dist/relay/loadSerializableQuery.d.ts +13 -3
- package/dist/relay/loadSerializableQuery.js +2 -0
- package/dist/relay/serverConfig.d.ts +5 -1
- package/dist/relay/serverConfig.js +83 -8
- package/dist/scripts/publishUnicodeData.js +68 -0
- package/dist/scripts/updateUnicodeData.js +41 -6
- package/dist/server/index.d.ts +37 -0
- package/dist/server/index.js +160 -0
- package/package.json +5 -1
- package/types/next-headers.d.ts +9 -0
- package/types/next-navigation.d.ts +10 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const DATA_DIR = path.join(__dirname, '../data');
|
|
7
|
+
|
|
8
|
+
// CDN buckets that serve the unicode-names asset. Both the npm package and the
|
|
9
|
+
// script-tag bundle default to cdn.fontdue.com (prod), so prod is the only
|
|
10
|
+
// strictly-required one — but the asset is reference data, not code, and we
|
|
11
|
+
// push to all three so dev/staging (and any `cdnUrl` override used for testing)
|
|
12
|
+
// resolve too. See docs/GCS.md.
|
|
13
|
+
const BUCKETS = ['fontdue-dev', 'fontdue-staging', 'fontdue-prod'];
|
|
14
|
+
const CACHE_CONTROL = 'public, max-age=31536000, immutable';
|
|
15
|
+
|
|
16
|
+
// Read the content hash written into unicodeNamesVersion.ts. It's the single
|
|
17
|
+
// source of truth for both the CDN filename and the runtime fetch URL, so the
|
|
18
|
+
// uploaded object always matches what CharacterViewer requests.
|
|
19
|
+
export function readUnicodeNamesVersion() {
|
|
20
|
+
const src = readFileSync(path.join(DATA_DIR, 'unicodeNamesVersion.ts'), 'utf-8');
|
|
21
|
+
const match = src.match(/UNICODE_NAMES_VERSION\s*=\s*['"]([^'"]+)['"]/);
|
|
22
|
+
if (!match) {
|
|
23
|
+
throw new Error('Could not read UNICODE_NAMES_VERSION from src/data/unicodeNamesVersion.ts');
|
|
24
|
+
}
|
|
25
|
+
return match[1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Upload data/unicodeNames.json to each CDN bucket at the immutable,
|
|
29
|
+
// content-hashed path the runtime fetches. `-n` (no-clobber) makes this
|
|
30
|
+
// idempotent: a re-run is a no-op unless the data — and therefore the hash —
|
|
31
|
+
// changed. Requires gsutil on PATH and credentials with write access to the
|
|
32
|
+
// buckets (`gcloud auth login`). Throws on failure so the caller can decide
|
|
33
|
+
// whether that's fatal.
|
|
34
|
+
export function publishUnicodeData() {
|
|
35
|
+
let {
|
|
36
|
+
buckets = BUCKETS
|
|
37
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
38
|
+
const version = readUnicodeNamesVersion();
|
|
39
|
+
const source = path.join(DATA_DIR, 'unicodeNames.json');
|
|
40
|
+
const fileName = `unicode-names.${version}.json`;
|
|
41
|
+
for (const bucket of buckets) {
|
|
42
|
+
const dest = `gs://${bucket}/js/dist/${fileName}`;
|
|
43
|
+
console.log(`uploading ${fileName} → ${dest}`);
|
|
44
|
+
execFileSync('gsutil', ['-h', `Cache-Control:${CACHE_CONTROL}`, '-h', 'Content-Type:application/json', 'cp', '-n', '-a', 'public-read', source, dest], {
|
|
45
|
+
stdio: 'inherit'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
console.log(`unicode names ${version} published to: ${buckets.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A friendly hint for the two failure modes worth distinguishing: gsutil not
|
|
52
|
+
// installed vs. installed-but-can't-write (usually missing auth).
|
|
53
|
+
export function describePublishError(err) {
|
|
54
|
+
if (err && err.code === 'ENOENT') {
|
|
55
|
+
return 'gsutil not found on PATH. Install the Google Cloud SDK and authenticate:\n' + ' brew install --cask gcloud-cli # then restart your shell\n' + ' gcloud auth login\n' + 'See docs/GCS.md.';
|
|
56
|
+
}
|
|
57
|
+
return 'Failed to upload to the CDN. Make sure you are authenticated with write\n' + 'access to the buckets:\n' + ' gcloud auth login\n' + 'See docs/GCS.md.';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// CLI entry point: `npm run publish-unicode-data`
|
|
61
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
62
|
+
try {
|
|
63
|
+
publishUnicodeData();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(describePublishError(err));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { publishUnicodeData, describePublishError } from './publishUnicodeData.js';
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
4
8
|
|
|
5
9
|
// URL from which to fetch the Unicode data
|
|
6
10
|
const UNICODE_DATA_URL = 'https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt';
|
|
@@ -31,15 +35,46 @@ function parseAndConvertData(data) {
|
|
|
31
35
|
return result;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
// Main function to fetch, parse, and
|
|
38
|
+
// Main function to fetch, parse, write, and publish the Unicode data.
|
|
39
|
+
//
|
|
40
|
+
// The map is NOT bundled into the package — it's ~1.2 MB of reference data used
|
|
41
|
+
// only to label glyphs in CharacterViewer. Instead it ships as a static JSON
|
|
42
|
+
// asset served from the Fontdue CDN, which CharacterViewer fetches at runtime.
|
|
43
|
+
// The asset is reference data on its own (roughly yearly) release schedule, so
|
|
44
|
+
// it isn't coupled to the npm publish or the app deploy: regenerating the data
|
|
45
|
+
// here also publishes it to the CDN, and any code that later references the new
|
|
46
|
+
// hash just works.
|
|
47
|
+
//
|
|
48
|
+
// We write two files:
|
|
49
|
+
// - data/unicodeNames.json the raw map (uploaded to the CDN; excluded
|
|
50
|
+
// from the npm tarball via .npmignore `src/*`)
|
|
51
|
+
// - data/unicodeNamesVersion.ts a content hash of that JSON
|
|
52
|
+
//
|
|
53
|
+
// The hash is the single source of truth for the immutable CDN filename AND the
|
|
54
|
+
// runtime fetch URL, so the two always agree. Refresh = re-run this script.
|
|
35
55
|
async function main() {
|
|
36
56
|
try {
|
|
37
57
|
const data = await fetchUnicodeData();
|
|
38
58
|
const jsonData = parseAndConvertData(data);
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
const json = JSON.stringify(jsonData);
|
|
60
|
+
const hash = crypto.createHash('sha256').update(json).digest('hex').slice(0, 16);
|
|
61
|
+
const dataDir = path.join(__dirname, '../data');
|
|
62
|
+
fs.writeFileSync(path.join(dataDir, 'unicodeNames.json'), json);
|
|
63
|
+
fs.writeFileSync(path.join(dataDir, 'unicodeNamesVersion.ts'), `// Generated by \`npm run update-unicode-data\`. Content hash of unicodeNames.json.\n` + `// Both the CDN asset filename and the runtime fetch URL derive from this, so\n` + `// they always agree. Do not edit by hand.\n` + `export const UNICODE_NAMES_VERSION = ${JSON.stringify(hash)};\n`);
|
|
64
|
+
console.log(`unicode data written: ${Object.keys(jsonData).length} entries, hash ${hash}`);
|
|
65
|
+
|
|
66
|
+
// Publish the freshly-written map to the CDN. If this fails (no gsutil /
|
|
67
|
+
// credentials), don't fail the regen — the data is valid and the hash is
|
|
68
|
+
// committed; just point the way to publishing it before the change ships.
|
|
69
|
+
try {
|
|
70
|
+
publishUnicodeData();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.warn('\n⚠ Data regenerated, but the CDN upload was skipped or failed. The new\n' + ' hash is in unicodeNamesVersion.ts, but the asset must be on the CDN\n' + ' before any code referencing it ships. Once you have gsutil + creds:\n' + ' npm run publish-unicode-data\n');
|
|
73
|
+
console.warn(describePublishError(err));
|
|
74
|
+
}
|
|
41
75
|
} catch (error) {
|
|
42
76
|
console.error('Error:', error);
|
|
77
|
+
process.exit(1);
|
|
43
78
|
}
|
|
44
79
|
}
|
|
45
80
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare class FontdueNotFoundError extends Error {
|
|
2
|
+
readonly queryName: string;
|
|
3
|
+
constructor(queryName: string);
|
|
4
|
+
}
|
|
5
|
+
export interface CreateFontdueFetchOptions {
|
|
6
|
+
/**
|
|
7
|
+
* GraphQL base URL (without the trailing /graphql), e.g.
|
|
8
|
+
* https://acme.fontdue.com. Falls back to the per-render config
|
|
9
|
+
* (resolveFontdueServerConfig().url, set by the Next adapter for the current
|
|
10
|
+
* tenant) and then to FONTDUE_URL / PUBLIC_FONTDUE_URL / VITE_FONTDUE_URL
|
|
11
|
+
* from the environment.
|
|
12
|
+
*/
|
|
13
|
+
url?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Extra headers sent with every fetch from this fetcher, merged over the
|
|
16
|
+
* ambient per-render headers (explicit winning). Pass
|
|
17
|
+
* `previewAuthHeaders(token)` (from fontdue-js/preview) to reveal hidden
|
|
18
|
+
* fonts while an admin is in preview.
|
|
19
|
+
*/
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Next.js data-cache tags. When present — passed here or via the per-render
|
|
23
|
+
* config — the fetch is opted into Next's data cache so the revalidate
|
|
24
|
+
* handler can purge it. The global `graphql` tag is always included (and
|
|
25
|
+
* deduped), so pass the per-site tags alone or the full `endpoint.tags`.
|
|
26
|
+
* Omit (or pass `[]`) to leave the fetch uncached, which is what preview
|
|
27
|
+
* renders want. Inert outside Next.
|
|
28
|
+
*/
|
|
29
|
+
cacheTags?: string[];
|
|
30
|
+
}
|
|
31
|
+
export type FontdueFetch = <Q, V = void>(queryName: string, query: string, variables?: V) => Promise<Q>;
|
|
32
|
+
/**
|
|
33
|
+
* Build a server-side GraphQL fetcher bound to a base URL and a set of headers.
|
|
34
|
+
* The returned function takes the operation name, the query text (load it from
|
|
35
|
+
* your .graphql files), and variables, and resolves to the unwrapped `data`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createFontdueFetch(options?: CreateFontdueFetchOptions): FontdueFetch;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Server-side GraphQL transport for host apps.
|
|
2
|
+
//
|
|
3
|
+
// Every framework example ships a near-identical `fetchGraphql` — resolve the
|
|
4
|
+
// Fontdue URL, POST the query, unwrap `data`, throw on errors. This exports
|
|
5
|
+
// that transport once so apps don't re-implement it, so the preview
|
|
6
|
+
// Authorization header is forwarded in one place, and so the same fetcher
|
|
7
|
+
// works in every server runtime. It is the sibling of the Relay network layer
|
|
8
|
+
// (relay/environment.ts) that powers the embedded components: both read the
|
|
9
|
+
// per-render config from resolveFontdueServerConfig() and apply it the same way.
|
|
10
|
+
//
|
|
11
|
+
// All three per-request inputs — the base URL, the admin preview token, and
|
|
12
|
+
// the Next cache tags — are resolved per call from two sources, so a single
|
|
13
|
+
// module-level fetcher serves every render:
|
|
14
|
+
//
|
|
15
|
+
// 1. Ambient context (preferred): whatever set the per-render config.
|
|
16
|
+
// - Astro / React Router etc.: runWithPreview (fontdue-js/preview/server)
|
|
17
|
+
// rides the token through AsyncLocalStorage.
|
|
18
|
+
// - Next: the single-tenant ambient resolver (registered by mounting
|
|
19
|
+
// <FontdueProvider>, no per-render call) or __prepareFontdueRender
|
|
20
|
+
// (fontdue-js/next, multi-tenant) supply the site URL, cache tags, and
|
|
21
|
+
// preview token for the render.
|
|
22
|
+
// Either way:
|
|
23
|
+
//
|
|
24
|
+
// const fetchGraphql = createFontdueFetch(); // once, at module scope
|
|
25
|
+
// const data = await fetchGraphql('Index', indexQuery, vars);
|
|
26
|
+
//
|
|
27
|
+
// 2. Explicit options: bind a fetcher with a url/headers/cacheTags, for
|
|
28
|
+
// runtimes where the ambient context can't propagate (route handlers,
|
|
29
|
+
// Astro edgeMiddleware — see runWithPreview's notes):
|
|
30
|
+
//
|
|
31
|
+
// const fetchGraphql = createFontdueFetch({
|
|
32
|
+
// url: endpoint.origin,
|
|
33
|
+
// headers: previewAuthHeaders(token), // from fontdue-js/preview
|
|
34
|
+
// cacheTags: [`graphql:${endpoint.domain}`],
|
|
35
|
+
// });
|
|
36
|
+
//
|
|
37
|
+
// Explicit options override the ambient context (headers merge, explicit
|
|
38
|
+
// winning), so the two compose.
|
|
39
|
+
//
|
|
40
|
+
// Caching: when the resolved config carries cacheTags (the Next adapter sets
|
|
41
|
+
// them per render; supply them explicitly elsewhere) the fetch is opted into
|
|
42
|
+
// Next's data cache (`force-cache` + tags) so /api/revalidate can purge it.
|
|
43
|
+
// With no tags the fetch is left uncached, which is what preview renders and
|
|
44
|
+
// the CDN-cached frameworks (Astro/RR7 cache HTML at the response layer) want.
|
|
45
|
+
// The Next fetch hints are inert in other runtimes: Node's fetch accepts and
|
|
46
|
+
// ignores the cache mode, and `next` is just an unknown init property.
|
|
47
|
+
|
|
48
|
+
import { resolveFontdueServerConfig } from '../relay/serverConfig.js';
|
|
49
|
+
import { PREVIEW_HEADER } from '../preview/constants.js';
|
|
50
|
+
function readEnv(name) {
|
|
51
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
52
|
+
const v = process.env[name];
|
|
53
|
+
if (v != null && v !== '') return v;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
// @ts-ignore - import.meta.env is Vite/Astro-specific
|
|
57
|
+
const metaEnv = typeof import.meta !== 'undefined' ? import.meta.env : undefined;
|
|
58
|
+
if (metaEnv) {
|
|
59
|
+
const v = metaEnv[name] ?? metaEnv[`PUBLIC_${name}`] ?? metaEnv[`VITE_${name}`];
|
|
60
|
+
if (v != null && v !== '') return v;
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function resolveFontdueUrl() {
|
|
66
|
+
return readEnv('FONTDUE_URL') ?? readEnv('NEXT_PUBLIC_FONTDUE_URL');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Storefront fetches join Next's data cache only in production. In local dev
|
|
70
|
+
// (`next dev`) that cache plus on-demand `revalidateTag` don't reliably
|
|
71
|
+
// refresh — and the storefront proxy strips the browser's `no-cache`, so a
|
|
72
|
+
// hard reload can't bust a stale entry either. Leaving dev fetches uncached
|
|
73
|
+
// makes every render fetch fresh, so admin content changes show on the next
|
|
74
|
+
// reload with no revalidation needed. Inert outside Next. Read per call (not
|
|
75
|
+
// memoized) so the literal `process.env.NODE_ENV` is inlined at build time and
|
|
76
|
+
// tests can stub it; `typeof process` guards non-Node bundles.
|
|
77
|
+
function cacheInProduction() {
|
|
78
|
+
return typeof process !== 'undefined' && process.env.NODE_ENV === 'production';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Thrown when the Fontdue server 404s — i.e. the requested host doesn't resolve
|
|
82
|
+
// to a site. Catch it to surface your framework's own not-found response.
|
|
83
|
+
export class FontdueNotFoundError extends Error {
|
|
84
|
+
constructor(queryName) {
|
|
85
|
+
super(`Fontdue returned 404 for query "${queryName}"`);
|
|
86
|
+
this.queryName = queryName;
|
|
87
|
+
this.name = 'FontdueNotFoundError';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build a server-side GraphQL fetcher bound to a base URL and a set of headers.
|
|
92
|
+
* The returned function takes the operation name, the query text (load it from
|
|
93
|
+
* your .graphql files), and variables, and resolves to the unwrapped `data`.
|
|
94
|
+
*/
|
|
95
|
+
export function createFontdueFetch() {
|
|
96
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
97
|
+
return async function fetchGraphql(queryName, query, variables) {
|
|
98
|
+
var _json$errors, _json$errors$;
|
|
99
|
+
// Per-render config (a runWithPreview AsyncLocalStorage store, the Next
|
|
100
|
+
// render-scoped slot, or the Next single-tenant ambient resolver). Awaited
|
|
101
|
+
// per call so a single module-level fetcher picks up the current request's
|
|
102
|
+
// tenant URL, preview token, and cache tags.
|
|
103
|
+
const config = await resolveFontdueServerConfig();
|
|
104
|
+
|
|
105
|
+
// URL: explicit option, then the per-render tenant URL, then the env.
|
|
106
|
+
const base = options.url ?? (config === null || config === void 0 ? void 0 : config.url) ?? resolveFontdueUrl();
|
|
107
|
+
if (!base) {
|
|
108
|
+
throw new Error('fontdue-js: no Fontdue URL configured. Set FONTDUE_URL / ' + 'PUBLIC_FONTDUE_URL / VITE_FONTDUE_URL, pass { url } to ' + 'createFontdueFetch, or set it on the per-render config.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cache tags: explicit option wins, else the per-render config's. A
|
|
112
|
+
// non-empty list opts the fetch into Next's data cache (inert elsewhere);
|
|
113
|
+
// an empty/absent list leaves it uncached (preview + CDN-cached frameworks).
|
|
114
|
+
const cacheTags = options.cacheTags ?? (config === null || config === void 0 ? void 0 : config.cacheTags);
|
|
115
|
+
|
|
116
|
+
// Ambient headers (e.g. tenant + preview token), overridden by any explicit
|
|
117
|
+
// per-fetcher headers.
|
|
118
|
+
const headers = {
|
|
119
|
+
'content-type': 'application/json',
|
|
120
|
+
...(config === null || config === void 0 ? void 0 : config.headers),
|
|
121
|
+
...options.headers
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Declare preview intent explicitly: a forwarded admin token means this is a
|
|
125
|
+
// preview render → reveal hidden fonts; otherwise "false" so a public (or
|
|
126
|
+
// merely session-authenticated) request never gets the hidden-fonts view.
|
|
127
|
+
// Older clients omit the header and keep the legacy behavior. Mirrors the
|
|
128
|
+
// Relay network layer (relay/environment.ts) and FontageWeb.Schema.Context.
|
|
129
|
+
headers[PREVIEW_HEADER] = headers.authorization != null || headers.Authorization != null ? 'true' : 'false';
|
|
130
|
+
|
|
131
|
+
// `next` is a Next.js-only fetch extension, ignored by other runtimes.
|
|
132
|
+
const init = {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
query,
|
|
136
|
+
variables
|
|
137
|
+
}),
|
|
138
|
+
headers
|
|
139
|
+
};
|
|
140
|
+
if (cacheInProduction() && Array.isArray(cacheTags) && cacheTags.length > 0) {
|
|
141
|
+
init.cache = 'force-cache';
|
|
142
|
+
// The global `graphql` tag is always present; dedupe so callers can pass
|
|
143
|
+
// the per-site tags alone or the full `endpoint.tags` (which include it).
|
|
144
|
+
init.next = {
|
|
145
|
+
tags: Array.from(new Set(['graphql', ...cacheTags]))
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const response = await fetch(`${base}/graphql?query=${queryName}`, init);
|
|
149
|
+
if (response.status === 404) throw new FontdueNotFoundError(queryName);
|
|
150
|
+
if (response.status !== 200) {
|
|
151
|
+
throw new Error(`Fontdue request failed: ${response.status}`);
|
|
152
|
+
}
|
|
153
|
+
const json = await response.json();
|
|
154
|
+
const errorMessage = (_json$errors = json.errors) === null || _json$errors === void 0 ? void 0 : (_json$errors$ = _json$errors[0]) === null || _json$errors$ === void 0 ? void 0 : _json$errors$.message;
|
|
155
|
+
if (errorMessage) {
|
|
156
|
+
throw new Error(`Fontdue GraphQL error: ${errorMessage}`);
|
|
157
|
+
}
|
|
158
|
+
return json.data;
|
|
159
|
+
};
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontdue-js",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npm run relay && run-p build-js build-css build-ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"build-css": "sass --load-path=node_modules --no-source-map src/index.scss dist/fontdue.css",
|
|
10
10
|
"relay": "relay-compiler",
|
|
11
11
|
"update-unicode-data": "node src/scripts/updateUnicodeData.js",
|
|
12
|
+
"publish-unicode-data": "node src/scripts/publishUnicodeData.js",
|
|
12
13
|
"test": "vitest run",
|
|
13
14
|
"test:watch": "vitest",
|
|
14
15
|
"prepublishOnly": "npm run build"
|
|
@@ -82,6 +83,9 @@
|
|
|
82
83
|
},
|
|
83
84
|
"exports": {
|
|
84
85
|
".": "./dist/index.js",
|
|
86
|
+
"./preview": "./dist/preview/index.js",
|
|
87
|
+
"./preview/server": "./dist/preview/server.js",
|
|
88
|
+
"./server": "./dist/server/index.js",
|
|
85
89
|
"./next": "./dist/next/index.js",
|
|
86
90
|
"./next/config": "./dist/next/config.js",
|
|
87
91
|
"./next/revalidate": "./dist/next/revalidate.js",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Minimal declaration so src/next/tenant.ts type-checks without `next`
|
|
2
|
+
// installed (it's the host app's dependency; this package only ever runs the
|
|
3
|
+
// import inside a Next.js server).
|
|
4
|
+
declare module 'next/headers' {
|
|
5
|
+
export function draftMode(): Promise<{ isEnabled: boolean }>;
|
|
6
|
+
export function cookies(): Promise<{
|
|
7
|
+
get(name: string): { value: string } | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Minimal declaration so src/next/tenant.ts type-checks without `next`
|
|
2
|
+
// installed (it's the host app's dependency; this package only ever runs the
|
|
3
|
+
// import inside a Next.js server).
|
|
4
|
+
declare module 'next/navigation' {
|
|
5
|
+
export function notFound(): never;
|
|
6
|
+
// Re-throws Next's internal control-flow errors (dynamic-rendering bailout,
|
|
7
|
+
// notFound, redirect) and is a no-op for everything else. Used so the preview
|
|
8
|
+
// resolver doesn't swallow the bailout that forces a dynamic render.
|
|
9
|
+
export function unstable_rethrow(error: unknown): void;
|
|
10
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -2,7 +2,16 @@ import { defineConfig } from 'vitest/config';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
+
// Stand-in for the babel define plugin that inlines the package version.
|
|
6
|
+
define: {
|
|
7
|
+
__FONTDUE_JS_VERSION__: JSON.stringify('0.0.0-test'),
|
|
8
|
+
},
|
|
5
9
|
test: {
|
|
10
|
+
// Only the TypeScript sources are the tests of record. Without this, a
|
|
11
|
+
// prior `npm run build` leaves compiled copies in dist/__tests__ that
|
|
12
|
+
// vitest also picks up — running each suite twice and racing on
|
|
13
|
+
// module-level singletons (e.g. the ambient config resolver).
|
|
14
|
+
include: ['src/**/*.test.{ts,tsx}'],
|
|
6
15
|
alias: {
|
|
7
16
|
'__generated__': path.resolve(__dirname, 'src/__generated__'),
|
|
8
17
|
},
|