@suiteop/storefront 0.1.0 → 0.2.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +38 -2
  2. package/README.md +74 -0
  3. package/dist/astro-content-model.cjs +17 -0
  4. package/dist/astro-content-model.d.cts +4 -0
  5. package/dist/astro-content-model.d.ts +4 -0
  6. package/dist/astro-content-model.js +10 -0
  7. package/dist/astro.cjs +3 -1
  8. package/dist/astro.d.cts +38 -17
  9. package/dist/astro.d.ts +38 -17
  10. package/dist/astro.js +2 -2
  11. package/dist/{availability-CYvqeJ6X.d.cts → availability-CnzmxuV3.d.cts} +2 -2
  12. package/dist/{availability-CYvqeJ6X.d.ts → availability-CnzmxuV3.d.ts} +2 -2
  13. package/dist/checkout.cjs +3 -3
  14. package/dist/checkout.js +2 -2
  15. package/dist/chunk-2U6L33AT.cjs +19 -0
  16. package/dist/chunk-4GZBXKBU.cjs +18 -0
  17. package/dist/chunk-GJSCEBSU.js +5 -0
  18. package/dist/chunk-HLGSWF4I.js +9 -0
  19. package/dist/{chunk-ZYDFRRFU.js → chunk-HMLBMNGA.js} +1 -1
  20. package/dist/chunk-KKJCPKPY.js +8 -0
  21. package/dist/chunk-PYVUDQSQ.js +3 -0
  22. package/dist/chunk-USOISC2R.cjs +17 -0
  23. package/dist/{chunk-TBADVD2J.cjs → chunk-VV2UNVQ2.cjs} +2 -2
  24. package/dist/chunk-ZHFZ6II2.cjs +16 -0
  25. package/dist/chunk-ZI2PG7LS.js +6 -0
  26. package/dist/chunk-ZLAOY6IF.cjs +9 -0
  27. package/dist/{errors-DPtiZNoK.d.ts → client-DER7y1WE.d.cts} +111 -151
  28. package/dist/{errors-B3sinzJq.d.cts → client-goQnVoLP.d.ts} +111 -151
  29. package/dist/components.cjs +1 -1
  30. package/dist/components.d.cts +1 -1
  31. package/dist/components.d.ts +1 -1
  32. package/dist/components.js +1 -1
  33. package/dist/config.cjs +6 -2
  34. package/dist/config.d.cts +33 -1
  35. package/dist/config.d.ts +33 -1
  36. package/dist/config.js +3 -2
  37. package/dist/content.cjs +37 -0
  38. package/dist/content.d.cts +254 -0
  39. package/dist/content.d.ts +254 -0
  40. package/dist/content.js +4 -0
  41. package/dist/errors-B2eCo5Cz.d.cts +151 -0
  42. package/dist/errors-D-dULpp1.d.ts +151 -0
  43. package/dist/index.cjs +21 -20
  44. package/dist/index.d.cts +11 -3
  45. package/dist/index.d.ts +11 -3
  46. package/dist/index.js +4 -3
  47. package/dist/react.cjs +6 -5
  48. package/dist/react.d.cts +3 -2
  49. package/dist/react.d.ts +3 -2
  50. package/dist/react.js +5 -4
  51. package/package.json +22 -6
  52. package/dist/chunk-AI6STRZP.cjs +0 -18
  53. package/dist/chunk-AIQUVZEI.cjs +0 -8
  54. package/dist/chunk-MJA3R7NP.js +0 -8
  55. package/dist/chunk-NNZZWGM2.js +0 -6
  56. package/dist/chunk-SK5TD73J.js +0 -3
  57. package/dist/chunk-T2GQ3DOZ.cjs +0 -17
package/CHANGELOG.md CHANGED
@@ -2,7 +2,35 @@
2
2
 
3
3
  Hand-written per release. Tag `storefront-sdk@<version>` on `main` publishes it.
4
4
 
5
- ## 0.1.0 — unreleased
5
+ ## 0.2.0
6
+
7
+ - `@suiteop/storefront/content`: `defineContent` and `field.*` declare the site's content model
8
+ (collections such as a blog, page sections, custom fields on buildings and areas) in its own
9
+ code. `createContentReader(client, definition)` reads the values typed from that same object —
10
+ `collection(key).list()` / `.get(slugOrId)`, `section(key).get()`, `fieldsOf(key, record)` —
11
+ with no code generation; a stored value that no longer fits its field reads as `null` (`[]` for
12
+ a list). `toContentModel` / `parseContentModel` give the model as the API stores it.
13
+ - The client reads content: `contentModel.get()`, `collections.list(key, { limit, offset })`,
14
+ `collections.get(key, slugOrId)` (null when nothing published answers), `sections.get(key)`.
15
+ - `suiteop({ content: './src/suiteop.content.ts' })` emits the model at
16
+ `/_suiteop/content-model.json`; the SuiteOp deploy registers it, so the dashboard shows a form
17
+ per type. `suiteop({ routes: { '/blog/[slug]': 'collection:blog' } })` puts a collection's
18
+ pages in the sitemap; `/area/[…]` routes are inferred as the site's areas (`locations`).
19
+ - Sites without a content model build and deploy exactly as on 0.1.
20
+ - `defineSuiteOpConfig` accepts 14 more theme fonts (free Google fonts on Fontsource): Fraunces,
21
+ DM Sans, Cormorant Garamond, EB Garamond, Lora, Crimson Pro, Source Serif 4, IBM Plex Serif,
22
+ Manrope, Plus Jakarta Sans, Space Grotesk, IBM Plex Sans, Libre Franklin, Josefin Sans. Every
23
+ earlier font id is unchanged.
24
+ - `@suiteop/storefront/config` exports `STOREFRONT_FONT_FAMILIES`, each theme font's Fontsource
25
+ family name. The starter names its fonts from it, so a font added in a later release builds on
26
+ sites forked before it.
27
+
28
+ ## 0.1.1
29
+
30
+ - The published `package.json` no longer carries the repository's own build scripts. No API
31
+ change.
32
+
33
+ ## 0.1.0
6
34
 
