@brandlab-digital/site-next 3.3.3 → 3.5.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/dist/google-business-reviews-server.d.ts +30 -0
- package/dist/google-business-reviews-server.d.ts.map +1 -0
- package/dist/google-business-reviews-server.js +62 -0
- package/dist/google-business-reviews-server.js.map +1 -0
- package/dist/robots.d.ts.map +1 -1
- package/dist/robots.js +6 -5
- package/dist/robots.js.map +1 -1
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +2 -8
- package/dist/sitemaps.js.map +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { type BusinessProfileReviews } from '@brandlab-digital/site-core/google-business-reviews';
|
|
3
|
+
/** Next keeps a successful feed response for a day, so a site asks the feed at most once a day per page. */
|
|
4
|
+
export declare const BUSINESS_PROFILE_REVIEWS_REVALIDATE_SECONDS = 86400;
|
|
5
|
+
/** The Brandlab reviews feed; `?site=<slug>` names the site. */
|
|
6
|
+
export declare const BUSINESS_PROFILE_REVIEWS_FEED_ENDPOINT = "https://brandlab-reviews-feed.vercel.app/api/reviews";
|
|
7
|
+
export declare const BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES: {
|
|
8
|
+
readonly token: "REVIEWS_FEED_TOKEN";
|
|
9
|
+
readonly feedUrl: "REVIEWS_FEED_URL";
|
|
10
|
+
};
|
|
11
|
+
export interface GetBusinessProfileReviewsOptions {
|
|
12
|
+
/** The site's slug in the reviews feed (lowercase kebab-case), for example `hr-kitchens`. */
|
|
13
|
+
site: string;
|
|
14
|
+
/** Defaults to `process.env`: reads `REVIEWS_FEED_TOKEN` and the optional `REVIEWS_FEED_URL`. */
|
|
15
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
/** Defaults to the global `fetch`. */
|
|
17
|
+
fetcher?: typeof fetch;
|
|
18
|
+
/** Overrides `REVIEWS_FEED_URL` and the default feed address. Must be https. */
|
|
19
|
+
feedUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Google reviews from the Business Profile API, served by the Brandlab reviews feed, for a
|
|
23
|
+
* server component. Visitors never trigger a Google request and the site holds no Google
|
|
24
|
+
* credential: only the feed's bearer token. Returns null without `REVIEWS_FEED_TOKEN` and on
|
|
25
|
+
* any failure (network, timeout after 5 seconds, an error status, an unexpected body), so
|
|
26
|
+
* the page shows its no-reviews state. Next caches only a successful response, for a day.
|
|
27
|
+
* A `site` that is not lowercase kebab-case is a configuration error and throws.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getBusinessProfileReviews(options: GetBusinessProfileReviewsOptions): Promise<BusinessProfileReviews | null>;
|
|
30
|
+
//# sourceMappingURL=google-business-reviews-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-business-reviews-server.d.ts","sourceRoot":"","sources":["../src/google-business-reviews-server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,qDAAqD,CAAA;AAE5D,4GAA4G;AAC5G,eAAO,MAAM,2CAA2C,QAAS,CAAA;AAEjE,gEAAgE;AAChE,eAAO,MAAM,sCAAsC,yDAAyD,CAAA;AAE5G,eAAO,MAAM,0CAA0C;;;CAG7C,CAAA;AAKV,MAAM,WAAW,gCAAgC;IAC/C,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAA;IACZ,iGAAiG;IACjG,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC1D,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IACtB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAgBD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAyBxC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { parseBusinessProfileReviews, } from '@brandlab-digital/site-core/google-business-reviews';
|
|
3
|
+
/** Next keeps a successful feed response for a day, so a site asks the feed at most once a day per page. */
|
|
4
|
+
export const BUSINESS_PROFILE_REVIEWS_REVALIDATE_SECONDS = 86_400;
|
|
5
|
+
/** The Brandlab reviews feed; `?site=<slug>` names the site. */
|
|
6
|
+
export const BUSINESS_PROFILE_REVIEWS_FEED_ENDPOINT = 'https://brandlab-reviews-feed.vercel.app/api/reviews';
|
|
7
|
+
export const BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES = {
|
|
8
|
+
token: 'REVIEWS_FEED_TOKEN',
|
|
9
|
+
feedUrl: 'REVIEWS_FEED_URL',
|
|
10
|
+
};
|
|
11
|
+
const TIMEOUT_MS = 5_000;
|
|
12
|
+
const SITE_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13
|
+
function setting(value) {
|
|
14
|
+
const trimmed = value?.trim();
|
|
15
|
+
return trimmed ? trimmed : undefined;
|
|
16
|
+
}
|
|
17
|
+
function httpsUrl(value) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
return url.protocol === 'https:' ? url.toString() : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Google reviews from the Business Profile API, served by the Brandlab reviews feed, for a
|
|
28
|
+
* server component. Visitors never trigger a Google request and the site holds no Google
|
|
29
|
+
* credential: only the feed's bearer token. Returns null without `REVIEWS_FEED_TOKEN` and on
|
|
30
|
+
* any failure (network, timeout after 5 seconds, an error status, an unexpected body), so
|
|
31
|
+
* the page shows its no-reviews state. Next caches only a successful response, for a day.
|
|
32
|
+
* A `site` that is not lowercase kebab-case is a configuration error and throws.
|
|
33
|
+
*/
|
|
34
|
+
export async function getBusinessProfileReviews(options) {
|
|
35
|
+
if (typeof options.site !== 'string' || !SITE_SLUG.test(options.site)) {
|
|
36
|
+
throw new Error('Business Profile reviews site must be a lowercase kebab-case slug.');
|
|
37
|
+
}
|
|
38
|
+
const environment = options.environment ?? process.env;
|
|
39
|
+
const token = setting(environment[BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES.token]);
|
|
40
|
+
if (!token)
|
|
41
|
+
return null;
|
|
42
|
+
// The bearer token goes only to an https address.
|
|
43
|
+
const url = httpsUrl(setting(options.feedUrl) ??
|
|
44
|
+
setting(environment[BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES.feedUrl]) ??
|
|
45
|
+
`${BUSINESS_PROFILE_REVIEWS_FEED_ENDPOINT}?site=${options.site}`);
|
|
46
|
+
if (!url)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const response = await (options.fetcher ?? fetch)(url, {
|
|
50
|
+
headers: { authorization: `Bearer ${token}`, accept: 'application/json' },
|
|
51
|
+
next: { revalidate: BUSINESS_PROFILE_REVIEWS_REVALIDATE_SECONDS },
|
|
52
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok)
|
|
55
|
+
return null;
|
|
56
|
+
return parseBusinessProfileReviews(await response.json());
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=google-business-reviews-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-business-reviews-server.js","sourceRoot":"","sources":["../src/google-business-reviews-server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAEpB,OAAO,EACL,2BAA2B,GAE5B,MAAM,qDAAqD,CAAA;AAE5D,4GAA4G;AAC5G,MAAM,CAAC,MAAM,2CAA2C,GAAG,MAAM,CAAA;AAEjE,gEAAgE;AAChE,MAAM,CAAC,MAAM,sCAAsC,GAAG,sDAAsD,CAAA;AAE5G,MAAM,CAAC,MAAM,0CAA0C,GAAG;IACxD,KAAK,EAAE,oBAAoB;IAC3B,OAAO,EAAE,kBAAkB;CACnB,CAAA;AAEV,MAAM,UAAU,GAAG,KAAK,CAAA;AACxB,MAAM,SAAS,GAAG,4BAA4B,CAAA;AAa9C,SAAS,OAAO,CAAC,KAAyB;IACxC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAA;IAC7B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AACtC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;QAC1B,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAyC;IAEzC,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAA;IACvF,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAA;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,0CAA0C,CAAC,KAAK,CAAC,CAAC,CAAA;IACpF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,kDAAkD;IAClD,MAAM,GAAG,GAAG,QAAQ,CAClB,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,CAAC,WAAW,CAAC,0CAA0C,CAAC,OAAO,CAAC,CAAC;QACxE,GAAG,sCAAsC,SAAS,OAAO,CAAC,IAAI,EAAE,CACnE,CAAA;IACD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE;YACrD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACzE,IAAI,EAAE,EAAE,UAAU,EAAE,2CAA2C,EAAE;YACjE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC;SACzB,CAAC,CAAA;QACjB,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QAC7B,OAAO,2BAA2B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["import 'server-only'\n\nimport {\n parseBusinessProfileReviews,\n type BusinessProfileReviews,\n} from '@brandlab-digital/site-core/google-business-reviews'\n\n/** Next keeps a successful feed response for a day, so a site asks the feed at most once a day per page. */\nexport const BUSINESS_PROFILE_REVIEWS_REVALIDATE_SECONDS = 86_400\n\n/** The Brandlab reviews feed; `?site=<slug>` names the site. */\nexport const BUSINESS_PROFILE_REVIEWS_FEED_ENDPOINT = 'https://brandlab-reviews-feed.vercel.app/api/reviews'\n\nexport const BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES = {\n token: 'REVIEWS_FEED_TOKEN',\n feedUrl: 'REVIEWS_FEED_URL',\n} as const\n\nconst TIMEOUT_MS = 5_000\nconst SITE_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\nexport interface GetBusinessProfileReviewsOptions {\n /** The site's slug in the reviews feed (lowercase kebab-case), for example `hr-kitchens`. */\n site: string\n /** Defaults to `process.env`: reads `REVIEWS_FEED_TOKEN` and the optional `REVIEWS_FEED_URL`. */\n environment?: Readonly<Record<string, string | undefined>>\n /** Defaults to the global `fetch`. */\n fetcher?: typeof fetch\n /** Overrides `REVIEWS_FEED_URL` and the default feed address. Must be https. */\n feedUrl?: string\n}\n\nfunction setting(value: string | undefined) {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nfunction httpsUrl(value: string) {\n try {\n const url = new URL(value)\n return url.protocol === 'https:' ? url.toString() : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Google reviews from the Business Profile API, served by the Brandlab reviews feed, for a\n * server component. Visitors never trigger a Google request and the site holds no Google\n * credential: only the feed's bearer token. Returns null without `REVIEWS_FEED_TOKEN` and on\n * any failure (network, timeout after 5 seconds, an error status, an unexpected body), so\n * the page shows its no-reviews state. Next caches only a successful response, for a day.\n * A `site` that is not lowercase kebab-case is a configuration error and throws.\n */\nexport async function getBusinessProfileReviews(\n options: GetBusinessProfileReviewsOptions,\n): Promise<BusinessProfileReviews | null> {\n if (typeof options.site !== 'string' || !SITE_SLUG.test(options.site)) {\n throw new Error('Business Profile reviews site must be a lowercase kebab-case slug.')\n }\n const environment = options.environment ?? process.env\n const token = setting(environment[BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES.token])\n if (!token) return null\n // The bearer token goes only to an https address.\n const url = httpsUrl(\n setting(options.feedUrl) ??\n setting(environment[BUSINESS_PROFILE_REVIEWS_ENVIRONMENT_NAMES.feedUrl]) ??\n `${BUSINESS_PROFILE_REVIEWS_FEED_ENDPOINT}?site=${options.site}`,\n )\n if (!url) return null\n try {\n const response = await (options.fetcher ?? fetch)(url, {\n headers: { authorization: `Bearer ${token}`, accept: 'application/json' },\n next: { revalidate: BUSINESS_PROFILE_REVIEWS_REVALIDATE_SECONDS },\n signal: AbortSignal.timeout(TIMEOUT_MS),\n } as RequestInit)\n if (!response.ok) return null\n return parseBusinessProfileReviews(await response.json())\n } catch {\n return null\n }\n}\n"]}
|
package/dist/robots.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;
|
|
1
|
+
{"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAYD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,kBAAkB,WAG3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAAO,SAGzB,sBAAsB,CAajD"}
|
package/dist/robots.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { resolveBrandlabDeployment, } from '@brandlab-digital/site-core/site';
|
|
2
2
|
/**
|
|
3
3
|
* The shared API routes crawlers have no use for, kept out of a production
|
|
4
|
-
* robots.txt (from 3.3.0): the contact endpoint
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* robots.txt (from 3.3.0): the contact endpoint. A robots.txt path is a prefix, so
|
|
5
|
+
* it covers its subpaths. Only this: not a blanket `/api/`, because a site may serve
|
|
6
|
+
* something public there. From 3.5.0 `/api/google-reviews` is no longer listed:
|
|
7
|
+
* sites read Google reviews from the Brandlab reviews feed on the server
|
|
8
|
+
* (`site-next/google-business-reviews/server`) and no longer serve that route.
|
|
8
9
|
*/
|
|
9
|
-
const SHARED_API_DISALLOW = ['/api/contact'
|
|
10
|
+
const SHARED_API_DISALLOW = ['/api/contact'];
|
|
10
11
|
/**
|
|
11
12
|
* Crawlers may index any production build except a Vercel Preview: the real
|
|
12
13
|
* production deployment and a local `next start`. `next dev` and every
|
package/dist/robots.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC
|
|
1
|
+
{"version":3,"file":"robots.js","sourceRoot":"","sources":["../src/robots.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kCAAkC,CAAA;AAOzC;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,CAAC,cAAc,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAA8B;IAC1E,OAAO,UAAU,CAAC,WAAW,KAAK,YAAY;QAC5C,CAAC,UAAU,CAAC,WAAW,KAAK,aAAa,IAAI,UAAU,CAAC,eAAe,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAkB,EAClB,UAAiD,EAAE;IAEnD,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,YAAY,cAAc,CAAA;IAClD,OAAO,SAAS,MAAM;QACpB,MAAM,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAA;QAChF,IAAI,CAAC,6BAA6B,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;QACvD,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,gCAAgC;QAChC,OAAO;YACL,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,mBAAmB,CAAC,EAAE,CAAC;YAC3E,OAAO;SACR,CAAA;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import {\n resolveBrandlabDeployment,\n type BrandlabDeployment,\n type BrandlabEnvironment,\n type BrandlabSite,\n} from '@brandlab-digital/site-core/site'\n\nexport interface BrandlabRobotsMetadata {\n rules: Array<{ userAgent: string; allow?: string; disallow?: string | string[] }>\n sitemap?: string\n}\n\n/**\n * The shared API routes crawlers have no use for, kept out of a production\n * robots.txt (from 3.3.0): the contact endpoint. A robots.txt path is a prefix, so\n * it covers its subpaths. Only this: not a blanket `/api/`, because a site may serve\n * something public there. From 3.5.0 `/api/google-reviews` is no longer listed:\n * sites read Google reviews from the Brandlab reviews feed on the server\n * (`site-next/google-business-reviews/server`) and no longer serve that route.\n */\nconst SHARED_API_DISALLOW = ['/api/contact']\n\n/**\n * Crawlers may index any production build except a Vercel Preview: the real\n * production deployment and a local `next start`. `next dev` and every\n * Preview are closed. There is no environment override.\n */\nexport function isBrandlabIndexableDeployment(deployment: BrandlabDeployment) {\n return deployment.environment === 'production' ||\n (deployment.environment === 'development' && deployment.productionBuild)\n}\n\n/**\n * `src/app/robots.ts`: `export default createBrandlabRobots(site)`.\n * The deployment is resolved when Next renders robots.txt. Production allows\n * everything but the shared API routes and names the canonical sitemap; every\n * other deployment keeps `Disallow: /`.\n */\nexport function createBrandlabRobots(\n site: BrandlabSite,\n options: { environment?: BrandlabEnvironment } = {},\n) {\n const sitemap = `${site.canonicalUrl}/sitemap.xml`\n return function robots(): BrandlabRobotsMetadata {\n const deployment = resolveBrandlabDeployment(options.environment ?? process.env)\n if (!isBrandlabIndexableDeployment(deployment)) {\n return { rules: [{ userAgent: '*', disallow: '/' }] }\n }\n // No `host` directive: it is a non-standard robots.txt extension ignored\n // by Google and Bing; canonical-host consolidation is owned by permanent\n // redirects and canonical URLs.\n return {\n rules: [{ userAgent: '*', allow: '/', disallow: [...SHARED_API_DISALLOW] }],\n sitemap,\n }\n }\n}\n"]}
|
package/dist/sitemaps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemaps.d.ts","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAqB,MAAM,oCAAoC,CAAA;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,
|
|
1
|
+
{"version":3,"file":"sitemaps.d.ts","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAqB,MAAM,oCAAoC,CAAA;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAQL,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAE7C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,QAAQ,CAI7E;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE;IAAE,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GACjD,QAAQ,CAIV;AAED,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEpD,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,IAAI,EAAE,mBAAmB,CAAA;IACzB,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;IAClC,uFAAuF;IACvF,GAAG,IAAI,QAAQ,CAAA;IACf,qEAAqE;IACrE,UAAU,IAAI,MAAM,EAAE,CAAA;IACtB,sFAAsF;IACtF,KAAK,EAAE;QACL,oBAAoB,IAAI;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;QAC3C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KACpF,CAAA;CACF;AA2BD;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,YAAY,EAClB,OAAO,GAAE,2BAAgD,GACxD,oBAAoB,CAsCtB"}
|
package/dist/sitemaps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata';
|
|
2
|
-
import { buildSitemapEntries, createSitemapChunks, getSitemapXmlHeaders, serializeSitemap, serializeSitemapIndex, } from '@brandlab-digital/site-core/sitemaps';
|
|
2
|
+
import { BRANDLAB_SITEMAP_FAMILIES, buildSitemapEntries, createSitemapChunks, getSitemapXmlHeaders, serializeSitemap, serializeSitemapIndex, } from '@brandlab-digital/site-core/sitemaps';
|
|
3
3
|
export function sitemapXmlResponse(entries) {
|
|
4
4
|
return new Response(serializeSitemap(entries), {
|
|
5
5
|
headers: getSitemapXmlHeaders(),
|
|
@@ -10,13 +10,7 @@ export function sitemapIndexXmlResponse(paths, options) {
|
|
|
10
10
|
headers: getSitemapXmlHeaders(),
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
|
-
const SPLIT_SITEMAP_FAMILIES =
|
|
14
|
-
{ name: 'services', kinds: ['service'] },
|
|
15
|
-
{ name: 'locations', kinds: ['location'] },
|
|
16
|
-
{ name: 'service-locations', kinds: ['service-location'] },
|
|
17
|
-
{ name: 'blog', kinds: ['blog-post'] },
|
|
18
|
-
{ name: 'projects', kinds: ['project'] },
|
|
19
|
-
];
|
|
13
|
+
const SPLIT_SITEMAP_FAMILIES = BRANDLAB_SITEMAP_FAMILIES;
|
|
20
14
|
const SPLIT_SITEMAP_PREFIX = '/sitemaps';
|
|
21
15
|
const DEFAULT_SPLIT_MAX_URLS = 1_000;
|
|
22
16
|
function splitSitemapChunks(routes, siteUrl, maxUrlsPerSitemap) {
|
package/dist/sitemaps.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sitemaps.js","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAA;AAGrE,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,GAGtB,MAAM,sCAAsC,CAAA;AAE7C,MAAM,UAAU,kBAAkB,CAAC,OAAgC;IACjE,OAAO,IAAI,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC7C,OAAO,EAAE,oBAAoB,EAAE;KAChC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,KAAwB,EACxB,OAAkD;IAElD,OAAO,IAAI,QAAQ,CAAC,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE;QACzD,OAAO,EAAE,oBAAoB,EAAE;KAChC,CAAC,CAAA;AACJ,CAAC;AA4BD,MAAM,sBAAsB,GAAqE
|
|
1
|
+
{"version":3,"file":"sitemaps.js","sourceRoot":"","sources":["../src/sitemaps.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAbsoluteUrl } from '@brandlab-digital/site-core/metadata'\nimport type { BrandlabRouteCatalog, BrandlabRouteKind } from '@brandlab-digital/site-core/routes'\nimport type { BrandlabSite } from '@brandlab-digital/site-core/site'\nimport {\n BRANDLAB_SITEMAP_FAMILIES,\n buildSitemapEntries,\n createSitemapChunks,\n getSitemapXmlHeaders,\n serializeSitemap,\n serializeSitemapIndex,\n type SitemapChunk,\n type SitemapEntry,\n} from '@brandlab-digital/site-core/sitemaps'\n\nexport function sitemapXmlResponse(entries: readonly SitemapEntry[]): Response {\n return new Response(serializeSitemap(entries), {\n headers: getSitemapXmlHeaders(),\n })\n}\n\nexport function sitemapIndexXmlResponse(\n paths: readonly string[],\n options: { absoluteUrl: (path: string) => string },\n): Response {\n return new Response(serializeSitemapIndex(paths, options), {\n headers: getSitemapXmlHeaders(),\n })\n}\n\nexport type BrandlabSitemapMode = 'single' | 'split'\n\nexport interface BrandlabSitemapRouteOptions {\n /** `split` only when brandlab.config declares `sitemap.mode: 'split'`. */\n mode: BrandlabSitemapMode\n /** Split mode: URLs per child sitemap (default 1000). */\n maxUrlsPerSitemap?: number\n}\n\nexport interface BrandlabSitemapChunkRouteContext {\n params: Promise<{ chunk: string }>\n}\n\nexport interface BrandlabSitemapRoute {\n readonly mode: BrandlabSitemapMode\n /** `src/app/sitemap.xml/route.ts`: the URL set, or the sitemap index in split mode. */\n GET(): Response\n /** Child sitemap paths listed by the index; empty in single mode. */\n chunkPaths(): string[]\n /** `src/app/sitemaps/[chunk]/route.ts` (split mode, with `dynamicParams = false`). */\n chunk: {\n generateStaticParams(): { chunk: string }[]\n GET(request: Request, context: BrandlabSitemapChunkRouteContext): Promise<Response>\n }\n}\n\nconst SPLIT_SITEMAP_FAMILIES: readonly { name: string; kinds: readonly BrandlabRouteKind[] }[] = BRANDLAB_SITEMAP_FAMILIES\nconst SPLIT_SITEMAP_PREFIX = '/sitemaps'\nconst DEFAULT_SPLIT_MAX_URLS = 1_000\n\nfunction splitSitemapChunks(\n routes: BrandlabRouteCatalog,\n siteUrl: string,\n maxUrlsPerSitemap: number,\n): SitemapChunk[] {\n const familyKinds = new Set(SPLIT_SITEMAP_FAMILIES.flatMap((family) => family.kinds))\n const families = [\n { name: 'core', routes: routes.filter((route) => !familyKinds.has(route.kind)) },\n ...SPLIT_SITEMAP_FAMILIES.map((family) => ({\n name: family.name,\n routes: routes.filter((route) => family.kinds.includes(route.kind)),\n })),\n ]\n return families.flatMap((family) =>\n createSitemapChunks(buildSitemapEntries(family.routes, { siteUrl }), {\n pathPrefix: `${SPLIT_SITEMAP_PREFIX}/${family.name}`,\n maxUrlsPerSitemap,\n }),\n )\n}\n\n/**\n * The route-catalogue sitemap. `lastmod` comes only from each route's\n * `lastSignificantChange`, never from build time. Entries are resolved once,\n * so an invalid catalogue fails the build.\n */\nexport function createBrandlabSitemapRoute(\n routes: BrandlabRouteCatalog,\n site: BrandlabSite,\n options: BrandlabSitemapRouteOptions = { mode: 'single' },\n): BrandlabSitemapRoute {\n if (options.mode !== 'single' && options.mode !== 'split') {\n throw new Error('Sitemap mode must be \"single\" or \"split\".')\n }\n if (options.mode === 'single' && options.maxUrlsPerSitemap !== undefined) {\n throw new Error('maxUrlsPerSitemap applies only to split sitemaps.')\n }\n const entries = buildSitemapEntries(routes, { siteUrl: site.canonicalUrl })\n const chunks = options.mode === 'split'\n ? splitSitemapChunks(routes, site.canonicalUrl, options.maxUrlsPerSitemap ?? DEFAULT_SPLIT_MAX_URLS)\n : []\n const chunkByFileName = new Map(\n chunks.map((chunk) => [chunk.path.slice(SPLIT_SITEMAP_PREFIX.length + 1), chunk]),\n )\n const absoluteUrl = (path: string) => getAbsoluteUrl(site.canonicalUrl, path)\n\n return Object.freeze({\n mode: options.mode,\n GET() {\n return options.mode === 'split'\n ? sitemapIndexXmlResponse(chunks.map((chunk) => chunk.path), { absoluteUrl })\n : sitemapXmlResponse(entries)\n },\n chunkPaths() {\n return chunks.map((chunk) => chunk.path)\n },\n chunk: Object.freeze({\n generateStaticParams() {\n return [...chunkByFileName.keys()].map((chunk) => ({ chunk }))\n },\n async GET(_request: Request, context: BrandlabSitemapChunkRouteContext) {\n const found = chunkByFileName.get((await context.params).chunk)\n return found\n ? sitemapXmlResponse(found.entries)\n : new Response('Not found', { status: 404 })\n },\n }),\n })\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandlab-digital/site-next",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@brandlab-digital/site-core": "3.
|
|
27
|
+
"@brandlab-digital/site-core": "3.5.0",
|
|
28
28
|
"browser-image-compression": "2.0.2",
|
|
29
29
|
"pdf-lib": "1.17.1",
|
|
30
30
|
"server-only": "0.0.1",
|
|
@@ -139,6 +139,11 @@
|
|
|
139
139
|
"types": "./dist/google-reviews-ui.d.ts",
|
|
140
140
|
"import": "./dist/google-reviews-ui.js"
|
|
141
141
|
},
|
|
142
|
+
"./google-business-reviews/server": {
|
|
143
|
+
"types": "./dist/google-business-reviews-server.d.ts",
|
|
144
|
+
"browser": null,
|
|
145
|
+
"import": "./dist/google-business-reviews-server.js"
|
|
146
|
+
},
|
|
142
147
|
"./image-runtime": {
|
|
143
148
|
"types": "./dist/image-runtime.d.ts",
|
|
144
149
|
"import": "./dist/image-runtime.js",
|