@universalcms/connect 0.0.0-stage → 0.13.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/README.md +779 -2
- package/dist/angular-runtime.d.mts +4 -0
- package/dist/angular-runtime.mjs +7 -0
- package/dist/angular-setup.mjs +124 -0
- package/dist/astro-runtime.d.mts +6 -0
- package/dist/astro-runtime.mjs +255 -0
- package/dist/astro-setup.mjs +215 -0
- package/dist/astro-source.mjs +451 -0
- package/dist/astro-website.d.mts +5 -0
- package/dist/astro-website.mjs +42 -0
- package/dist/cli.mjs +552 -0
- package/dist/connector-runtime.mjs +130 -0
- package/dist/content-next-setup.mjs +200 -0
- package/dist/content-registry-validation.mjs +40 -0
- package/dist/content-registry.mjs +44 -0
- package/dist/content-setup.mjs +13 -0
- package/dist/content-storefront-setup.mjs +155 -0
- package/dist/content-vite-setup.mjs +107 -0
- package/dist/core.mjs +205 -0
- package/dist/framework-ownership.mjs +68 -0
- package/dist/framework.mjs +54 -0
- package/dist/guided.mjs +335 -0
- package/dist/native-adapters.mjs +31 -0
- package/dist/native-chrome-model.d.mts +3 -0
- package/dist/native-chrome-model.mjs +14 -0
- package/dist/native-chrome-review.mjs +49 -0
- package/dist/native-chrome-source.mjs +69 -0
- package/dist/native-route-review.mjs +152 -0
- package/dist/native-routes.mjs +27 -0
- package/dist/native-website-model.d.mts +18 -0
- package/dist/native-website-model.mjs +67 -0
- package/dist/native-website-server.d.mts +1 -0
- package/dist/native-website-server.mjs +35222 -0
- package/dist/native-website-server.mjs.LEGAL.txt +56 -0
- package/dist/native-website.d.mts +3 -0
- package/dist/native-website.mjs +120 -0
- package/dist/native-website.mjs.LEGAL.txt +56 -0
- package/dist/next-publication.d.mts +12 -0
- package/dist/next-publication.mjs +107 -0
- package/dist/next-settings-layout.mjs +233 -0
- package/dist/next-source.d.mts +8 -0
- package/dist/next-source.mjs +93 -0
- package/dist/page-discovery.mjs +236 -0
- package/dist/page-mapping-review.mjs +160 -0
- package/dist/preflight-recovery.mjs +122 -0
- package/dist/preflight.mjs +138 -0
- package/dist/project-files.mjs +56 -0
- package/dist/public-bindings.d.mts +3 -0
- package/dist/public-bindings.mjs +28 -0
- package/dist/publication-browser.d.mts +5 -0
- package/dist/publication-browser.mjs +258 -0
- package/dist/publication-check.mjs +141 -0
- package/dist/publication-document.d.mts +2 -0
- package/dist/publication-document.mjs +59 -0
- package/dist/publication-next-setup.mjs +134 -0
- package/dist/publication-setup.mjs +138 -0
- package/dist/publication-verification.mjs +85 -0
- package/dist/publication.css +6 -0
- package/dist/publication.d.mts +31 -0
- package/dist/publication.mjs +320 -0
- package/dist/react-publication.d.mts +22 -0
- package/dist/react-publication.mjs +301 -0
- package/dist/react-website.d.mts +15 -0
- package/dist/react-website.mjs +735 -0
- package/dist/reviewed-commerce-ownership.mjs +26 -0
- package/dist/reviewed-commerce-settings-setup.mjs +243 -0
- package/dist/reviewed-native-ownership.mjs +118 -0
- package/dist/reviewed-storefront-source.mjs +227 -0
- package/dist/runtime-dependencies.mjs +59 -0
- package/dist/setup-environment.mjs +307 -0
- package/dist/source-bindings.mjs +77 -0
- package/dist/source-check.mjs +149 -0
- package/dist/source-collect.mjs +80 -0
- package/dist/source-config.mjs +20 -0
- package/dist/source-extract.mjs +514 -0
- package/dist/source-finalize.mjs +52 -0
- package/dist/source-routes.mjs +65 -0
- package/dist/source-setup.mjs +490 -0
- package/dist/staged-setup.mjs +287 -0
- package/dist/static-runtime.mjs +67 -0
- package/dist/static-setup.mjs +136 -0
- package/dist/svelte-runtime.d.mts +27 -0
- package/dist/svelte-runtime.mjs +40 -0
- package/dist/svelte-setup.mjs +135 -0
- package/dist/svelte-template.mjs +48 -0
- package/dist/templates.mjs +248 -0
- package/dist/vendor/analyze-project.mjs +82 -0
- package/dist/vendor/astro-compiler/LICENSE +53 -0
- package/dist/vendor/astro-compiler/astro.wasm +0 -0
- package/dist/vendor/astro-compiler/chunk-W5DTLHV4.js +1 -0
- package/dist/vendor/astro-compiler/node/index.js +1 -0
- package/dist/vendor/check-delivery.mjs +158 -0
- package/dist/vendor/dependency-diagnostics.mjs +72 -0
- package/dist/vendor/inspect-installed-dependencies.mjs +125 -0
- package/dist/vendor/project-analysis.mjs +498 -0
- package/dist/vendor/react-LICENSE.txt +21 -0
- package/dist/vendor/react-document.mjs +124 -0
- package/dist/vendor/react-dom-LICENSE.txt +21 -0
- package/dist/vendor/react-form.mjs +63 -0
- package/dist/vendor/react-safety.mjs +47 -0
- package/dist/vendor/source-manifest-provider.mjs +104 -0
- package/dist/vendor/typescript-LICENSE.txt +55 -0
- package/dist/vendor/typescript.cjs +200276 -0
- package/dist/vite-handler.mjs +54 -0
- package/dist/vite-publication-server.d.mts +3 -0
- package/dist/vite-publication-server.mjs +82 -0
- package/dist/vite-publication.d.mts +15 -0
- package/dist/vite-publication.mjs +241 -0
- package/dist/vite-source-discovery.mjs +315 -0
- package/dist/vite-source.d.mts +30 -0
- package/dist/vite-source.mjs +141 -0
- package/dist/vue-runtime.d.mts +10 -0
- package/dist/vue-runtime.mjs +50 -0
- package/dist/vue-server.d.mts +2 -0
- package/dist/vue-server.mjs +58 -0
- package/dist/vue-setup.mjs +127 -0
- package/dist/vue-source.mjs +168 -0
- package/dist/website-check.mjs +41 -0
- package/dist/website-http.mjs +149 -0
- package/dist/website-model.d.mts +20 -0
- package/dist/website-model.mjs +155 -0
- package/dist/website-next.d.mts +2 -0
- package/dist/website-next.mjs +29 -0
- package/dist/website-node.d.mts +3 -0
- package/dist/website-node.mjs +50 -0
- package/dist/website-runtime.d.mts +13 -0
- package/dist/website-runtime.mjs +396 -0
- package/dist/website-schema.mjs +22175 -0
- package/dist/website-setup.mjs +256 -0
- package/dist/website.css +49 -0
- package/dist/wizard.mjs +298 -0
- package/package.json +60 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import g from"crypto";import _ from"fs";import{TextDecoder as b,TextEncoder as v}from"util";globalThis.fs||Object.defineProperty(globalThis,"fs",{value:_});globalThis.process||Object.defineProperties(globalThis,"process",{value:process});globalThis.crypto||Object.defineProperty(globalThis,"crypto",{value:g.webcrypto?g.webcrypto:{getRandomValues(m){return g.randomFillSync(m)}}});globalThis.performance||Object.defineProperty(globalThis,"performance",{value:{now(){let[m,o]=process.hrtime();return m*1e3+o/1e6}}});var y=new v("utf-8"),w=new b("utf-8");var d=class{constructor(){this.argv=["js"],this.env={},this.exit=t=>{t!==0&&console.warn("exit code:",t)},this._exitPromise=new Promise(t=>{this._resolveExitPromise=t}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;let o=(t,e)=>{this.mem.setUint32(t+0,e,!0),this.mem.setUint32(t+4,Math.floor(e/4294967296),!0)},n=t=>{let e=this.mem.getUint32(t+0,!0),s=this.mem.getInt32(t+4,!0);return e+s*4294967296},r=t=>{let e=this.mem.getFloat64(t,!0);if(e===0)return;if(!isNaN(e))return e;let s=this.mem.getUint32(t,!0);return this._values[s]},l=(t,e)=>{if(typeof e=="number"&&e!==0){if(isNaN(e)){this.mem.setUint32(t+4,2146959360,!0),this.mem.setUint32(t,0,!0);return}this.mem.setFloat64(t,e,!0);return}if(e===void 0){this.mem.setFloat64(t,0,!0);return}let i=this._ids.get(e);i===void 0&&(i=this._idPool.pop(),i===void 0&&(i=this._values.length),this._values[i]=e,this._goRefCounts[i]=0,this._ids.set(e,i)),this._goRefCounts[i]++;let a=0;switch(typeof e){case"object":e!==null&&(a=1);break;case"string":a=2;break;case"symbol":a=3;break;case"function":a=4;break}this.mem.setUint32(t+4,2146959360|a,!0),this.mem.setUint32(t,i,!0)},c=t=>{let e=n(t+0),s=n(t+8);return new Uint8Array(this._inst.exports.mem.buffer,e,s)},f=t=>{let e=n(t+0),s=n(t+8),i=new Array(s);for(let a=0;a<s;a++)i[a]=r(e+a*8);return i},u=t=>{let e=n(t+0),s=n(t+8);return w.decode(new DataView(this._inst.exports.mem.buffer,e,s))},h=Date.now()-performance.now();this.importObject={gojs:{"runtime.wasmExit":t=>{t>>>=0;let e=this.mem.getInt32(t+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(e)},"runtime.wasmWrite":t=>{t>>>=0;let e=n(t+8),s=n(t+16),i=this.mem.getInt32(t+24,!0);_.writeSync(e,new Uint8Array(this._inst.exports.mem.buffer,s,i))},"runtime.resetMemoryDataView":t=>{t>>>=0,this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":t=>{t>>>=0,o(t+8,(h+performance.now())*1e6)},"runtime.walltime":t=>{t>>>=0;let e=new Date().getTime();o(t+8,e/1e3),this.mem.setInt32(t+16,e%1e3*1e6,!0)},"runtime.scheduleTimeoutEvent":t=>{t>>>=0;let e=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(e,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(e);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},n(t+8)+1)),this.mem.setInt32(t+16,e,!0)},"runtime.clearTimeoutEvent":t=>{t>>>=0;let e=this.mem.getInt32(t+8,!0);clearTimeout(this._scheduledTimeouts.get(e)),this._scheduledTimeouts.delete(e)},"runtime.getRandomData":t=>{t>>>=0,globalThis.crypto.getRandomValues(c(t+8))},"syscall/js.finalizeRef":t=>{t>>>=0;let e=this.mem.getUint32(t+8,!0);if(this._goRefCounts[e]--,this._goRefCounts[e]===0){let s=this._values[e];this._values[e]=null,this._ids.delete(s),this._idPool.push(e)}},"syscall/js.stringVal":t=>{t>>>=0,l(t+24,u(t+8))},"syscall/js.valueGet":t=>{t>>>=0;let e=Reflect.get(r(t+8),u(t+16));t=this._inst.exports.getsp()>>>0,l(t+32,e)},"syscall/js.valueSet":t=>{t>>>=0,Reflect.set(r(t+8),u(t+16),r(t+32))},"syscall/js.valueDelete":t=>{t>>>=0,Reflect.deleteProperty(r(t+8),u(t+16))},"syscall/js.valueIndex":t=>{t>>>=0,l(t+24,Reflect.get(r(t+8),n(t+16)))},"syscall/js.valueSetIndex":t=>{t>>>=0,Reflect.set(r(t+8),n(t+16),r(t+24))},"syscall/js.valueCall":t=>{t>>>=0;try{let e=r(t+8),s=Reflect.get(e,u(t+16)),i=f(t+32),a=Reflect.apply(s,e,i);t=this._inst.exports.getsp()>>>0,l(t+56,a),this.mem.setUint8(t+64,1)}catch(e){t=this._inst.exports.getsp()>>>0,l(t+56,e),this.mem.setUint8(t+64,0)}},"syscall/js.valueInvoke":t=>{t>>>=0;try{let e=r(t+8),s=f(t+16),i=Reflect.apply(e,void 0,s);t=this._inst.exports.getsp()>>>0,l(t+40,i),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,l(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueNew":t=>{t>>>=0;try{let e=r(t+8),s=f(t+16),i=Reflect.construct(e,s);t=this._inst.exports.getsp()>>>0,l(t+40,i),this.mem.setUint8(t+48,1)}catch(e){t=this._inst.exports.getsp()>>>0,l(t+40,e),this.mem.setUint8(t+48,0)}},"syscall/js.valueLength":t=>{t>>>=0,o(t+16,Number.parseInt(r(t+8).length))},"syscall/js.valuePrepareString":t=>{t>>>=0;let e=y.encode(String(r(t+8)));l(t+16,e),o(t+24,e.length)},"syscall/js.valueLoadString":t=>{t>>>=0;let e=r(t+8);c(t+16).set(e)},"syscall/js.valueInstanceOf":t=>{t>>>=0,this.mem.setUint8(t+24,r(t+8)instanceof r(t+16)?1:0)},"syscall/js.copyBytesToGo":t=>{t>>>=0;let e=c(t+8),s=r(t+32);if(!(s instanceof Uint8Array||s instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}let i=s.subarray(0,e.length);e.set(i),o(t+40,i.length),this.mem.setUint8(t+48,1)},"syscall/js.copyBytesToJS":t=>{t>>>=0;let e=r(t+8),s=c(t+16);if(!(e instanceof Uint8Array||e instanceof Uint8ClampedArray)){this.mem.setUint8(t+48,0);return}let i=s.subarray(0,e.length);e.set(i),o(t+40,i.length),this.mem.setUint8(t+48,1)},debug:t=>{console.log(t)}}}}async run(o){if(!(o instanceof WebAssembly.Instance))throw new Error("Go.run: WebAssembly.Instance expected");this._inst=o,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[Number.NaN,0,null,!0,!1,globalThis,this],this._goRefCounts=new Array(this._values.length).fill(Number.POSITIVE_INFINITY),this._ids=new Map([[0,1],[null,2],[!0,3],[!1,4],[globalThis,5],[this,6]]),this._idPool=[],this.exited=!1;let n=4096,r=h=>{let t=n,e=y.encode(`${h}\0`);return new Uint8Array(this.mem.buffer,n,e.length).set(e),n+=e.length,n%8!==0&&(n+=8-n%8),t},l=this.argv.length,c=[];this.argv.forEach(h=>{c.push(r(h))}),c.push(0),Object.keys(this.env).sort().forEach(h=>{c.push(r(`${h}=${this.env[h]}`))}),c.push(0);let u=n;c.forEach(h=>{this.mem.setUint32(n,h,!0),this.mem.setUint32(n+4,0,!0),n+=8}),this._inst.exports.run(l,u),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(o){let n=this;return function(){let r={id:o,this:this,args:arguments};return n._pendingEvent=r,n._resume(),r.result}}};export{d as a};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as c}from"../chunk-W5DTLHV4.js";import{promises as m}from"fs";import{fileURLToPath as f}from"url";var w=async(t,s)=>i().then(r=>r.transform(t,s)),l=async(t,s)=>i().then(r=>r.parse(t,s)),b=async(t,s)=>i().then(r=>r.convertToTSX(t,s)),P=async t=>{let{default:s}=await import(`data:text/javascript;charset=utf-8;base64,${Buffer.from(t).toString("base64")}`);return s},n,g=()=>{n=void 0,globalThis["@astrojs/compiler"]=void 0},i=()=>(n||(n=d().catch(t=>{throw n=void 0,t})),n),y=async(t,s)=>{let r;return r=await(async()=>{let o=await m.readFile(t).then(e=>e.buffer);return WebAssembly.instantiate(new Uint8Array(o),s)})(),r},d=async()=>{let t=new c,s=await y(f(new URL("../astro.wasm",import.meta.url)),t.importObject);t.run(s.instance);let r=globalThis["@astrojs/compiler"];return{transform:(a,o)=>new Promise(e=>{try{e(r.transform(a,o||{}))}catch(p){throw n=void 0,p}}),parse:(a,o)=>new Promise(e=>e(r.parse(a,o||{}))).catch(e=>{throw n=void 0,e}).then(e=>({...e,ast:JSON.parse(e.ast)})),convertToTSX:(a,o)=>new Promise(e=>e(r.convertToTSX(a,o||{}))).catch(e=>{throw n=void 0,e}).then(e=>({...e,map:JSON.parse(e.map)}))}};export{P as compile,b as convertToTSX,l as parse,g as teardown,w as transform};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Private, read-only delivery check. Never reads .env or imports website code. */
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export const DELIVERY_CHECK_TIMEOUT_MS = 10_000;
|
|
6
|
+
export const DELIVERY_CHECK_MAX_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
export const DELIVERY_CHECK_MESSAGES = Object.freeze({
|
|
8
|
+
verified: 'The requested published page was returned. Website rendering, installed dependencies and content updates need separate checks.',
|
|
9
|
+
'not-published': 'No matching published page was returned. Verify the slug, locale and publication in the selected environment.',
|
|
10
|
+
'missing-key': 'Delivery was not checked. Set CMS_API_KEY in the private verification process.',
|
|
11
|
+
'configuration-error': 'Delivery was not checked. Configure a valid API origin, site, environment, server key, page slug and locale.',
|
|
12
|
+
'authorization-rejected': 'Delivery authorization was rejected. Check the server key, its environment, expiry, revocation and current issuer access.',
|
|
13
|
+
'invalid-response': 'Delivery was not verified. The API returned an unexpected, redirected, ambiguous or oversized response.',
|
|
14
|
+
'http-error': 'Delivery was not verified. The API rejected the read request or is unavailable.',
|
|
15
|
+
'transport-error': 'Delivery was not verified. The private process could not complete the API request.',
|
|
16
|
+
timeout: 'Delivery was not verified. The API request exceeded the verification deadline.',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
+
const keyPattern = /^cms_server\.([0-9a-f-]{36})\.([0-9a-f-]{36})\.[A-Za-z0-9_-]{43}$/;
|
|
21
|
+
const object = value => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
22
|
+
const invalid = Symbol('Invalid delivery response');
|
|
23
|
+
const result = status => ({ status, verified: status === 'verified', message: DELIVERY_CHECK_MESSAGES[status] });
|
|
24
|
+
const positiveBoundedInteger = (value, limit) => Number.isSafeInteger(value) && value > 0 && value <= limit;
|
|
25
|
+
|
|
26
|
+
function apiOrigin(value) {
|
|
27
|
+
if (typeof value !== 'string') return null;
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(value);
|
|
30
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
31
|
+
if (!url.username && !url.password && !url.search && !url.hash && url.pathname === '/' &&
|
|
32
|
+
(url.protocol === 'https:' || (url.protocol === 'http:' && loopback))) return url.origin;
|
|
33
|
+
} catch { /* Configuration is never echoed. */ }
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function untilAborted(pending, signal) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const cancel = () => reject(new Error('Delivery check canceled'));
|
|
40
|
+
if (signal.aborted) { cancel(); return; }
|
|
41
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
42
|
+
pending.then(value => { signal.removeEventListener('abort', cancel); resolve(value); }, error => { signal.removeEventListener('abort', cancel); reject(error); });
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cancelBody(response) {
|
|
47
|
+
try { if (response?.body && !response.body.locked) void response.body.cancel().catch(() => {}); }
|
|
48
|
+
catch { /* Cleanup must not replace the sanitized outcome. */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function boundedBody(response, maxBytes, signal, checkDeadline) {
|
|
52
|
+
const declared = response.headers.get('content-length');
|
|
53
|
+
if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > maxBytes)) throw invalid;
|
|
54
|
+
if (!response.body) throw invalid;
|
|
55
|
+
const reader = response.body.getReader(), body = new Uint8Array(maxBytes);
|
|
56
|
+
let bytes = 0;
|
|
57
|
+
try {
|
|
58
|
+
while (true) {
|
|
59
|
+
const chunk = await untilAborted(reader.read(), signal);
|
|
60
|
+
checkDeadline();
|
|
61
|
+
if (chunk.done) break;
|
|
62
|
+
bytes += chunk.value.byteLength;
|
|
63
|
+
if (bytes > maxBytes) throw invalid;
|
|
64
|
+
body.set(chunk.value, bytes - chunk.value.byteLength);
|
|
65
|
+
}
|
|
66
|
+
try { return new TextDecoder('utf-8', { fatal: true }).decode(body.subarray(0, bytes)); }
|
|
67
|
+
catch { throw invalid; }
|
|
68
|
+
} finally {
|
|
69
|
+
// Even a stalled cancellation cannot hold the verification deadline open.
|
|
70
|
+
void reader.cancel().catch(() => {});
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function publishedPage(raw, slug, locale) {
|
|
76
|
+
let envelope;
|
|
77
|
+
try { envelope = JSON.parse(raw); } catch { throw invalid; }
|
|
78
|
+
const data = envelope?.data;
|
|
79
|
+
if (!object(envelope) || !object(data) || !Array.isArray(data.items) || !Number.isSafeInteger(data.total) || data.total < 0) throw invalid;
|
|
80
|
+
if (data.items.length === 0 && data.total === 0) return 'not-published';
|
|
81
|
+
if (data.items.length !== 1 || data.total !== 1) throw invalid;
|
|
82
|
+
const item = data.items[0], document = item?.document;
|
|
83
|
+
// Check the published-delivery envelope, not renderer or full block compatibility.
|
|
84
|
+
if (!object(item) || !uuid.test(item.id) || item.kind !== 'page' || item.slug !== slug || item.locale !== locale ||
|
|
85
|
+
!Number.isSafeInteger(item.version) || item.version < 0 || typeof item.publishedAt !== 'string' || !Number.isFinite(Date.parse(item.publishedAt)) ||
|
|
86
|
+
item.preview === true || (item.status !== undefined && item.status !== 'published') || !object(document) || document.schemaVersion !== 1 ||
|
|
87
|
+
typeof document.title !== 'string' || !document.title.trim() || document.title.length > 200 ||
|
|
88
|
+
typeof document.excerpt !== 'string' || document.excerpt.length > 1000 || !Array.isArray(document.blocks) || document.blocks.length > 200) throw invalid;
|
|
89
|
+
return 'verified';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Returns constant outcomes only; neither configuration nor response content is returned or printed. */
|
|
93
|
+
export async function verifyPublishedDelivery({ env = process.env, slug, locale = 'en', fetcher = globalThis.fetch, timeoutMs = DELIVERY_CHECK_TIMEOUT_MS, maxResponseBytes = DELIVERY_CHECK_MAX_BYTES } = {}) {
|
|
94
|
+
let origin, siteId, environmentId, key, canonicalLocale;
|
|
95
|
+
try {
|
|
96
|
+
key = env?.CMS_API_KEY;
|
|
97
|
+
if (typeof key !== 'string' || !key.trim()) return result('missing-key');
|
|
98
|
+
origin = apiOrigin(env?.CMS_API_BASE_URL); siteId = env?.CMS_SITE_ID; environmentId = env?.CMS_ENVIRONMENT_ID;
|
|
99
|
+
const keyParts = keyPattern.exec(key);
|
|
100
|
+
if (!origin || typeof siteId !== 'string' || !uuid.test(siteId) || typeof environmentId !== 'string' || !uuid.test(environmentId) ||
|
|
101
|
+
!keyParts || !uuid.test(keyParts[1]) || !uuid.test(keyParts[2]) || typeof slug !== 'string' || slug.length > 160 || !/^[a-z0-9]+(?:[-/][a-z0-9]+)*$/.test(slug) ||
|
|
102
|
+
typeof locale !== 'string' || locale.length < 2 || locale.length > 35 || typeof fetcher !== 'function' ||
|
|
103
|
+
!positiveBoundedInteger(timeoutMs, DELIVERY_CHECK_TIMEOUT_MS) || !positiveBoundedInteger(maxResponseBytes, DELIVERY_CHECK_MAX_BYTES)) return result('configuration-error');
|
|
104
|
+
canonicalLocale = Intl.getCanonicalLocales(locale)[0];
|
|
105
|
+
if (!canonicalLocale) return result('configuration-error');
|
|
106
|
+
} catch { return result('configuration-error'); }
|
|
107
|
+
const url = new URL(`/api/v1/delivery/sites/${siteId}/content`, origin);
|
|
108
|
+
url.search = new URLSearchParams({ environmentId, kind: 'page', slug, locale: canonicalLocale, limit: '2' }).toString();
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
let timedOut = false, response;
|
|
111
|
+
const deadline = Date.now() + timeoutMs;
|
|
112
|
+
const checkDeadline = () => { if (Date.now() >= deadline) { timedOut = true; controller.abort(); throw new Error('Delivery check deadline'); } };
|
|
113
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
114
|
+
try {
|
|
115
|
+
response = await untilAborted(fetcher(url, {
|
|
116
|
+
method: 'GET', headers: { Authorization: `Bearer ${key}`, Accept: 'application/json' },
|
|
117
|
+
credentials: 'omit', redirect: 'error', cache: 'no-store', referrerPolicy: 'no-referrer', signal: controller.signal,
|
|
118
|
+
}), controller.signal);
|
|
119
|
+
checkDeadline();
|
|
120
|
+
if (!(response instanceof Response) || response.redirected || ['opaque', 'opaqueredirect'].includes(response.type) ||
|
|
121
|
+
(response.url && response.url !== url.href) || (response.status >= 300 && response.status < 400)) return result('invalid-response');
|
|
122
|
+
if ([401, 403].includes(response.status)) return result('authorization-rejected');
|
|
123
|
+
if (!response.ok) return result('http-error');
|
|
124
|
+
if (response.status !== 200 || response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() !== 'application/json') throw invalid;
|
|
125
|
+
const status = publishedPage(await boundedBody(response, maxResponseBytes, controller.signal, checkDeadline), slug, canonicalLocale);
|
|
126
|
+
checkDeadline();
|
|
127
|
+
return result(status);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return result(timedOut ? 'timeout' : error === invalid ? 'invalid-response' : 'transport-error');
|
|
130
|
+
} finally { clearTimeout(timer); controller.abort(); cancelBody(response); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function parseDeliveryArguments(args) {
|
|
134
|
+
if (!Array.isArray(args)) return null;
|
|
135
|
+
const options = {};
|
|
136
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
137
|
+
const name = args[index], value = args[index + 1];
|
|
138
|
+
if (!['--slug', '--locale'].includes(name) || typeof value !== 'string' || !value || value.startsWith('-') || Object.hasOwn(options, name.slice(2))) return null;
|
|
139
|
+
options[name.slice(2)] = value;
|
|
140
|
+
}
|
|
141
|
+
return options.slug ? options : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function main() {
|
|
145
|
+
if (process.argv.length === 3 && process.argv[2] === '--help') {
|
|
146
|
+
process.stdout.write('Usage: check-delivery --slug <published-page-slug> [--locale <locale>]\nUses private CMS_API_BASE_URL, CMS_SITE_ID, CMS_ENVIRONMENT_ID and CMS_API_KEY. Reads published content only; does not read .env or verify website rendering.\n');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let outcome;
|
|
150
|
+
try {
|
|
151
|
+
const options = parseDeliveryArguments(process.argv.slice(2));
|
|
152
|
+
outcome = options ? await verifyPublishedDelivery(options) : result('configuration-error');
|
|
153
|
+
} catch { outcome = result('configuration-error'); }
|
|
154
|
+
process.stdout.write(JSON.stringify(outcome) + '\n');
|
|
155
|
+
if (!outcome.verified) process.exitCode = 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Sanitized dependency advice. Browser-safe: no filesystem, modules or network. */
|
|
2
|
+
export const CMS_DEPENDENCY_NAMES = Object.freeze({
|
|
3
|
+
sdk: '@universalcms/sdk', react: '@universalcms/react', next: '@universalcms/next',
|
|
4
|
+
});
|
|
5
|
+
const packageKeys = Object.keys(CMS_DEPENDENCY_NAMES);
|
|
6
|
+
const declarations = ['runtime', 'development', 'missing', 'unknown'];
|
|
7
|
+
const installations = ['present', 'missing', 'unknown'];
|
|
8
|
+
const compatibility = ['compatible', 'incompatible', 'unknown'];
|
|
9
|
+
|
|
10
|
+
function values(value, keys) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
12
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
13
|
+
if (Object.keys(descriptors).length !== keys.length || keys.some(key => !descriptors[key] || !('value' in descriptors[key]))) return null;
|
|
14
|
+
return Object.fromEntries(keys.map(key => [key, descriptors[key].value]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Reject arbitrary labels, paths, package versions, credentials and getters. */
|
|
18
|
+
export function validateDependencyDiagnostics(value) {
|
|
19
|
+
try {
|
|
20
|
+
const diagnostic = values(value, ['generatedAt', 'packages', 'node']);
|
|
21
|
+
if (!diagnostic || typeof diagnostic.generatedAt !== 'string' ||
|
|
22
|
+
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(diagnostic.generatedAt) ||
|
|
23
|
+
new Date(diagnostic.generatedAt).toISOString() !== diagnostic.generatedAt ||
|
|
24
|
+
!compatibility.includes(diagnostic.node)) return null;
|
|
25
|
+
const candidates = values(diagnostic.packages, packageKeys);
|
|
26
|
+
if (!candidates) return null;
|
|
27
|
+
const packages = {};
|
|
28
|
+
for (const key of packageKeys) {
|
|
29
|
+
const candidate = values(candidates[key], ['declared', 'installed', 'compatibility']);
|
|
30
|
+
if (!candidate || !declarations.includes(candidate.declared) || !installations.includes(candidate.installed) ||
|
|
31
|
+
!compatibility.includes(candidate.compatibility) ||
|
|
32
|
+
(candidate.installed !== 'present' && candidate.compatibility !== 'unknown')) return null;
|
|
33
|
+
packages[key] = { declared: candidate.declared, installed: candidate.installed, compatibility: candidate.compatibility };
|
|
34
|
+
}
|
|
35
|
+
if (packages.sdk.compatibility === 'compatible' && diagnostic.node !== 'compatible') return null;
|
|
36
|
+
if (packages.next.compatibility === 'compatible' && (diagnostic.node !== 'compatible' || packages.sdk.compatibility !== 'compatible')) return null;
|
|
37
|
+
// The React renderer may run in a browser even when the inspection process
|
|
38
|
+
// cannot run the credentialed Node client. Its package dependency must exist.
|
|
39
|
+
if (packages.react.compatibility === 'compatible' && packages.sdk.installed !== 'present') return null;
|
|
40
|
+
return { generatedAt: diagnostic.generatedAt, packages, node: diagnostic.node };
|
|
41
|
+
} catch { return null; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A manifest establishes declarations only, never installation or runtime health. */
|
|
45
|
+
export function createManifestDependencyDiagnostics(packageJsonText, now = new Date()) {
|
|
46
|
+
let manifest = null;
|
|
47
|
+
try {
|
|
48
|
+
if (typeof packageJsonText === 'string' && new TextEncoder().encode(packageJsonText).byteLength <= 128 * 1024) {
|
|
49
|
+
const parsed = JSON.parse(packageJsonText);
|
|
50
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) manifest = parsed;
|
|
51
|
+
}
|
|
52
|
+
} catch { /* Keep declaration confidence unknown, without reflecting source. */ }
|
|
53
|
+
const sections = ['dependencies', 'devDependencies', 'optionalDependencies'];
|
|
54
|
+
const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
55
|
+
const invalidSections = manifest && sections.some(key => own(manifest, key) && (!manifest[key] || typeof manifest[key] !== 'object' || Array.isArray(manifest[key])));
|
|
56
|
+
const declaration = name => {
|
|
57
|
+
if (!manifest || invalidSections) return 'unknown';
|
|
58
|
+
for (const section of sections) {
|
|
59
|
+
if (manifest[section] && own(manifest[section], name) && (typeof manifest[section][name] !== 'string' || !manifest[section][name].trim())) return 'unknown';
|
|
60
|
+
}
|
|
61
|
+
if (['dependencies', 'optionalDependencies'].some(key => manifest[key] && own(manifest[key], name))) return 'runtime';
|
|
62
|
+
if (manifest.devDependencies && own(manifest.devDependencies, name)) return 'development';
|
|
63
|
+
return 'missing';
|
|
64
|
+
};
|
|
65
|
+
const packages = {};
|
|
66
|
+
for (const [key, name] of Object.entries(CMS_DEPENDENCY_NAMES)) {
|
|
67
|
+
const declared = declaration(name);
|
|
68
|
+
packages[key] = { declared, installed: 'unknown', compatibility: 'unknown' };
|
|
69
|
+
}
|
|
70
|
+
const generatedAt = new Date(now).toISOString();
|
|
71
|
+
return { generatedAt, packages, node: 'unknown' };
|
|
72
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** Bounded local metadata inspection. Never executes website modules or scripts. */
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open, realpath } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createManifestDependencyDiagnostics, CMS_DEPENDENCY_NAMES } from './dependency-diagnostics.mjs';
|
|
6
|
+
|
|
7
|
+
const MAX_METADATA_BYTES = 128 * 1024;
|
|
8
|
+
const packages = [
|
|
9
|
+
{ key: 'sdk', name: CMS_DEPENDENCY_NAMES.sdk, entry: 'dist/index.js', exportCondition: 'default' },
|
|
10
|
+
{ key: 'react', name: CMS_DEPENDENCY_NAMES.react, entry: 'dist/index.js', exportCondition: 'import' },
|
|
11
|
+
{ key: 'next', name: CMS_DEPENDENCY_NAMES.next, entry: 'dist/index.js', exportCondition: 'import' },
|
|
12
|
+
{ key: 'reactPeer', name: 'react', entry: 'index.js' },
|
|
13
|
+
{ key: 'nextPeer', name: 'next', entry: 'dist/server/next.js' },
|
|
14
|
+
];
|
|
15
|
+
const inside = (root, target) => {
|
|
16
|
+
const relative = path.relative(root, target);
|
|
17
|
+
return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
18
|
+
};
|
|
19
|
+
const plain = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
20
|
+
|
|
21
|
+
async function readMetadata(filename, directory) {
|
|
22
|
+
let handle;
|
|
23
|
+
try {
|
|
24
|
+
const before = await lstat(filename);
|
|
25
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > MAX_METADATA_BYTES || await realpath(filename) !== filename || !inside(directory, filename)) return null;
|
|
26
|
+
handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
27
|
+
const opened = await handle.stat(), current = await lstat(filename);
|
|
28
|
+
if (!opened.isFile() || current.isSymbolicLink() || opened.ino !== current.ino || opened.dev !== current.dev ||
|
|
29
|
+
opened.size > MAX_METADATA_BYTES || await realpath(filename) !== filename) return null;
|
|
30
|
+
const buffer = Buffer.alloc(MAX_METADATA_BYTES + 1);
|
|
31
|
+
let bytes = 0;
|
|
32
|
+
while (bytes < buffer.length) {
|
|
33
|
+
const part = await handle.read(buffer, bytes, buffer.length - bytes, bytes);
|
|
34
|
+
if (!part.bytesRead) break;
|
|
35
|
+
bytes += part.bytesRead;
|
|
36
|
+
}
|
|
37
|
+
if (bytes > MAX_METADATA_BYTES) return null;
|
|
38
|
+
const parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, bytes)));
|
|
39
|
+
return plain(parsed) ? parsed : null;
|
|
40
|
+
} catch { return null; }
|
|
41
|
+
finally { await handle?.close(); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function numericVersion(value) {
|
|
45
|
+
if (typeof value !== 'string') return null;
|
|
46
|
+
const match = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(value);
|
|
47
|
+
if (!match) return null;
|
|
48
|
+
const numbers = match.slice(1).map(Number);
|
|
49
|
+
return numbers.every(Number.isSafeInteger) ? numbers : null;
|
|
50
|
+
}
|
|
51
|
+
function nodeCompatibility(value) {
|
|
52
|
+
const version = numericVersion(value);
|
|
53
|
+
return !version ? 'unknown' : version[0] > 20 || (version[0] === 20 && version[1] >= 19) ? 'compatible' : 'incompatible';
|
|
54
|
+
}
|
|
55
|
+
function peerCompatibility(value, major, minimumMinor = 0) {
|
|
56
|
+
const version = numericVersion(value);
|
|
57
|
+
return !version ? 'unknown' : version[0] === major && version[1] >= minimumMinor ? 'compatible' : 'incompatible';
|
|
58
|
+
}
|
|
59
|
+
function allCompatible(states) {
|
|
60
|
+
return states.includes('incompatible') ? 'incompatible' : states.every(value => value === 'compatible') ? 'compatible' : 'unknown';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function inspectPackage(modules, descriptor) {
|
|
64
|
+
const directory = path.join(modules, ...descriptor.name.split('/'));
|
|
65
|
+
let info;
|
|
66
|
+
// Validate a scoped package's parent before even probing its child. A scope
|
|
67
|
+
// link outside node_modules must not leak whether that external child exists.
|
|
68
|
+
if (descriptor.name.startsWith('@')) {
|
|
69
|
+
try {
|
|
70
|
+
const scope = await realpath(path.dirname(directory));
|
|
71
|
+
if (!inside(modules, scope) || !(await lstat(scope)).isDirectory()) return { installed: 'unknown', version: null };
|
|
72
|
+
} catch (error) { return { installed: error.code === 'ENOENT' || error.code === 'ENOTDIR' ? 'missing' : 'unknown', version: null }; }
|
|
73
|
+
}
|
|
74
|
+
try { info = await lstat(directory); }
|
|
75
|
+
catch (error) { return { installed: error.code === 'ENOENT' || error.code === 'ENOTDIR' ? 'missing' : 'unknown', version: null }; }
|
|
76
|
+
try {
|
|
77
|
+
if (!info.isDirectory() && !info.isSymbolicLink()) return { installed: 'unknown', version: null };
|
|
78
|
+
// pnpm stores package directories behind symlinks inside node_modules/.pnpm.
|
|
79
|
+
// External links and shared workspaces are outside this bounded inspection.
|
|
80
|
+
const actual = await realpath(directory);
|
|
81
|
+
if (!inside(modules, actual) || actual === modules || !(await lstat(actual)).isDirectory()) return { installed: 'unknown', version: null };
|
|
82
|
+
const metadata = await readMetadata(path.join(actual, 'package.json'), actual);
|
|
83
|
+
if (!metadata || metadata.name !== descriptor.name || typeof metadata.version !== 'string') return { installed: 'unknown', version: null };
|
|
84
|
+
const entry = path.join(actual, descriptor.entry), entryInfo = await lstat(entry);
|
|
85
|
+
if (!entryInfo.isFile() || entryInfo.isSymbolicLink() || await realpath(entry) !== entry || !inside(actual, entry)) return { installed: 'unknown', version: null };
|
|
86
|
+
if (descriptor.exportCondition) {
|
|
87
|
+
if (!plain(metadata.exports) || !plain(metadata.exports['.']) || metadata.exports['.'][descriptor.exportCondition] !== `./${descriptor.entry}`) return { installed: 'unknown', version: null };
|
|
88
|
+
} else if (descriptor.name === 'next' && ![`./${descriptor.entry}`, descriptor.entry].includes(metadata.main)) return { installed: 'unknown', version: null };
|
|
89
|
+
return { installed: 'present', version: metadata.version };
|
|
90
|
+
} catch { return { installed: 'unknown', version: null }; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Inspect one selected project; public output never contains versions or paths. */
|
|
94
|
+
export async function inspectInstalledDependencies(directory, packageJsonText, { now = new Date(), nodeVersion = process.version } = {}) {
|
|
95
|
+
const diagnostics = createManifestDependencyDiagnostics(packageJsonText, now);
|
|
96
|
+
diagnostics.node = nodeCompatibility(nodeVersion);
|
|
97
|
+
let root;
|
|
98
|
+
try {
|
|
99
|
+
const requested = path.resolve(directory), rootInfo = await lstat(requested);
|
|
100
|
+
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) return diagnostics;
|
|
101
|
+
root = await realpath(requested);
|
|
102
|
+
} catch { return diagnostics; }
|
|
103
|
+
const modules = path.join(root, 'node_modules');
|
|
104
|
+
let modulesInfo;
|
|
105
|
+
try { modulesInfo = await lstat(modules); }
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') {
|
|
108
|
+
for (const candidate of Object.values(diagnostics.packages)) candidate.installed = 'missing';
|
|
109
|
+
}
|
|
110
|
+
return diagnostics;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
if (!modulesInfo.isDirectory() || modulesInfo.isSymbolicLink() || await realpath(modules) !== modules) return diagnostics;
|
|
114
|
+
} catch { return diagnostics; }
|
|
115
|
+
const observed = {};
|
|
116
|
+
for (const descriptor of packages) observed[descriptor.key] = await inspectPackage(modules, descriptor);
|
|
117
|
+
for (const key of Object.keys(CMS_DEPENDENCY_NAMES)) diagnostics.packages[key].installed = observed[key].installed;
|
|
118
|
+
const release = key => observed[key].installed === 'present' && observed[key].version === '0.1.0' ? 'compatible' : 'unknown';
|
|
119
|
+
const reactPeer = observed.reactPeer.installed === 'present' ? peerCompatibility(observed.reactPeer.version, 19, 2) : 'unknown';
|
|
120
|
+
const nextPeer = observed.nextPeer.installed === 'present' ? peerCompatibility(observed.nextPeer.version, 16) : 'unknown';
|
|
121
|
+
if (observed.sdk.installed === 'present') diagnostics.packages.sdk.compatibility = allCompatible([release('sdk'), diagnostics.node]);
|
|
122
|
+
if (observed.react.installed === 'present') diagnostics.packages.react.compatibility = allCompatible([release('react'), release('sdk'), reactPeer]);
|
|
123
|
+
if (observed.next.installed === 'present') diagnostics.packages.next.compatibility = allCompatible([release('next'), release('sdk'), diagnostics.node, reactPeer, nextPeer]);
|
|
124
|
+
return diagnostics;
|
|
125
|
+
}
|