7
35
  - `createStorefrontClient`: site, listings, buildings, locations, amenities, reviews,
8
36
  availability, calendar, quotes, checkout sessions (create / get / confirm), bookings, contact.
@@ -15,7 +43,9 @@ Hand-written per release. Tag `storefront-sdk@<version>` on `main` publishes it.
15
43
  `<suiteop-checkout>` element, loaded from the storefront host with Subresource Integrity.
16
44
  - `getAttribution()`: the page's first-party attribution (`so_attr` first/last touch, GA and Meta
17
45
  browser ids, consent); `checkoutSessions.create` attaches it unless the caller passes its own
18
- `attribution` (or `null`).
46
+ `attribution` (or `null`). On a site running an A/B split between two deployments it also carries
47
+ the visitor's variant (`experiment: { id, variant }`, from the edge's `__so_exp` cookie, under
48
+ analytics consent).
19
49
  - `@suiteop/storefront/config`: `defineSuiteOpConfig` validates a site's `suiteop.config.ts`
20
50
  (theme, navigation, languages, SEO defaults).
21
51
  - `@suiteop/storefront/astro`: the `suiteop()` Astro integration — writes the site manifest to
@@ -24,4 +54,10 @@ Hand-written per release. Tag `storefront-sdk@<version>` on `main` publishes it.
24
54
  - `createAnalytics` / `analyticsFor` and `useTrack()` in `/react`: batched page events (with the
25
55
  publishable key) to the storefront edge's `/_suiteop/events`; `SuiteOpProvider` sends `page_view`.
26
56
  The earlier single-event `track(name, params)` is gone: the edge accepts only the batch.
57
+ - A checkout session carries `extras` (the upsell lines the API priced and the total held), and
58
+ `checkoutSessions.create` accepts `extras` (custom-field values, upsell ids and quantities).
59
+ `<suiteop-checkout>` collects and sends them itself. New error code `extras_invalid`.
60
+ `bookings.get` answers `totalMinor` as what was paid (the stay plus its extras) and lists the
61
+ booked `lines`. `suiteop-checkout:ready` on the payment step carries `totalMinor` and
62
+ `currency` of the session being paid for.
27
63
  - Speaks Storefront API version `2026-09-27`.
package/README.md CHANGED
@@ -58,11 +58,85 @@ charged, so a retry replays the dead session and your UI should show the failure
58
58
  `checkoutSessions.forget(quoteRef, ratePlanId)` releases the key explicitly — never call it after
59
59
  a `failed` result, or a retry can take a second payment. A failed create carries the key in `error.details.idempotencyKey`.
60
60
 
61
+ A site can add custom fields and upsells to its checkout. `<suiteop-checkout>` shows them and
62
+ sends the guest's choices itself, so a host page creates the session exactly as above. A session
63
+ carries `extras`: the upsell lines the API priced and `heldTotalMinor`, the total the hold is
64
+ for. Show that total, never one computed on the page. With extras the guest pays for a session
65
+ the element created, so a page that prints a total of its own takes it from the element's
66
+ `suiteop-checkout:ready` event (`detail.totalMinor`, `detail.currency`, present on the payment
67
+ step). A booking's `totalMinor` is what was paid, and `lines` lists the extras inside it.
68
+
61
69
  Requires Node 20+ outside browsers (global `fetch` and `crypto`).
62
70
 
63
71
  Money is an integer `…Minor` field (the amount × 100) with a lower-case ISO 4217 currency;
64
72
  upper-case it yourself for display.
65
73
 
