@v-office/website-headless 0.0.1
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/README.md +86 -0
- package/dist/enhance/index.d.mts +181 -0
- package/dist/enhance/index.mjs +533 -0
- package/dist/index.d.mts +912 -0
- package/dist/index.mjs +1423 -0
- package/dist/manifest/index.d.mts +118 -0
- package/dist/manifest/index.mjs +264 -0
- package/package.json +48 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/manifest/surfaces.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The FIVE platform UI surfaces — each a component that carries real logic —
|
|
4
|
+
* and the two deterministic signals that tell how a site consumes each one:
|
|
5
|
+
*
|
|
6
|
+
* - `component`: the named export of @v-office/website-components. A site
|
|
7
|
+
* that imports it renders the surface as IMPORTED (updates are pin bumps).
|
|
8
|
+
* - `hooks`: named exports of @v-office/website-headless that only a UI for
|
|
9
|
+
* this surface would import. A site that imports one of them WITHOUT
|
|
10
|
+
* importing the component has rendered its own UI — the surface is CUSTOM
|
|
11
|
+
* (behavior arrives by pin bump; a new capability is offered, never forced).
|
|
12
|
+
* - `bindings`: framework bindings of that behavior exported by
|
|
13
|
+
* @v-office/website-components next to the component (e.g. the React hook
|
|
14
|
+
* over the flow). Importing one without the component is CUSTOM as well.
|
|
15
|
+
*
|
|
16
|
+
* Surfaces with no hooks yet (the search trio) can only be detected as imported
|
|
17
|
+
* or absent. That is fine: a custom surface without a headless hook never
|
|
18
|
+
* receives a platform-initiated change, so the distinction has no consumer.
|
|
19
|
+
* The moment headless gains behavior for one (e.g. a search hook), its name is
|
|
20
|
+
* added here in the same release and every adopting site reports `custom`.
|
|
21
|
+
*
|
|
22
|
+
* Everything else a site renders (rental detail, FAQ, consent, brand pages) is
|
|
23
|
+
* the site's own markup and is not a platform surface. Decided 2026-09-03.
|
|
24
|
+
*/
|
|
25
|
+
declare const PLATFORM_SURFACES: {
|
|
26
|
+
/** Entry to search: dates + guests → the search page. */
|
|
27
|
+
readonly searchBar: {
|
|
28
|
+
readonly component: "SearchBar";
|
|
29
|
+
readonly hooks: readonly [];
|
|
30
|
+
readonly bindings: readonly [];
|
|
31
|
+
};
|
|
32
|
+
/** Curated rentals on brand pages, driven by the search logic. */
|
|
33
|
+
readonly showcase: {
|
|
34
|
+
readonly component: "Showcase";
|
|
35
|
+
readonly hooks: readonly [];
|
|
36
|
+
readonly bindings: readonly [];
|
|
37
|
+
};
|
|
38
|
+
/** The search page's results, including the map. */
|
|
39
|
+
readonly searchResults: {
|
|
40
|
+
readonly component: "SearchResults";
|
|
41
|
+
readonly hooks: readonly [];
|
|
42
|
+
readonly bindings: readonly [];
|
|
43
|
+
};
|
|
44
|
+
/** Calendar + price quote + cancellation-policy choice on the rental page; entry to the booking flow. */
|
|
45
|
+
readonly quote: {
|
|
46
|
+
readonly component: "Quote";
|
|
47
|
+
readonly hooks: readonly ["fetchQuote", "pricePolicyBranches", "policyFreeCancellationUntil"];
|
|
48
|
+
readonly bindings: readonly [];
|
|
49
|
+
};
|
|
50
|
+
/** The booking flow (checkout): stay → quote → selections → § 7 gate → book → pay. Its data-part vocabulary is `checkout-*`. */
|
|
51
|
+
readonly booking: {
|
|
52
|
+
readonly component: "Booking";
|
|
53
|
+
readonly hooks: readonly ["createCheckoutFlow"];
|
|
54
|
+
readonly bindings: readonly ["useCheckoutFlow"];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
type PlatformSurface = keyof typeof PLATFORM_SURFACES;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/manifest/index.d.ts
|
|
60
|
+
type ComponentState = "imported" | "custom";
|
|
61
|
+
interface SiteManifest {
|
|
62
|
+
manifestVersion: 3;
|
|
63
|
+
builtAt: string;
|
|
64
|
+
/** Installed versions of the tracked packages. */
|
|
65
|
+
packages: Record<string, string>;
|
|
66
|
+
/** Per platform surface the site renders: imported from the package, or the site's own UI over the hook. */
|
|
67
|
+
components: Record<string, ComponentState>;
|
|
68
|
+
}
|
|
69
|
+
interface ImportSets {
|
|
70
|
+
components: Set<string>;
|
|
71
|
+
headless: Set<string>;
|
|
72
|
+
}
|
|
73
|
+
/** Add every platform import found in `code` to `into`. Shared by the source scan and the bundler plugin. */
|
|
74
|
+
declare function collectImportsFromCode(code: string, into: ImportSets): void;
|
|
75
|
+
declare const emptyImports: () => ImportSets;
|
|
76
|
+
/** Signal 1: what the source under src/ contains. */
|
|
77
|
+
declare function collectImports(root: string): ImportSets;
|
|
78
|
+
declare function deriveComponentStates(imports: ImportSets): Record<string, ComponentState>;
|
|
79
|
+
/** Which marked surfaces the built output ships. */
|
|
80
|
+
declare function renderedSurfaces(distDir: string): Set<string>;
|
|
81
|
+
interface CrossCheck {
|
|
82
|
+
/** The manifest's states: derived from what the bundler reached. */
|
|
83
|
+
states: Record<string, ComponentState>;
|
|
84
|
+
warnings: string[];
|
|
85
|
+
/** Non-empty = the build must fail. */
|
|
86
|
+
errors: string[];
|
|
87
|
+
}
|
|
88
|
+
declare function crossCheck(source: ImportSets, bundled: ImportSets | null, rendered: Set<string>): CrossCheck;
|
|
89
|
+
/** Everything without the bundler signal — used by tests and by anything that runs outside a build. */
|
|
90
|
+
declare function buildSiteManifest(root: string): SiteManifest;
|
|
91
|
+
/**
|
|
92
|
+
* The Astro integration:
|
|
93
|
+
*
|
|
94
|
+
* import { siteManifest } from "@v-office/website-headless/manifest";
|
|
95
|
+
* integrations: [react(), siteManifest()]
|
|
96
|
+
*
|
|
97
|
+
* Typed structurally so this package needs no astro or vite dependency.
|
|
98
|
+
*/
|
|
99
|
+
declare function siteManifest(): {
|
|
100
|
+
name: string;
|
|
101
|
+
hooks: {
|
|
102
|
+
"astro:config:setup": (ctx: {
|
|
103
|
+
config: {
|
|
104
|
+
root: URL;
|
|
105
|
+
};
|
|
106
|
+
updateConfig: (cfg: {
|
|
107
|
+
vite: {
|
|
108
|
+
plugins: unknown[];
|
|
109
|
+
};
|
|
110
|
+
}) => void;
|
|
111
|
+
}) => void;
|
|
112
|
+
"astro:build:done": (ctx: {
|
|
113
|
+
dir: URL;
|
|
114
|
+
}) => void;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { ComponentState, CrossCheck, ImportSets, PLATFORM_SURFACES, type PlatformSurface, SiteManifest, buildSiteManifest, collectImports, collectImportsFromCode, crossCheck, deriveComponentStates, emptyImports, renderedSurfaces, siteManifest };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/manifest/surfaces.ts
|
|
5
|
+
/**
|
|
6
|
+
* The FIVE platform UI surfaces — each a component that carries real logic —
|
|
7
|
+
* and the two deterministic signals that tell how a site consumes each one:
|
|
8
|
+
*
|
|
9
|
+
* - `component`: the named export of @v-office/website-components. A site
|
|
10
|
+
* that imports it renders the surface as IMPORTED (updates are pin bumps).
|
|
11
|
+
* - `hooks`: named exports of @v-office/website-headless that only a UI for
|
|
12
|
+
* this surface would import. A site that imports one of them WITHOUT
|
|
13
|
+
* importing the component has rendered its own UI — the surface is CUSTOM
|
|
14
|
+
* (behavior arrives by pin bump; a new capability is offered, never forced).
|
|
15
|
+
* - `bindings`: framework bindings of that behavior exported by
|
|
16
|
+
* @v-office/website-components next to the component (e.g. the React hook
|
|
17
|
+
* over the flow). Importing one without the component is CUSTOM as well.
|
|
18
|
+
*
|
|
19
|
+
* Surfaces with no hooks yet (the search trio) can only be detected as imported
|
|
20
|
+
* or absent. That is fine: a custom surface without a headless hook never
|
|
21
|
+
* receives a platform-initiated change, so the distinction has no consumer.
|
|
22
|
+
* The moment headless gains behavior for one (e.g. a search hook), its name is
|
|
23
|
+
* added here in the same release and every adopting site reports `custom`.
|
|
24
|
+
*
|
|
25
|
+
* Everything else a site renders (rental detail, FAQ, consent, brand pages) is
|
|
26
|
+
* the site's own markup and is not a platform surface. Decided 2026-09-03.
|
|
27
|
+
*/
|
|
28
|
+
const PLATFORM_SURFACES = {
|
|
29
|
+
/** Entry to search: dates + guests → the search page. */
|
|
30
|
+
searchBar: {
|
|
31
|
+
component: "SearchBar",
|
|
32
|
+
hooks: [],
|
|
33
|
+
bindings: []
|
|
34
|
+
},
|
|
35
|
+
/** Curated rentals on brand pages, driven by the search logic. */
|
|
36
|
+
showcase: {
|
|
37
|
+
component: "Showcase",
|
|
38
|
+
hooks: [],
|
|
39
|
+
bindings: []
|
|
40
|
+
},
|
|
41
|
+
/** The search page's results, including the map. */
|
|
42
|
+
searchResults: {
|
|
43
|
+
component: "SearchResults",
|
|
44
|
+
hooks: [],
|
|
45
|
+
bindings: []
|
|
46
|
+
},
|
|
47
|
+
/** Calendar + price quote + cancellation-policy choice on the rental page; entry to the booking flow. */
|
|
48
|
+
quote: {
|
|
49
|
+
component: "Quote",
|
|
50
|
+
hooks: [
|
|
51
|
+
"fetchQuote",
|
|
52
|
+
"pricePolicyBranches",
|
|
53
|
+
"policyFreeCancellationUntil"
|
|
54
|
+
],
|
|
55
|
+
bindings: []
|
|
56
|
+
},
|
|
57
|
+
/** The booking flow (checkout): stay → quote → selections → § 7 gate → book → pay. Its data-part vocabulary is `checkout-*`. */
|
|
58
|
+
booking: {
|
|
59
|
+
component: "Booking",
|
|
60
|
+
hooks: ["createCheckoutFlow"],
|
|
61
|
+
bindings: ["useCheckoutFlow"]
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/manifest/index.ts
|
|
66
|
+
/**
|
|
67
|
+
* The site manifest — the machine-readable answer to "what IS this site",
|
|
68
|
+
* written by every build as `dist/site-manifest.json` and reported by the
|
|
69
|
+
* build worker to the controller with the build's finalize call (process/05).
|
|
70
|
+
*
|
|
71
|
+
* EVERYTHING IN IT IS DERIVED, nothing is declared: package versions come
|
|
72
|
+
* from the installed packages, component states from the site's imports (see
|
|
73
|
+
* ./surfaces.ts). An AI never writes the manifest, so it cannot be wrong in
|
|
74
|
+
* the ways a declaration can.
|
|
75
|
+
*
|
|
76
|
+
* THREE INDEPENDENT SIGNALS, cross-checked on every build:
|
|
77
|
+
* 1. a text scan of the imports under src/ — what the code contains
|
|
78
|
+
* 2. the imports the bundler actually resolved — what the build reaches
|
|
79
|
+
* (a Vite plugin the integration registers; dead files never enter it,
|
|
80
|
+
* re-exports are followed)
|
|
81
|
+
* 3. the `data-part` markers in the built output — what a guest gets
|
|
82
|
+
* (HTML and client chunks; the booking flow is an island)
|
|
83
|
+
* The manifest states what the build reached (2). Disagreements with (1) are
|
|
84
|
+
* printed. A booking flow rendered without the platform's booking behavior
|
|
85
|
+
* (3 without 2) FAILS the build: behavior is never forked per site.
|
|
86
|
+
*
|
|
87
|
+
* This module is an ASTRO INTEGRATION and uses node:fs — it is exported ONLY
|
|
88
|
+
* through the "./manifest" subpath so client bundles never touch it.
|
|
89
|
+
*/
|
|
90
|
+
const TRACKED_PACKAGES = [
|
|
91
|
+
"@v-office/website-sdk",
|
|
92
|
+
"@v-office/website-headless",
|
|
93
|
+
"@v-office/website-components",
|
|
94
|
+
"astro",
|
|
95
|
+
"react",
|
|
96
|
+
"tailwindcss"
|
|
97
|
+
];
|
|
98
|
+
const COMPONENTS_PACKAGE = "@v-office/website-components";
|
|
99
|
+
const HEADLESS_PACKAGE = "@v-office/website-headless";
|
|
100
|
+
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
101
|
+
".ts",
|
|
102
|
+
".tsx",
|
|
103
|
+
".js",
|
|
104
|
+
".jsx",
|
|
105
|
+
".mjs",
|
|
106
|
+
".astro"
|
|
107
|
+
]);
|
|
108
|
+
function installedVersion(root, name) {
|
|
109
|
+
const file = path.join(root, "node_modules", ...name.split("/"), "package.json");
|
|
110
|
+
if (!fs.existsSync(file)) return void 0;
|
|
111
|
+
return JSON.parse(fs.readFileSync(file, "utf8")).version;
|
|
112
|
+
}
|
|
113
|
+
function* filesUnder(dir, extensions) {
|
|
114
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
115
|
+
const full = path.join(dir, entry.name);
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
if (entry.name !== "node_modules") yield* filesUnder(full, extensions);
|
|
118
|
+
} else if (extensions.has(path.extname(entry.name))) yield full;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** `import { a, b as c } from "pkg"` and `export { a } from "pkg"` (a local barrel) →
|
|
122
|
+
* named imports per package. Type-only forms render nothing and are skipped.
|
|
123
|
+
* Namespace imports carry no names and are not detected — the cross-check reports
|
|
124
|
+
* them as a source/bundler disagreement. */
|
|
125
|
+
const IMPORT_RE = /(?:import|export)\s+(type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*["']([^"']+)["']/g;
|
|
126
|
+
const packageOf = (specifier) => specifier === COMPONENTS_PACKAGE || specifier.startsWith(`${COMPONENTS_PACKAGE}/`) ? "components" : specifier === HEADLESS_PACKAGE || specifier.startsWith(`${HEADLESS_PACKAGE}/`) ? "headless" : null;
|
|
127
|
+
/** Add every platform import found in `code` to `into`. Shared by the source scan and the bundler plugin. */
|
|
128
|
+
function collectImportsFromCode(code, into) {
|
|
129
|
+
for (const m of code.matchAll(IMPORT_RE)) {
|
|
130
|
+
const [, typeOnly, names, specifier] = m;
|
|
131
|
+
if (typeOnly) continue;
|
|
132
|
+
const target = packageOf(specifier);
|
|
133
|
+
if (!target) continue;
|
|
134
|
+
for (const part of names.split(",")) {
|
|
135
|
+
const name = part.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0].trim();
|
|
136
|
+
if (name) into[target].add(name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const emptyImports = () => ({
|
|
141
|
+
components: /* @__PURE__ */ new Set(),
|
|
142
|
+
headless: /* @__PURE__ */ new Set()
|
|
143
|
+
});
|
|
144
|
+
/** Signal 1: what the source under src/ contains. */
|
|
145
|
+
function collectImports(root) {
|
|
146
|
+
const sets = emptyImports();
|
|
147
|
+
const src = path.join(root, "src");
|
|
148
|
+
if (!fs.existsSync(src)) return sets;
|
|
149
|
+
for (const file of filesUnder(src, SOURCE_EXTENSIONS)) collectImportsFromCode(fs.readFileSync(file, "utf8"), sets);
|
|
150
|
+
return sets;
|
|
151
|
+
}
|
|
152
|
+
function deriveComponentStates(imports) {
|
|
153
|
+
const states = {};
|
|
154
|
+
for (const [surface, { component, hooks, bindings }] of Object.entries(PLATFORM_SURFACES)) if (imports.components.has(component)) states[surface] = "imported";
|
|
155
|
+
else if (hooks.some((h) => imports.headless.has(h)) || bindings.some((b) => imports.components.has(b))) states[surface] = "custom";
|
|
156
|
+
return states;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Surfaces whose markup carries a `data-part` vocabulary, and the one marker
|
|
160
|
+
* every rendering of the surface must contain. Looked for in the built output —
|
|
161
|
+
* HTML for server-rendered markup, client JS chunks for islands whose shell is
|
|
162
|
+
* a loading state (Krentz's booking flow is `client:load` and fetches the quote live).
|
|
163
|
+
*/
|
|
164
|
+
const OUTPUT_MARKERS = { booking: "checkout-submit" };
|
|
165
|
+
const OUTPUT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
166
|
+
".html",
|
|
167
|
+
".js",
|
|
168
|
+
".mjs"
|
|
169
|
+
]);
|
|
170
|
+
/** Which marked surfaces the built output ships. */
|
|
171
|
+
function renderedSurfaces(distDir) {
|
|
172
|
+
const found = /* @__PURE__ */ new Set();
|
|
173
|
+
if (!fs.existsSync(distDir)) return found;
|
|
174
|
+
for (const file of filesUnder(distDir, OUTPUT_EXTENSIONS)) {
|
|
175
|
+
const text = fs.readFileSync(file, "utf8");
|
|
176
|
+
for (const [surface, marker] of Object.entries(OUTPUT_MARKERS)) if (text.includes(marker)) found.add(surface);
|
|
177
|
+
}
|
|
178
|
+
return found;
|
|
179
|
+
}
|
|
180
|
+
function crossCheck(source, bundled, rendered) {
|
|
181
|
+
const fromSource = deriveComponentStates(source);
|
|
182
|
+
const states = bundled ? deriveComponentStates(bundled) : fromSource;
|
|
183
|
+
const warnings = [];
|
|
184
|
+
const errors = [];
|
|
185
|
+
if (bundled) for (const surface of /* @__PURE__ */ new Set([...Object.keys(fromSource), ...Object.keys(states)])) {
|
|
186
|
+
const a = fromSource[surface];
|
|
187
|
+
const b = states[surface];
|
|
188
|
+
if (a && !b) warnings.push(`${surface}: the source imports it (${a}) but no page reaches that code — dead file? The manifest reports the build, not the file.`);
|
|
189
|
+
else if (!a && b) warnings.push(`${surface}: the build resolves it (${b}) but the source scan did not see a named import — namespace import or re-export; the build wins.`);
|
|
190
|
+
else if (a && b && a !== b) warnings.push(`${surface}: source says ${a}, build says ${b}; the build wins.`);
|
|
191
|
+
}
|
|
192
|
+
for (const surface of rendered) if (!states[surface]) {
|
|
193
|
+
const { hooks, bindings, component } = PLATFORM_SURFACES[surface];
|
|
194
|
+
errors.push(`${surface}: the built output ships its data-part markers, but the build imports neither the ${component} component nor its behavior (${[...hooks, ...bindings].join(", ")}) from the platform packages. A ${surface} without the platform's behavior is not allowed — behavior is never forked per site.`);
|
|
195
|
+
}
|
|
196
|
+
for (const surface of Object.keys(OUTPUT_MARKERS)) if (states[surface] && !rendered.has(surface)) warnings.push(`${surface}: the code has it (${states[surface]}) but the built output ships no ${OUTPUT_MARKERS[surface]} marker — nothing renders it.`);
|
|
197
|
+
return {
|
|
198
|
+
states,
|
|
199
|
+
warnings,
|
|
200
|
+
errors
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function packagesOf(root) {
|
|
204
|
+
const packages = {};
|
|
205
|
+
for (const name of TRACKED_PACKAGES) {
|
|
206
|
+
const version = installedVersion(root, name);
|
|
207
|
+
if (version) packages[name] = version;
|
|
208
|
+
}
|
|
209
|
+
return packages;
|
|
210
|
+
}
|
|
211
|
+
/** Everything without the bundler signal — used by tests and by anything that runs outside a build. */
|
|
212
|
+
function buildSiteManifest(root) {
|
|
213
|
+
return {
|
|
214
|
+
manifestVersion: 3,
|
|
215
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
216
|
+
packages: packagesOf(root),
|
|
217
|
+
components: deriveComponentStates(collectImports(root))
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The Astro integration:
|
|
222
|
+
*
|
|
223
|
+
* import { siteManifest } from "@v-office/website-headless/manifest";
|
|
224
|
+
* integrations: [react(), siteManifest()]
|
|
225
|
+
*
|
|
226
|
+
* Typed structurally so this package needs no astro or vite dependency.
|
|
227
|
+
*/
|
|
228
|
+
function siteManifest() {
|
|
229
|
+
let root = process.cwd();
|
|
230
|
+
const bundled = emptyImports();
|
|
231
|
+
return {
|
|
232
|
+
name: "@v-office/website-headless/manifest",
|
|
233
|
+
hooks: {
|
|
234
|
+
"astro:config:setup": ({ config, updateConfig }) => {
|
|
235
|
+
root = fileURLToPath(config.root);
|
|
236
|
+
const src = path.join(root, "src") + path.sep;
|
|
237
|
+
updateConfig({ vite: { plugins: [{
|
|
238
|
+
name: "voffice-manifest-imports",
|
|
239
|
+
transform(code, id) {
|
|
240
|
+
if (id.startsWith(src) && !id.includes("/node_modules/")) collectImportsFromCode(code, bundled);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}] } });
|
|
244
|
+
},
|
|
245
|
+
"astro:build:done": ({ dir }) => {
|
|
246
|
+
const dist = fileURLToPath(dir);
|
|
247
|
+
const check = crossCheck(collectImports(root), bundled, renderedSurfaces(dist));
|
|
248
|
+
for (const w of check.warnings) console.warn(`[site-manifest] ${w}`);
|
|
249
|
+
if (check.errors.length) throw new Error(`[site-manifest] build refused:\n - ${check.errors.join("\n - ")}`);
|
|
250
|
+
const manifest = {
|
|
251
|
+
manifestVersion: 3,
|
|
252
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
253
|
+
packages: packagesOf(root),
|
|
254
|
+
components: check.states
|
|
255
|
+
};
|
|
256
|
+
fs.writeFileSync(path.join(dist, "site-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
257
|
+
const states = Object.values(manifest.components);
|
|
258
|
+
console.log(`[site-manifest] ${Object.keys(manifest.packages).length} packages, ${states.length} surfaces (${states.filter((s) => s === "imported").length} imported, ${states.filter((s) => s === "custom").length} custom); source scan, bundler and built output agree${check.warnings.length ? ` with ${check.warnings.length} warning(s)` : ""}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
export { PLATFORM_SURFACES, buildSiteManifest, collectImports, collectImportsFromCode, crossCheck, deriveComponentStates, emptyImports, renderedSurfaces, siteManifest };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@v-office/website-headless",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Framework-free behavior layer for v-office websites, never UI: the checkout flow as a subscribable store (createCheckoutFlow), validation and insurance rules, the checkout's structural requirements, the derived site-manifest Astro integration and progressive-enhancement engines for server-rendered markup",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"README.md"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.mjs",
|
|
14
|
+
"./manifest": "./dist/manifest/index.mjs",
|
|
15
|
+
"./enhance": "./dist/enhance/index.mjs",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "26.1.2",
|
|
23
|
+
"@v-office/website-sdk": "2.17.1",
|
|
24
|
+
"jsdom": "^30.0.1",
|
|
25
|
+
"oxfmt": "0.62.0",
|
|
26
|
+
"oxlint": "1.77.0",
|
|
27
|
+
"tsdown": "0.22.14",
|
|
28
|
+
"typescript": "7.0.2",
|
|
29
|
+
"vitest": "^4.1.11"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@v-office/website-sdk": ">=2.13.0 <3"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": "^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"dev": "tsdown --watch",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"lint": "oxlint .",
|
|
42
|
+
"lint:fix": "oxlint --fix .",
|
|
43
|
+
"fmt": "oxfmt",
|
|
44
|
+
"fmt:check": "oxfmt --check",
|
|
45
|
+
"check": "pnpm run typecheck && pnpm run lint && pnpm run fmt:check",
|
|
46
|
+
"test": "vitest run"
|
|
47
|
+
}
|
|
48
|
+
}
|