74
+ ## Content
75
+
76
+ A site declares the content its property manager edits in the SuiteOp dashboard — collections
77
+ (a blog, press mentions), page sections (a home-page banner) and extra fields on buildings or
78
+ areas — in its own code, and reads the values per request:
79
+
80
+ ```ts
81
+ // src/suiteop.content.ts
82
+ import { defineContent, field } from '@suiteop/storefront/content'
83
+
84
+ export default defineContent({
85
+ blog: {
86
+ kind: 'collection',
87
+ label: 'Blog',
88
+ titleField: 'title',
89
+ routable: true, // each post gets a slug
90
+ drafts: true, // posts are published explicitly
91
+ order: 'newest',
92
+ fields: {
93
+ title: field.text({ label: 'Title', required: true, maxLength: 150 }),
94
+ cover: field.image({ label: 'Cover photo' }),
95
+ body: field.richText({ label: 'Article', required: true }),
96
+ },
97
+ },
98
+ home_hero: {
99
+ kind: 'section',
100
+ label: 'Home — top banner',
101
+ fields: {
102
+ title: field.text({ label: 'Headline' }),
103
+ photos: field.gallery({ label: 'Photos' }),
104
+ },
105
+ },
106
+ area_details: {
107
+ kind: 'fields',
108
+ attachesTo: 'area',
109
+ label: 'Area details',
110
+ fields: { best_for: field.text({ label: 'Best for' }) },
111
+ },
112
+ })
113
+ ```
114
+
115
+ ```ts
116
+ import { createContentReader } from '@suiteop/storefront/content'
117
+ import definition from './suiteop.content'
118
+
119
+ const content = createContentReader(storefront, definition)
120
+ const { items } = await content.collection('blog').list({ limit: 10 }) // newest first
121
+ const post = await content.collection('blog').get(slug) // null: draft, scheduled or unknown
122
+ const hero = await content.section('home_hero').get()
123
+ const [area] = await storefront.locations.list()
124
+ const bestFor = content.fieldsOf('area_details', area).best_for // string | null
125
+ ```
126
+
127
+ Field types: `text`, `richText` (Markdown — render it with raw HTML disabled), `image`,
128
+ `gallery`, `number`, `boolean`, `date` (`YYYY-MM-DD`), `option`, `link` (`{ url, label }`),
129
+ `reference` / `references` (`{ kind, id, slug }`, `to: 'listing' | 'building' | 'area' |
130
+ 'collection:<key>'`). Every value is typed from the declaration; any single value may be `null`
131
+ (not filled in yet, or no longer fitting its field after a code change) and lists may be empty.
132
+ Text is in the client's `locale` with the site's default language as fallback.
133
+
134
+ With `suiteop({ content: './src/suiteop.content.ts' })` in `astro.config.mjs`, the build emits the
135
+ model at `/_suiteop/content-model.json` and the SuiteOp deploy registers it: new types and fields
136
+ appear in the dashboard; ones the code stops declaring are hidden, and nothing a manager typed is
137
+ ever deleted. Add `routes: { '/blog/[slug]': 'collection:blog' }` for the sitemap to list each
138
+ published post.
139
+
66
140
  ## Errors
67
141
 
68
142
  Every failure is a `StorefrontError` with `status` (0 when nothing answered), `code`,
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var chunkZHFZ6II2_cjs = require('./chunk-ZHFZ6II2.cjs');
4
+ require('./chunk-4GZBXKBU.cjs');
5
+ require('./chunk-USOISC2R.cjs');
6
+ require('./chunk-LVFRVNT3.cjs');
7
+ var definition = require('virtual:suiteop/content');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var definition__default = /*#__PURE__*/_interopDefault(definition);
12
+
13
+ var prerender=true;function GET(){return new Response(`${JSON.stringify(chunkZHFZ6II2_cjs.parseContentModel(definition__default.default))}
14
+ `,{headers:{"content-type":"application/json"}})}
15
+
16
+ exports.GET = GET;
17
+ exports.prerender = prerender;
@@ -0,0 +1,4 @@
1
+ declare const prerender = true;
2
+ declare function GET(): Response;
3
+
4
+ export { GET, prerender };
@@ -0,0 +1,4 @@
1
+ declare const prerender = true;
2
+ declare function GET(): Response;
3
+
4
+ export { GET, prerender };
@@ -0,0 +1,10 @@
1
+ import { parseContentModel } from './chunk-KKJCPKPY.js';
2
+ import './chunk-GJSCEBSU.js';
3
+ import './chunk-ZI2PG7LS.js';
4
+ import './chunk-6NFURJXI.js';
5
+ import definition from 'virtual:suiteop/content';
6
+
7
+ var prerender=true;function GET(){return new Response(`${JSON.stringify(parseContentModel(definition))}
8
+ `,{headers:{"content-type":"application/json"}})}
9
+
10
+ export { GET, prerender };
package/dist/astro.cjs CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  var zod = require('zod');
4
4
 
5
- var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"]];var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=zod.z.string().regex(/^\/(?![/\\])/);var siteContextSchema=zod.z.object({siteId:zod.z.string().min(1),orgId:zod.z.string().min(1),region:zod.z.enum(["us","eu","apac"]),environment:zod.z.enum(["live","test"]),publishableKey:zod.z.string().regex(/^pk_(live|test)_/),country:zod.z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:zod.z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:zod.z.record(zod.z.string().regex(/^x-suiteop-[a-z0-9-]+$/),zod.z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function suiteop(){let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,updateConfig})=>{if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
5
+ var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"],[/^\/area\/\[[^\]/]+\]$/,"locations"]];var DECLARABLE_SOURCE=/^(listings|buildings|locations|collection:[a-z][a-z0-9_]{0,39})$/;var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=input.declaredRoutes?.[pattern]??SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=zod.z.string().regex(/^\/(?![/\\])/);var siteContextSchema=zod.z.object({siteId:zod.z.string().min(1),orgId:zod.z.string().min(1),region:zod.z.enum(["us","eu","apac"]),environment:zod.z.enum(["live","test"]),publishableKey:zod.z.string().regex(/^pk_(live|test)_/),country:zod.z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:zod.z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:zod.z.record(zod.z.string().regex(/^x-suiteop-[a-z0-9-]+$/),zod.z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";var CONTENT_MODEL_PATH="_suiteop/content-model.json";var CONTENT_MODULE_ID="virtual:suiteop/content";var RESOLVED_CONTENT_MODULE_ID=`\0${CONTENT_MODULE_ID}`;function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function contentModulePlugin(file){return {name:"@suiteop/storefront:content",resolveId:id=>id===CONTENT_MODULE_ID?RESOLVED_CONTENT_MODULE_ID:void 0,load:id=>id===RESOLVED_CONTENT_MODULE_ID?`export { default } from ${JSON.stringify(file)}`:void 0}}function suiteop(options={}){for(const[pattern,source]of Object.entries(options.routes??{})){if(!DECLARABLE_SOURCE.test(source)){throw new Error(`suiteop(): route ${pattern} has source "${source}"; use listings, buildings, locations or collection:<key>.`)}}let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,config,updateConfig,injectRoute})=>{if(options.content){const file=decodeURIComponent(new URL(options.content,config.root).pathname);updateConfig({vite:{plugins:[contentModulePlugin(file)]}});injectRoute({pattern:`/${CONTENT_MODEL_PATH}`,entrypoint:"@suiteop/storefront/astro/content-model",prerender:true});}if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,declaredRoutes:options.routes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
6
6
  `);logger.info(`suiteop manifest: ${manifest.pages.length} pages, ${manifest.dynamicRoutes.length} dynamic routes`);}}}}async function builtHtmlPaths(dir){const{readdir}=await import('node:fs/promises');const{fileURLToPath}=await import('node:url');const entries=await readdir(fileURLToPath(dir),{recursive:true,withFileTypes:true});const paths=[];for(const entry of entries){if(!entry.isFile()||!entry.name.endsWith(".html"))continue;const rel=`${entry.parentPath}/${entry.name}`.slice(fileURLToPath(dir).length).replace(/\\/g,"/").replace(/^\/+/,"");if(/^(_astro|_suiteop)\//.test(rel))continue;paths.push(`/${rel.replace(/(^|\/)index\.html$/,"$1").replace(/\.html$/,"")}`);}return paths}async function readBuiltHtml(dir,pathname,format){const{readFile}=await import('node:fs/promises');const base=pathname.replace(/^\/+/,"").replace(/\/+$/,"");const candidates=base===""?["index.html"]:format==="file"?[`${base}.html`,`${base}/index.html`]:[`${base}/index.html`,`${base}.html`];for(const candidate of candidates){try{return await readFile(new URL(candidate,dir),"utf8")}catch(err){if(err.code!=="ENOENT")throw err}}return null}
7
7
 
8
+ exports.CONTENT_MODEL_PATH = CONTENT_MODEL_PATH;
8
9
  exports.SITE_MANIFEST_PATH = SITE_MANIFEST_PATH;
9
10
  exports.buildSiteManifest = buildSiteManifest;
11
+ exports.contentModulePlugin = contentModulePlugin;
10
12
  exports.devProxy = devProxy;
11
13
  exports.getSiteContext = getSiteContext;
12
14
  exports.suiteop = suiteop;
package/dist/astro.d.cts CHANGED
@@ -1,17 +1,4 @@
1
- interface SiteContext {
2
- siteId: string;
3
- orgId: string;
4
- region: 'us' | 'eu' | 'apac';
5
- environment: 'live' | 'test';
6
- publishableKey: string;
7
- country: string | null;
8
- apiBasePath: string;
9
- apiBaseUrl: string | null;
10
- apiHeaders: Record<string, string>;
11
- }
12
- declare function getSiteContext(locals: object): SiteContext | null;
13
-
14
- type SiteManifestSource = 'listings' | 'buildings';
1
+ type SiteManifestSource = 'listings' | 'buildings' | 'locations' | `collection:${string}`;
15
2
  interface SiteManifestPage {
16
3
  path: string;
17
4
  locale: string;
@@ -30,6 +17,7 @@ interface SiteManifest {
30
17
  interface BuildSiteManifestInput {
31
18
  pages: string[];
32
19
  dynamicRoutes: string[];
20
+ declaredRoutes?: Record<string, SiteManifestSource>;
33
21
  readHtml(pathname: string): Promise<string | null>;
34
22
  now: Date;
35
23
  config: {
@@ -39,7 +27,21 @@ interface BuildSiteManifestInput {
39
27
  }
40
28
  declare function buildSiteManifest(input: BuildSiteManifestInput): Promise<SiteManifest>;
41
29
 
30
+ interface SiteContext {
31
+ siteId: string;
32
+ orgId: string;
33
+ region: 'us' | 'eu' | 'apac';
34
+ environment: 'live' | 'test';
35
+ publishableKey: string;
36
+ country: string | null;
37
+ apiBasePath: string;
38
+ apiBaseUrl: string | null;
39
+ apiHeaders: Record<string, string>;
40
+ }
41
+ declare function getSiteContext(locals: object): SiteContext | null;
42
+
42
43
  declare const SITE_MANIFEST_PATH = "_suiteop/manifest.json";
44
+ declare const CONTENT_MODEL_PATH = "_suiteop/content-model.json";
43
45
  interface ProxyTarget {
44
46
  target: string;
45
47
  changeOrigin: boolean;
@@ -49,16 +51,30 @@ interface ProxyTarget {
49
51
  interface I18nLocale {
50
52
  path: string;
51
53
  }
54
+ interface ContentModulePlugin {
55
+ name: string;
56
+ resolveId(id: string): string | undefined;
57
+ load(id: string): string | undefined;
58
+ }
52
59
  interface ConfigSetupOptions {
53
60
  command: 'dev' | 'build' | 'preview' | 'sync';
61
+ config: {
62
+ root: URL;
63
+ };
54
64
  updateConfig: (config: {
55
65
  vite: {
56
- server: {
66
+ server?: {
57
67
  proxy: Record<string, ProxyTarget>;
58
68
  strictPort: boolean;
59
69
  };
70
+ plugins?: ContentModulePlugin[];
60
71
  };
61
72
  }) => unknown;
73
+ injectRoute: (route: {
74
+ pattern: string;
75
+ entrypoint: string;
76
+ prerender?: boolean;
77
+ }) => void;
62
78
  }
63
79
  interface ConfigDoneOptions {
64
80
  config: {
@@ -99,6 +115,11 @@ interface SuiteOpIntegration {
99
115
  };
100
116
  }
101
117
  declare function devProxy(env: Record<string, string | undefined>): Record<string, ProxyTarget>;
102
- declare function suiteop(): SuiteOpIntegration;
118
+ interface SuiteOpIntegrationOptions {
119
+ content?: string;
120
+ routes?: Record<string, SiteManifestSource>;
121
+ }
122
+ declare function contentModulePlugin(file: string): ContentModulePlugin;
123
+ declare function suiteop(options?: SuiteOpIntegrationOptions): SuiteOpIntegration;
103
124
 
104
- export { type BuildSiteManifestInput, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, buildSiteManifest, devProxy, getSiteContext, suiteop };
125
+ export { type BuildSiteManifestInput, CONTENT_MODEL_PATH, type ContentModulePlugin, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, type SuiteOpIntegrationOptions, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
package/dist/astro.d.ts CHANGED
@@ -1,17 +1,4 @@
1
- interface SiteContext {
2
- siteId: string;
3
- orgId: string;
4
- region: 'us' | 'eu' | 'apac';
5
- environment: 'live' | 'test';
6
- publishableKey: string;
7
- country: string | null;
8
- apiBasePath: string;
9
- apiBaseUrl: string | null;
10
- apiHeaders: Record<string, string>;
11
- }
12
- declare function getSiteContext(locals: object): SiteContext | null;
13
-
14
- type SiteManifestSource = 'listings' | 'buildings';
1
+ type SiteManifestSource = 'listings' | 'buildings' | 'locations' | `collection:${string}`;
15
2
  interface SiteManifestPage {
16
3
  path: string;
17
4
  locale: string;
@@ -30,6 +17,7 @@ interface SiteManifest {
30
17
  interface BuildSiteManifestInput {
31
18
  pages: string[];
32
19
  dynamicRoutes: string[];
20
+ declaredRoutes?: Record<string, SiteManifestSource>;
33
21
  readHtml(pathname: string): Promise<string | null>;
34
22
  now: Date;
35
23
  config: {
@@ -39,7 +27,21 @@ interface BuildSiteManifestInput {
39
27
  }
40
28
  declare function buildSiteManifest(input: BuildSiteManifestInput): Promise<SiteManifest>;
41
29
 
30
+ interface SiteContext {
31
+ siteId: string;
32
+ orgId: string;
33
+ region: 'us' | 'eu' | 'apac';
34
+ environment: 'live' | 'test';
35
+ publishableKey: string;
36
+ country: string | null;
37
+ apiBasePath: string;
38
+ apiBaseUrl: string | null;
39
+ apiHeaders: Record<string, string>;
40
+ }
41
+ declare function getSiteContext(locals: object): SiteContext | null;
42
+
42
43
  declare const SITE_MANIFEST_PATH = "_suiteop/manifest.json";
44
+ declare const CONTENT_MODEL_PATH = "_suiteop/content-model.json";
43
45
  interface ProxyTarget {
44
46
  target: string;
45
47
  changeOrigin: boolean;
@@ -49,16 +51,30 @@ interface ProxyTarget {
49
51
  interface I18nLocale {
50
52
  path: string;
51
53
  }
54
+ interface ContentModulePlugin {
55
+ name: string;
56
+ resolveId(id: string): string | undefined;
57
+ load(id: string): string | undefined;
58
+ }
52
59
  interface ConfigSetupOptions {
53
60
  command: 'dev' | 'build' | 'preview' | 'sync';
61
+ config: {
62
+ root: URL;
63
+ };
54
64
  updateConfig: (config: {
55
65
  vite: {
56
- server: {
66
+ server?: {
57
67
  proxy: Record<string, ProxyTarget>;
58
68
  strictPort: boolean;
59
69
  };
70
+ plugins?: ContentModulePlugin[];
60
71
  };
61
72
  }) => unknown;
73
+ injectRoute: (route: {
74
+ pattern: string;
75
+ entrypoint: string;
76
+ prerender?: boolean;
77
+ }) => void;
62
78
  }
63
79
  interface ConfigDoneOptions {
64
80
  config: {
@@ -99,6 +115,11 @@ interface SuiteOpIntegration {
99
115
  };
100
116
  }
101
117
  declare function devProxy(env: Record<string, string | undefined>): Record<string, ProxyTarget>;
102
- declare function suiteop(): SuiteOpIntegration;
118
+ interface SuiteOpIntegrationOptions {
119
+ content?: string;
120
+ routes?: Record<string, SiteManifestSource>;
121
+ }
122
+ declare function contentModulePlugin(file: string): ContentModulePlugin;
123
+ declare function suiteop(options?: SuiteOpIntegrationOptions): SuiteOpIntegration;
103
124
 
104
- export { type BuildSiteManifestInput, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, buildSiteManifest, devProxy, getSiteContext, suiteop };
125
+ export { type BuildSiteManifestInput, CONTENT_MODEL_PATH, type ContentModulePlugin, SITE_MANIFEST_PATH, type SiteContext, type SiteManifest, type SiteManifestPage, type SiteManifestSource, type SuiteOpIntegration, type SuiteOpIntegrationOptions, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
package/dist/astro.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"]];var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=z.string().regex(/^\/(?![/\\])/);var siteContextSchema=z.object({siteId:z.string().min(1),orgId:z.string().min(1),region:z.enum(["us","eu","apac"]),environment:z.enum(["live","test"]),publishableKey:z.string().regex(/^pk_(live|test)_/),country:z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:z.record(z.string().regex(/^x-suiteop-[a-z0-9-]+$/),z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function suiteop(){let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,updateConfig})=>{if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
3
+ var SOURCES=[[/^\/listing\/\[[^\]/]+\]$/,"listings"],[/^\/building\/\[[^\]/]+\]$/,"buildings"],[/^\/area\/\[[^\]/]+\]$/,"locations"]];var DECLARABLE_SOURCE=/^(listings|buildings|locations|collection:[a-z][a-z0-9_]{0,39})$/;var ERROR_PAGES=new Set(["404","500"]);function normalizePath(pathname){const withSlash=pathname.startsWith("/")?pathname:`/${pathname}`;const trimmed=withSlash.replace(/\/+$/,"").replace(/\/index(\.html)?$/,"");return trimmed===""?"/":trimmed}var ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"};function decodeEntities(text){return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi,(whole,ref)=>{if(ref[0]==="#"){const code=ref[1]==="x"||ref[1]==="X"?parseInt(ref.slice(2),16):Number(ref.slice(1));return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):whole}return ENTITIES[ref.toLowerCase()]??whole})}function metaContent(html,name){for(const[tag]of html.matchAll(/<meta\b[^>]*>/gi)){const attr=key=>new RegExp(`\\b${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`,"i").exec(tag);const tagName=attr("name");if((tagName?.[1]??tagName?.[2])?.toLowerCase()!==name)continue;const content=attr("content");return decodeEntities(content?.[1]??content?.[2]??"")}return null}function localeOf(path,config){const first=path.split("/")[1]??"";return first!==config.defaultLocale&&config.locales.includes(first)?first:config.defaultLocale}function isErrorPage(path,locale,config){const rest=locale===config.defaultLocale?path:path.slice(locale.length+1);return ERROR_PAGES.has(rest.replace(/^\//,""))}async function buildSiteManifest(input){const now=input.now.toISOString();const pages=[];const seen=new Set;for(const pathname of input.pages){const path=normalizePath(pathname);if(seen.has(path))continue;seen.add(path);const locale=localeOf(path,input.config);if(isErrorPage(path,locale,input.config))continue;const html=await input.readHtml(pathname);if(!html)continue;if(/\bnoindex\b/i.test(metaContent(html,"robots")??""))continue;const title=/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1];const lastmodRaw=metaContent(html,"suiteop:lastmod");const lastmodMs=lastmodRaw?Date.parse(lastmodRaw):Number.NaN;pages.push({path,locale,title:title?decodeEntities(title.trim()):"",lastmod:Number.isNaN(lastmodMs)?now:new Date(lastmodMs).toISOString()});}pages.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);const dynamicRoutes=[];for(const pattern of input.dynamicRoutes){const source=input.declaredRoutes?.[pattern]??SOURCES.find(([re])=>re.test(pattern))?.[1];if(source&&!dynamicRoutes.some(r=>r.pattern===pattern)){dynamicRoutes.push({pattern,source});}}return {version:1,generatedAt:now,pages,dynamicRoutes}}var sameOriginPath=z.string().regex(/^\/(?![/\\])/);var siteContextSchema=z.object({siteId:z.string().min(1),orgId:z.string().min(1),region:z.enum(["us","eu","apac"]),environment:z.enum(["live","test"]),publishableKey:z.string().regex(/^pk_(live|test)_/),country:z.string().nullable().optional(),apiBasePath:sameOriginPath,apiBaseUrl:z.url({protocol:/^https$/}).nullable().optional(),apiHeaders:z.record(z.string().regex(/^x-suiteop-[a-z0-9-]+$/),z.string()).optional()});function getSiteContext(locals){const props=locals.cfContext?.props;if(typeof props!=="object"||props===null)return null;const parsed=siteContextSchema.safeParse(props);if(!parsed.success)return null;const p=parsed.data;return {...p,country:p.country??null,apiBaseUrl:p.apiBaseUrl??null,apiHeaders:p.apiHeaders??{}}}var SITE_MANIFEST_PATH="_suiteop/manifest.json";var CONTENT_MODEL_PATH="_suiteop/content-model.json";var CONTENT_MODULE_ID="virtual:suiteop/content";var RESOLVED_CONTENT_MODULE_ID=`\0${CONTENT_MODULE_ID}`;function devProxy(env){const apiOrigin=env["SUITEOP_API_ORIGIN"];const edgeOrigin=env["SUITEOP_EDGE_ORIGIN"];const proxy={};if(apiOrigin){proxy["/api/storefront/v1"]={target:apiOrigin,changeOrigin:true,...edgeOrigin?{headers:{origin:edgeOrigin}}:{}};}if(edgeOrigin){proxy["^/checkout/(manifest\\.json|v\\d+(\\.[0-9a-f]+)?\\.js)$"]={target:edgeOrigin,changeOrigin:true};}return proxy}function contentModulePlugin(file){return {name:"@suiteop/storefront:content",resolveId:id=>id===CONTENT_MODULE_ID?RESOLVED_CONTENT_MODULE_ID:void 0,load:id=>id===RESOLVED_CONTENT_MODULE_ID?`export { default } from ${JSON.stringify(file)}`:void 0}}function suiteop(options={}){for(const[pattern,source]of Object.entries(options.routes??{})){if(!DECLARABLE_SOURCE.test(source)){throw new Error(`suiteop(): route ${pattern} has source "${source}"; use listings, buildings, locations or collection:<key>.`)}}let i18n={locales:[],defaultLocale:"en"};let format="directory";let onDemandRoutes=[];return {name:"@suiteop/storefront",hooks:{"astro:config:setup":({command,config,updateConfig,injectRoute})=>{if(options.content){const file=decodeURIComponent(new URL(options.content,config.root).pathname);updateConfig({vite:{plugins:[contentModulePlugin(file)]}});injectRoute({pattern:`/${CONTENT_MODEL_PATH}`,entrypoint:"@suiteop/storefront/astro/content-model",prerender:true});}if(command==="dev"&&process.env["SUITEOP_DEV_PROXY"]==="1"){updateConfig({vite:{server:{proxy:devProxy(process.env),strictPort:true}}});}},"astro:config:done":({config})=>{format=config.build.format;if(config.i18n){i18n={locales:config.i18n.locales.map(l=>typeof l==="string"?l:l.path),defaultLocale:config.i18n.defaultLocale};}},"astro:routes:resolved":({routes})=>{onDemandRoutes=routes.filter(r=>r.type==="page"&&!r.isPrerendered&&r.origin==="project").map(r=>r.pattern);},"astro:build:done":async({dir,pages,logger})=>{const{mkdir,writeFile}=await import('node:fs/promises');const built=await builtHtmlPaths(dir);const all=new Set([...pages.map(p=>`/${p.pathname}`),...built]);const manifest=await buildSiteManifest({pages:[...all],dynamicRoutes:onDemandRoutes,declaredRoutes:options.routes,readHtml:pathname=>readBuiltHtml(dir,pathname,format),now:new Date,config:i18n});const target=new URL(SITE_MANIFEST_PATH,dir);await mkdir(new URL(".",target),{recursive:true});await writeFile(target,`${JSON.stringify(manifest)}
4
4
  `);logger.info(`suiteop manifest: ${manifest.pages.length} pages, ${manifest.dynamicRoutes.length} dynamic routes`);}}}}async function builtHtmlPaths(dir){const{readdir}=await import('node:fs/promises');const{fileURLToPath}=await import('node:url');const entries=await readdir(fileURLToPath(dir),{recursive:true,withFileTypes:true});const paths=[];for(const entry of entries){if(!entry.isFile()||!entry.name.endsWith(".html"))continue;const rel=`${entry.parentPath}/${entry.name}`.slice(fileURLToPath(dir).length).replace(/\\/g,"/").replace(/^\/+/,"");if(/^(_astro|_suiteop)\//.test(rel))continue;paths.push(`/${rel.replace(/(^|\/)index\.html$/,"$1").replace(/\.html$/,"")}`);}return paths}async function readBuiltHtml(dir,pathname,format){const{readFile}=await import('node:fs/promises');const base=pathname.replace(/^\/+/,"").replace(/\/+$/,"");const candidates=base===""?["index.html"]:format==="file"?[`${base}.html`,`${base}/index.html`]:[`${base}/index.html`,`${base}.html`];for(const candidate of candidates){try{return await readFile(new URL(candidate,dir),"utf8")}catch(err){if(err.code!=="ENOENT")throw err}}return null}
5
5
 
6
- export { SITE_MANIFEST_PATH, buildSiteManifest, devProxy, getSiteContext, suiteop };
6
+ export { CONTENT_MODEL_PATH, SITE_MANIFEST_PATH, buildSiteManifest, contentModulePlugin, devProxy, getSiteContext, suiteop };
@@ -18,7 +18,7 @@ declare const storefrontCalendarDaySchema: z.ZodObject<{
18
18
  minNights: z.ZodNullable<z.ZodNumber>;
19
19
  closedToArrival: z.ZodOptional<z.ZodBoolean>;
20
20
  closedToDeparture: z.ZodOptional<z.ZodBoolean>;
21
- status: z.ZodType<(string & {}) | "available" | "blocked" | "booked", unknown, z.core.$ZodTypeInternals<(string & {}) | "available" | "blocked" | "booked", unknown>>;
21
+ status: z.ZodType<"blocked" | "available" | "booked" | (string & {}), unknown, z.core.$ZodTypeInternals<"blocked" | "available" | "booked" | (string & {}), unknown>>;
22
22
  }, z.core.$strip>;
23
23
  type StorefrontCalendarDay = z.infer<typeof storefrontCalendarDaySchema>;
24
24
  declare const storefrontCalendarSchema: z.ZodObject<{
@@ -31,7 +31,7 @@ declare const storefrontCalendarSchema: z.ZodObject<{
31
31
  minNights: z.ZodNullable<z.ZodNumber>;
32
32
  closedToArrival: z.ZodOptional<z.ZodBoolean>;
33
33
  closedToDeparture: z.ZodOptional<z.ZodBoolean>;
34
- status: z.ZodType<(string & {}) | "available" | "blocked" | "booked", unknown, z.core.$ZodTypeInternals<(string & {}) | "available" | "blocked" | "booked", unknown>>;
34
+ status: z.ZodType<"blocked" | "available" | "booked" | (string & {}), unknown, z.core.$ZodTypeInternals<"blocked" | "available" | "booked" | (string & {}), unknown>>;
35
35
  }, z.core.$strip>>;
36
36
  }, z.core.$strip>;
37
37
  type StorefrontCalendar = z.infer<typeof storefrontCalendarSchema>;
@@ -18,7 +18,7 @@ declare const storefrontCalendarDaySchema: z.ZodObject<{
18
18
  minNights: z.ZodNullable<z.ZodNumber>;
19
19
  closedToArrival: z.ZodOptional<z.ZodBoolean>;
20
20
  closedToDeparture: z.ZodOptional<z.ZodBoolean>;
21
- status: z.ZodType<(string & {}) | "available" | "blocked" | "booked", unknown, z.core.$ZodTypeInternals<(string & {}) | "available" | "blocked" | "booked", unknown>>;
21
+ status: z.ZodType<"blocked" | "available" | "booked" | (string & {}), unknown, z.core.$ZodTypeInternals<"blocked" | "available" | "booked" | (string & {}), unknown>>;
22
22
  }, z.core.$strip>;
23
23
  type StorefrontCalendarDay = z.infer<typeof storefrontCalendarDaySchema>;
24
24
  declare const storefrontCalendarSchema: z.ZodObject<{
@@ -31,7 +31,7 @@ declare const storefrontCalendarSchema: z.ZodObject<{
31
31
  minNights: z.ZodNullable<z.ZodNumber>;
32
32
  closedToArrival: z.ZodOptional<z.ZodBoolean>;
33
33
  closedToDeparture: z.ZodOptional<z.ZodBoolean>;
34
- status: z.ZodType<(string & {}) | "available" | "blocked" | "booked", unknown, z.core.$ZodTypeInternals<(string & {}) | "available" | "blocked" | "booked", unknown>>;
34
+ status: z.ZodType<"blocked" | "available" | "booked" | (string & {}), unknown, z.core.$ZodTypeInternals<"blocked" | "available" | "booked" | (string & {}), unknown>>;
35
35
  }, z.core.$strip>>;
36
36
  }, z.core.$strip>;
37
37
  type StorefrontCalendar = z.infer<typeof storefrontCalendarSchema>;
package/dist/checkout.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkTBADVD2J_cjs = require('./chunk-TBADVD2J.cjs');
4
- require('./chunk-T2GQ3DOZ.cjs');
3
+ var chunkVV2UNVQ2_cjs = require('./chunk-VV2UNVQ2.cjs');
4
+ require('./chunk-USOISC2R.cjs');
5
5
  require('./chunk-LVFRVNT3.cjs');
6
6
 
7
7
 
8
8
 
9
9
  Object.defineProperty(exports, "loadCheckout", {
10
10
  enumerable: true,
11
- get: function () { return chunkTBADVD2J_cjs.loadCheckout; }
11
+ get: function () { return chunkVV2UNVQ2_cjs.loadCheckout; }
12
12
  });
package/dist/checkout.js CHANGED
@@ -1,3 +1,3 @@
1
- export { loadCheckout } from './chunk-ZYDFRRFU.js';
2
- import './chunk-NNZZWGM2.js';
1
+ export { loadCheckout } from './chunk-HMLBMNGA.js';
2
+ import './chunk-ZI2PG7LS.js';
3
3
  import './chunk-6NFURJXI.js';