@universalcms/connect 0.1.0 → 0.13.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.
Files changed (130) hide show
  1. package/README.md +699 -37
  2. package/dist/angular-runtime.d.mts +4 -0
  3. package/dist/angular-runtime.mjs +7 -0
  4. package/dist/angular-setup.mjs +124 -0
  5. package/dist/astro-runtime.d.mts +6 -0
  6. package/dist/astro-runtime.mjs +255 -0
  7. package/dist/astro-setup.mjs +215 -0
  8. package/dist/astro-source.mjs +451 -0
  9. package/dist/astro-website.d.mts +5 -0
  10. package/dist/astro-website.mjs +42 -0
  11. package/dist/cli.mjs +418 -15
  12. package/dist/connector-runtime.mjs +130 -0
  13. package/dist/content-next-setup.mjs +207 -0
  14. package/dist/content-registry-validation.mjs +40 -0
  15. package/dist/content-registry.mjs +44 -0
  16. package/dist/content-setup.mjs +13 -0
  17. package/dist/content-storefront-setup.mjs +155 -0
  18. package/dist/content-vite-setup.mjs +107 -0
  19. package/dist/core.mjs +32 -51
  20. package/dist/framework-ownership.mjs +68 -0
  21. package/dist/framework.mjs +54 -0
  22. package/dist/guided.mjs +514 -0
  23. package/dist/native-adapters.mjs +31 -0
  24. package/dist/native-chrome-model.d.mts +3 -0
  25. package/dist/native-chrome-model.mjs +14 -0
  26. package/dist/native-chrome-review.mjs +49 -0
  27. package/dist/native-chrome-source.mjs +69 -0
  28. package/dist/native-route-review.mjs +152 -0
  29. package/dist/native-routes.mjs +27 -0
  30. package/dist/native-website-model.d.mts +18 -0
  31. package/dist/native-website-model.mjs +67 -0
  32. package/dist/native-website-server.d.mts +1 -0
  33. package/dist/native-website-server.mjs +35222 -0
  34. package/dist/native-website-server.mjs.LEGAL.txt +56 -0
  35. package/dist/native-website.d.mts +3 -0
  36. package/dist/native-website.mjs +120 -0
  37. package/dist/native-website.mjs.LEGAL.txt +56 -0
  38. package/dist/next-publication.d.mts +12 -0
  39. package/dist/next-publication.mjs +107 -0
  40. package/dist/next-settings-layout.mjs +233 -0
  41. package/dist/next-source.d.mts +8 -0
  42. package/dist/next-source.mjs +93 -0
  43. package/dist/page-discovery.mjs +236 -0
  44. package/dist/page-mapping-review.mjs +160 -0
  45. package/dist/preflight-recovery.mjs +130 -0
  46. package/dist/preflight.mjs +138 -0
  47. package/dist/project-files.mjs +56 -0
  48. package/dist/public-bindings.d.mts +3 -0
  49. package/dist/public-bindings.mjs +28 -0
  50. package/dist/publication-browser.d.mts +5 -0
  51. package/dist/publication-browser.mjs +258 -0
  52. package/dist/publication-check.mjs +141 -0
  53. package/dist/publication-document.d.mts +2 -0
  54. package/dist/publication-document.mjs +59 -0
  55. package/dist/publication-next-setup.mjs +134 -0
  56. package/dist/publication-setup.mjs +138 -0
  57. package/dist/publication-verification.mjs +85 -0
  58. package/dist/publication.css +6 -0
  59. package/dist/publication.d.mts +31 -0
  60. package/dist/publication.mjs +320 -0
  61. package/dist/react-publication.d.mts +22 -0
  62. package/dist/react-publication.mjs +301 -0
  63. package/dist/react-website.d.mts +15 -0
  64. package/dist/react-website.mjs +735 -0
  65. package/dist/reviewed-commerce-ownership.mjs +26 -0
  66. package/dist/reviewed-commerce-settings-setup.mjs +243 -0
  67. package/dist/reviewed-native-ownership.mjs +118 -0
  68. package/dist/reviewed-storefront-source.mjs +227 -0
  69. package/dist/runtime-dependencies.mjs +59 -0
  70. package/dist/settings-only-setup.mjs +100 -0
  71. package/dist/settings-only.d.mts +4 -0
  72. package/dist/settings-only.mjs +49 -0
  73. package/dist/setup-environment.mjs +70 -0
  74. package/dist/source-bindings.mjs +77 -0
  75. package/dist/source-check.mjs +149 -0
  76. package/dist/source-collect.mjs +80 -0
  77. package/dist/source-config.mjs +20 -0
  78. package/dist/source-extract.mjs +514 -0
  79. package/dist/source-finalize.mjs +52 -0
  80. package/dist/source-routes.mjs +65 -0
  81. package/dist/source-setup.mjs +490 -0
  82. package/dist/staged-setup.mjs +287 -0
  83. package/dist/static-runtime.mjs +67 -0
  84. package/dist/static-setup.mjs +136 -0
  85. package/dist/svelte-runtime.d.mts +27 -0
  86. package/dist/svelte-runtime.mjs +40 -0
  87. package/dist/svelte-setup.mjs +135 -0
  88. package/dist/svelte-template.mjs +48 -0
  89. package/dist/templates.mjs +167 -3
  90. package/dist/vendor/astro-compiler/LICENSE +53 -0
  91. package/dist/vendor/astro-compiler/astro.wasm +0 -0
  92. package/dist/vendor/astro-compiler/chunk-W5DTLHV4.js +1 -0
  93. package/dist/vendor/astro-compiler/node/index.js +1 -0
  94. package/dist/vendor/project-analysis.mjs +58 -4
  95. package/dist/vendor/react-LICENSE.txt +21 -0
  96. package/dist/vendor/react-document.mjs +124 -0
  97. package/dist/vendor/react-dom-LICENSE.txt +21 -0
  98. package/dist/vendor/react-form.mjs +63 -0
  99. package/dist/vendor/react-safety.mjs +47 -0
  100. package/dist/vendor/source-manifest-provider.mjs +104 -0
  101. package/dist/vendor/typescript-LICENSE.txt +55 -0
  102. package/dist/vendor/typescript.cjs +200276 -0
  103. package/dist/vite-publication-server.d.mts +3 -0
  104. package/dist/vite-publication-server.mjs +82 -0
  105. package/dist/vite-publication.d.mts +15 -0
  106. package/dist/vite-publication.mjs +241 -0
  107. package/dist/vite-source-discovery.mjs +315 -0
  108. package/dist/vite-source.d.mts +30 -0
  109. package/dist/vite-source.mjs +141 -0
  110. package/dist/vue-runtime.d.mts +10 -0
  111. package/dist/vue-runtime.mjs +50 -0
  112. package/dist/vue-server.d.mts +2 -0
  113. package/dist/vue-server.mjs +58 -0
  114. package/dist/vue-setup.mjs +127 -0
  115. package/dist/vue-source.mjs +168 -0
  116. package/dist/website-check.mjs +41 -0
  117. package/dist/website-http.mjs +149 -0
  118. package/dist/website-model.d.mts +20 -0
  119. package/dist/website-model.mjs +155 -0
  120. package/dist/website-next.d.mts +2 -0
  121. package/dist/website-next.mjs +29 -0
  122. package/dist/website-node.d.mts +3 -0
  123. package/dist/website-node.mjs +50 -0
  124. package/dist/website-runtime.d.mts +13 -0
  125. package/dist/website-runtime.mjs +404 -0
  126. package/dist/website-schema.mjs +22175 -0
  127. package/dist/website-setup.mjs +256 -0
  128. package/dist/website.css +49 -0
  129. package/dist/wizard.mjs +237 -38
  130. package/package.json +44 -7
@@ -0,0 +1,135 @@
1
+ /** Bounded SvelteKit Node setup. Reads literal public project evidence only. */
2
+ import path from 'node:path';
3
+ import {lstat,readdir,realpath} from 'node:fs/promises';
4
+ import ts from './vendor/typescript.cjs';
5
+ import {projectRoot,readProjectFile,SetupError} from './core.mjs';
6
+ import {contentDigest,readContentRegistry,readFrozenContent,validateContentRegistry,CONTENT_BINDINGS_FILE} from './content-registry.mjs';
7
+ import {validatePublicationConfig,isExcludedPublicationPath} from './publication.mjs';
8
+ import {WEBSITE_SECTIONS} from './website-http.mjs';
9
+ import {transformNativeTemplate} from './svelte-template.mjs';
10
+ export const SVELTE_MANIFEST='.universalcms/svelte.integration.json';
11
+ const json=value=>JSON.stringify(value,null,2)+'\n', fail=message=>{throw new SetupError(`SvelteKit automatic setup needs review: ${message}. Existing files were preserved.`);};
12
+ import {readNativeChromeReview,reviewedNativeChrome,nativeChromeObservation} from './native-chrome-review.mjs';
13
+ const inspections=new WeakMap();
14
+ export async function svelteRead(root,file){const text=await readProjectFile(root,file);if(text!==null&&(await lstat(path.join(root,file))).nlink!==1)fail('linked files');return text;}
15
+ export function supportsNativeNode(framework,version){const m=/^(\d+)\.(\d+)\.(\d+)$/.exec(version??'');if(!m)return false;const [,major,minor,patch]=m.map(Number);return framework==='angular'?major>=26||major===24&&minor>=15||major===22&&(minor>22||minor===22&&patch>=3):framework==='svelte'&&(major>22||major===22&&minor>=17);}
16
+ export function svelteVersion(value,major,minor=0,patch=0){const m=typeof value==='string'&&/^[~^]?(\d+)\.(\d+)\.(\d+)$/.exec(value);return !!m&&+m[1]===major&&(+m[2]>minor||+m[2]===minor&&+m[3]>=patch);}
17
+ export async function inspectSvelteAssets(root,directory='static') {
18
+ let count=0;const walk=async(relative,depth=0)=>{let stat;try{stat=await lstat(path.join(root,relative));}catch(e){if(e.code==='ENOENT')return;throw e;}
19
+ if(depth>12||stat.isSymbolicLink()||!stat.isDirectory()||await realpath(path.join(root,relative))!==path.join(root,relative))fail('static assets have unsafe ownership');
20
+ for(const item of await readdir(path.join(root,relative),{withFileTypes:true})){if(++count>5000||item.isSymbolicLink()||!/^[-a-zA-Z0-9_.]+$/.test(item.name)||item.name.startsWith('.')||/^(?:api|admin|private|internal|login|auth|robots\.txt|sitemap\.xml|.*(?:secret|credential|\.env).*)$/i.test(item.name))fail('static assets could shadow private or CMS routes');const filename=relative+'/'+item.name;if(item.isDirectory())await walk(filename,depth+1);else{const info=await lstat(path.join(root,filename));if(!info.isFile()||info.nlink!==1||! /\.(?:css|js|mjs|map|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|pdf|txt|webmanifest|mp[34]|webm|ogg)$/i.test(item.name))fail('static HTML, extensionless or non-asset files');}}
21
+ };await walk(directory);
22
+ }
23
+ function configReview(source,filename) {
24
+ if(typeof source!=='string')fail('missing Vite configuration');
25
+ const file=ts.createSourceFile(filename,source,ts.ScriptTarget.Latest,true,ts.ScriptKind.TS);if(file.parseDiagnostics.length)fail('configuration syntax');
26
+ const imports=new Map();let config;
27
+ for(const statement of file.statements){
28
+ if(ts.isImportDeclaration(statement)&&ts.isStringLiteral(statement.moduleSpecifier)){
29
+ const module=statement.moduleSpecifier.text,clause=statement.importClause;
30
+ if(['@sveltejs/adapter-node','@sveltejs/adapter-auto'].includes(module)&&clause?.name&&!clause.namedBindings)imports.set(clause.name.text,'adapter');
31
+ else if(['vite','@sveltejs/kit/vite'].includes(module)&&!clause?.name&&clause?.namedBindings&&ts.isNamedImports(clause.namedBindings)&&clause.namedBindings.elements.length===1){const item=clause.namedBindings.elements[0],expected=module==='vite'?'defineConfig':'sveltekit';if((item.propertyName?.text??item.name.text)!==expected)fail('custom configuration import');imports.set(item.name.text,expected);}
32
+ else fail('custom configuration import');continue;
33
+ }
34
+ if(ts.isExportAssignment(statement)&&ts.isCallExpression(statement.expression)&&imports.get(statement.expression.expression.getText(file))==='defineConfig'&&statement.expression.arguments.length===1){config=statement.expression.arguments[0];continue;}
35
+ if(!ts.isEmptyStatement(statement))fail('custom configuration code');
36
+ }
37
+ if(!config||!ts.isObjectLiteralExpression(config)||config.properties.length!==1)fail('custom Vite configuration');
38
+ const plugins=config.properties[0];if(!ts.isPropertyAssignment(plugins)||plugins.name.getText(file)!=='plugins'||!ts.isArrayLiteralExpression(plugins.initializer)||plugins.initializer.elements.length!==1)fail('custom Vite plugins');
39
+ const kit=plugins.initializer.elements[0];if(!ts.isCallExpression(kit)||imports.get(kit.expression.getText(file))!=='sveltekit'||kit.arguments.length!==1||!ts.isObjectLiteralExpression(kit.arguments[0])||kit.arguments[0].properties.length!==1)fail('custom Kit options');
40
+ const adapter=kit.arguments[0].properties[0];if(!ts.isPropertyAssignment(adapter)||adapter.name.getText(file)!=='adapter'||!ts.isCallExpression(adapter.initializer)||imports.get(adapter.initializer.expression.getText(file))!=='adapter'||adapter.initializer.arguments.length)fail('ordinary Node adapter required');
41
+ }
42
+ export async function inspectSvelteProject(directory){
43
+ if(!supportsNativeNode('svelte',process.versions.node))fail('Node.js 22.17 or newer is required before setup');
44
+ const root=await projectRoot(directory),manifest=JSON.parse(await svelteRead(root,'package.json')??'null'),deps={...manifest?.devDependencies,...manifest?.dependencies};
45
+ for(const [name,major,minor,patch=0] of [['@sveltejs/kit',3,1],['svelte',5,57,1],['@sveltejs/adapter-node',6,0],['@sveltejs/vite-plugin-svelte',7,3],['vite',8,0,12],['typescript',6,0,2]]){
46
+ if(!svelteVersion(deps[name],major,minor,patch))fail(`requires declared ${name} ${major}.${minor}+`);
47
+ const installed=JSON.parse(await svelteRead(root,`node_modules/${name}/package.json`)??'null');if(installed?.name!==name||!svelteVersion(installed.version,major,minor,patch))fail(`install compatible ${name}`);
48
+ }
49
+ if(manifest.workspaces||['next','nuxt','astro','@angular/core','vue'].some(name=>deps[name]))fail('competing frameworks or workspace');
50
+ const config='';
51
+ for(const name of ['svelte.config.js','svelte.config.ts','svelte.config.mjs','svelte.config.cjs','src/hooks.js','src/hooks.ts','src/hooks.client.js','src/hooks.client.ts','src/hooks.server.ts','src/service-worker.js','src/service-worker.ts'])if(await svelteRead(root,name)!==null)fail('custom hooks or alternative configuration');
52
+ let viteFile=null,viteSource=null;for(const name of ['vite.config.js','vite.config.ts']){const text=await svelteRead(root,name);if(text!==null){if(viteFile)fail('multiple Vite configurations');viteFile=name;viteSource=text;}}
53
+ configReview(viteSource,viteFile);
54
+ await inspectSvelteAssets(root);
55
+ return {root,manifest,config,configFile:viteFile,viteFile,viteSource};
56
+ }
57
+ async function routes(root){const ownershipText=await svelteRead(root,SVELTE_MANIFEST),ownership=ownershipText?JSON.parse(ownershipText):null;const rows=[];let count=0;const walk=async(directory,segments=[])=>{const stat=await lstat(path.join(root,directory));if(stat.isSymbolicLink()||!stat.isDirectory()||await realpath(path.join(root,directory))!==path.join(root,directory))fail('linked route directory');for(const item of (await readdir(path.join(root,directory),{withFileTypes:true})).sort((a,b)=>a.name.localeCompare(b.name))){if(++count>1000||segments.length>10||item.isSymbolicLink())fail('route inventory budget or linked route');const filename=directory+'/'+item.name;
58
+ if(item.isDirectory()){if(item.name==='[...cmsPath]'){if(segments.length||!ownership?.publication)fail('unowned CMS catch-all route');const children=await readdir(path.join(root,filename),{withFileTypes:true});if(children.length!==2||children.some(child=>!child.isFile()||!['+page.svelte','+page.server.js'].includes(child.name)))fail('custom CMS catch-all files');for(const child of children){const file=filename+'/'+child.name,expected=ownership.files?.find(row=>row.path===file);if(!expected||contentDigest(await svelteRead(root,file))!==expected.sha256)fail('changed CMS catch-all');}continue;}if(!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(item.name)||isExcludedPublicationPath('/'+item.name))fail('dynamic/private route');await walk(filename,[...segments,item.name]);}
59
+ else if(item.name==='+page.svelte')rows.push({path:'/'+segments.join('/'),kind:'page',slug:segments.length?segments.join('/'):'home',sourceFile:filename});
60
+ else if(item.name!=='+page.server.js')fail('custom server/layout route');
61
+ }};await walk('src/routes');if(!rows.length||rows.some(r=>r.path==='/home')&&rows.some(r=>r.path==='/'))fail('missing or duplicate home identity');return rows;}
62
+ function pageTemplate(content){return `<script>\nimport { onMount } from 'svelte';\nimport { defineNativeWebsite } from '@universalcms/connect/native-website';\nlet { data } = $props();\nlet host;\nonMount(() => { defineNativeWebsite(); const cms=data.universalcms; host.cms={pathname:cms.pathname,scope:cms.scope,initialResult:cms.result,initialSettings:cms.settings,...(cms.publicInventory?{inventory:cms.publicInventory}:{})}; });\n</script>\n<universalcms-website bind:this={host}>${content}</universalcms-website>\n`;}
63
+ export async function inspectSvelteSource(directory,{locale='en',websiteOrigin}={}){
64
+ const root=await projectRoot(directory),list=await routes(root),registry=await readContentRegistry(root),prepared=[],entries=[],inventories={};
65
+ const chromeReview=await readNativeChromeReview(root,'svelte');
66
+ if(registry?.profile||registry?.routes.some(row=>!row.sourceFile.endsWith('.svelte')))fail('existing content belongs to another adapter');
67
+ for(const route of list){const current=await svelteRead(root,route.sourceFile),installed=registry?.routes.find(row=>row.path===route.path),original=installed?(await readFrozenContent(root,installed)).texts.original:current;
68
+ const allowNativeChrome=reviewedNativeChrome(chromeReview,route.sourceFile,original);
69
+ let extracted;
70
+ try {
71
+ extracted=await transformNativeTemplate({source:original,slug:route.slug,locale,websiteOrigin,framework:'svelte',allowNativeChrome});
72
+ } catch(error) {
73
+ if(error instanceof SetupError)fail('automatic content binding requires a supported literal Svelte template; review dynamic components, controls and content ownership before setup');
74
+ throw error;
75
+ }
76
+ extracted.source = pageTemplate(extracted.source);
77
+ if(installed&&(installed.sourceFile!==route.sourceFile||installed.locale!==locale||current!==extracted.source))fail('changed native content ownership');
78
+ prepared.push({...route,locale,current,original,installed,extracted});entries.push(extracted.entry);inventories[route.path]=extracted.inventory;
79
+ }
80
+ if(registry&&registry.routes.length!==list.length)fail('removed installed route');
81
+ const result={entries,routes:prepared.map(({path,kind,slug,locale})=>({path,kind,slug,locale})),snapshot:{version:1,entries,inventories},sourceFiles:[...prepared.map(row=>({path:row.sourceFile,sha256:contentDigest(row.current)})),...(chromeReview?[{path:chromeReview.path,sha256:chromeReview.sha256}]:[])],diagnostics:{discoveredPages:list.length,automaticPages:list.length,skippedPages:0,unsupportedRoutes:0,warnings:[]}};
82
+ inspections.set(result,{root,prepared,chromeReview,digest:contentDigest(json(result))});return result;
83
+ }
84
+ export async function planSvelteContent(directory,configuration,inspection){
85
+ const root=await projectRoot(directory),config=validatePublicationConfig(configuration);inspection??=await inspectSvelteSource(root,{locale:config.routes[0]?.locale,websiteOrigin:config.websiteOrigin});const state=inspections.get(inspection);
86
+ if(!state||state.root!==root||state.digest!==contentDigest(json(inspection))||json(inspection.routes)!==json(config.routes))fail('source/publication inspection changed');
87
+ const files=await nativeChromeObservation(root,state.chromeReview),rows=[];for(const item of state.prepared){if(await svelteRead(root,item.sourceFile)!==item.current)fail('source changed after inspection');
88
+ if(item.installed){const frozen=await readFrozenContent(root,item.installed);for(const name of ['original','native','inventory','source'])files.push({path:item.installed[name+'File'],content:frozen.texts[name],state:'unchanged'});rows.push(item.installed);continue;}
89
+ const token=contentDigest(item.path+item.locale).slice(0,20),originalFile=`.universalcms/content/${token}.original.txt`,nativeFile=`.universalcms/content/${token}.native.txt`,inventoryFile=`.universalcms/content/${token}.json`,source=item.extracted.source,inventory=json(item.extracted.inventory);
90
+ for(const file of [originalFile,nativeFile,inventoryFile])if(await svelteRead(root,file)!==null)fail('frozen content collision');
91
+ files.push({path:originalFile,content:item.original,state:'create'},{path:nativeFile,content:source,state:'create'},{path:inventoryFile,content:inventory,state:'create'},{path:item.sourceFile,content:source,state:'update',previous:item.current});
92
+ rows.push({...Object.fromEntries(['path','kind','slug','locale','sourceFile'].map(key=>[key,item[key]])),originalFile,nativeFile,inventoryFile,sourceHash:contentDigest(source),nativeHash:contentDigest(source),originalHash:contentDigest(item.original),inventoryHash:contentDigest(inventory)});
93
+ }
94
+ const registry=validateContentRegistry({version:1,routes:rows}),text=json(registry),previous=await svelteRead(root,CONTENT_BINDINGS_FILE);if(previous!==null&&previous!==text)fail('content registry changed');files.push({path:CONTENT_BINDINGS_FILE,content:text,state:previous===null?'create':'unchanged'});return{root,framework:'svelte',files,routes:rows,entries:inspection.entries,snapshot:inspection.snapshot};
95
+ }
96
+ export async function planSvelteIntegration(directory,{publication=false,sourceConfiguration}={}){
97
+ const project=await inspectSvelteProject(directory),{root}=project,manifestText=await svelteRead(root,SVELTE_MANIFEST),prior=manifestText?JSON.parse(manifestText):null;
98
+ if(prior?.publication)publication=true;
99
+ const app=await svelteRead(root,'src/app.html');if(!app||!app.includes('%sveltekit.head%')||!app.includes('%sveltekit.body%')||/<script\b|\son\w+\s*=/i.test(app))fail('custom application shell');
100
+ const appSource=prior?.originalApp??app;if(prior&&prior.originalAppHash!==contentDigest(appSource))fail('application ownership changed');
101
+ const appOutput=appSource.replace(/<body\b([^>]*)>/,(_m,attrs)=>`<body${attrs.replace(/\sdata-sveltekit-(?:reload|preload-data)(?:=(?:"[^"]*"|'[^']*'))?/g,'')} data-sveltekit-reload data-sveltekit-preload-data="false">`);
102
+ const hook=`import { createSvelteRuntime } from '@universalcms/connect/svelte-runtime';\nimport { renderNativeWebsite } from '@universalcms/connect/native-website-server';\nimport sourceConfiguration from '../.universalcms/source.config.json';\nimport snapshot from '../.universalcms/source.generated.json';\n${publication?"import configuration from '../.universalcms/publication.config.json';\nimport contentBindings from '../.universalcms/content.bindings.json';\n":''}const runtime=createSvelteRuntime({${publication?'configuration,contentBindings,':''}sourceConfiguration,snapshot,sourceRoot:process.cwd(),renderWebsite:renderNativeWebsite});\nexport const handle=runtime.handle;\n`;
103
+ const load=`export const prerender=false;\nexport const load=({locals})=>({universalcms:locals.universalcms});\n`;
104
+ const generated=[['src/hooks.server.js',hook],['src/app.html',appOutput],['.universalcms/svelte.vite.config.mjs',`import adapter from '@sveltejs/adapter-node';\nimport {sveltekit} from '@sveltejs/kit/vite';\nimport config from './source.config.json' with {type:'json'};\nconst port=Number(new URL(config.websiteOrigin).port||(config.websiteOrigin.startsWith('https:')?443:80));\nconst privateFiles={name:'universalcms-private-files',enforce:'pre',configureServer(server){server.middlewares.use((request,response,next)=>{try{const pathname=decodeURIComponent(new URL(request.url,'http://localhost').pathname);if(pathname.includes('%')||pathname.split('/').some(part=>['.universalcms','.git','.ssh','.aws'].includes(part)||/\\.(?:crt|pem|key)$/i.test(part)||part==='.env'||part.startsWith('.env.'))){response.statusCode=404;response.setHeader('Cache-Control','no-store');response.end('Not found');return;}}catch{response.statusCode=400;response.end('Invalid URL');return;}next();});}};\nexport default {plugins:[privateFiles,sveltekit({adapter:adapter(),paths:{origin:config.websiteOrigin}})],server:{port,strictPort:true,fs:{deny:['**/.env','**/.env.*','**/.universalcms/**','**/.git/**','**/.ssh/**','**/.aws/**','**/*.{crt,pem,key}']}},preview:{port,strictPort:true}};\n`],['.universalcms/svelte.serve.mjs',`import {fileURLToPath} from 'node:url';\nprocess.chdir(fileURLToPath(new URL('../',import.meta.url)));\nimport config from './source.config.json' with {type:'json'};\nprocess.env.ORIGIN=config.websiteOrigin;process.env.PORT=String(new URL(config.websiteOrigin).port||(config.websiteOrigin.startsWith('https:')?443:80));\nawait import('../build/index.js');\n`]];
105
+ if(publication){for(const row of await routes(root))generated.push([path.posix.dirname(row.sourceFile)+'/+page.server.js',load]);generated.push(['src/routes/[...cmsPath]/+page.svelte',pageTemplate('')],['src/routes/[...cmsPath]/+page.server.js',load]);}
106
+ const scripts={...project.manifest.scripts};for(const name of ['dev','build','preview']){const expected=name==='build'?'vite build':`vite ${name==='dev'?'':name}`.trim(),generated=expected+' --config .universalcms/svelte.vite.config.mjs';if(scripts[name]!==undefined&&scripts[name]!==expected&&scripts[name]!==generated)fail('custom startup/build scripts');if(name!=='preview'&&scripts[name]===undefined)fail('missing startup/build scripts');if(scripts[name]!==undefined)scripts[name]=generated;}
107
+ const start='node .universalcms/svelte.serve.mjs';if(scripts.start&&scripts.start!==start&&scripts.start!=='node build')fail('custom production startup');scripts.start=start;
108
+ const verify='universalcms-connect sync-source';for(const name of ['predev','prebuild','prestart','prepreview']){if(scripts[name]&&scripts[name]!==verify)fail('custom lifecycle hooks');scripts[name]=verify;}
109
+ const oldFiles=new Map((prior?.files??[]).map(item=>[item.path,item.sha256])),files=[];
110
+ if(prior&&(prior.version!==1||prior.configHash!==contentDigest(project.config)||prior.viteHash!==contentDigest(project.viteSource)))fail('framework configuration changed');
111
+ for(const [file,content]of generated){const before=await svelteRead(root,file),owned=oldFiles.get(file);if(file==='src/app.html'){if(before!==appSource&&before!==appOutput)fail('application shell changed');}else if(before!==null&&(!owned||contentDigest(before)!==owned))fail('custom runtime or load file');files.push({path:file,content,state:before===content?'unchanged':before===null?'create':'update',...(before!==null&&before!==content?{previous:before}:{})});}
112
+ files.push({path:project.viteFile,content:project.viteSource,state:'unchanged'});
113
+ const packageBefore=await svelteRead(root,'package.json'),packageAfter=json({...project.manifest,scripts});files.push({path:'package.json',content:packageAfter,state:packageBefore===packageAfter?'unchanged':'update',previous:packageBefore});
114
+ const ownership={version:1,publication,configHash:contentDigest(project.config),viteHash:contentDigest(project.viteSource),originalApp:appSource,originalAppHash:contentDigest(appSource),files:generated.map(([path,content])=>({path,sha256:contentDigest(content)}))};
115
+ files.push({path:SVELTE_MANIFEST,content:json(ownership),state:manifestText===json(ownership)?'unchanged':manifestText===null?'create':'update',...(manifestText?{previous:manifestText}:{})});
116
+ return{root,framework:'svelte',files,project};
117
+ }
118
+ export async function validateInstalledSvelte(directory,configuration){
119
+ const plan=await planSvelteIntegration(directory,{publication:true,sourceConfiguration:configuration});
120
+ if(plan.files.some(file=>file.state!=='unchanged'))fail('integration incomplete or changed');
121
+ const inspection=await inspectSvelteSource(directory,{locale:configuration.routes[0]?.locale,websiteOrigin:configuration.websiteOrigin});
122
+ if(json(inspection.routes)!==json(configuration.routes)||await svelteRead(plan.root,'.universalcms/source.generated.json')!==json(inspection.snapshot))fail('published route or frozen source inventory changed');
123
+ const registry=await readContentRegistry(plan.root);if(!registry||registry.routes.length!==configuration.routes.length)fail('published-content bindings incomplete');
124
+ const content=await planSvelteContent(directory,configuration,inspection);if(content.files.some(file=>file.state!=='unchanged'))fail('native content bindings incomplete');
125
+ plan.files.push(...content.files);return{...plan,config:configuration};
126
+ }
127
+ export async function planSvelteSettings(directory,{config,source}){
128
+ const plan=await validateInstalledSvelte(directory,config),name='.universalcms/website.integration.json';
129
+ const unique=new Map(plan.files.filter(file=>file.path!=='package.json').map(file=>[file.path,{path:file.path,sha256:contentDigest(file.content)}]));
130
+ const content=json({version:1,framework:'svelte',siteId:config.siteId,environmentId:config.environmentId,apiOrigin:config.apiOrigin,websiteOrigin:config.websiteOrigin,adminOrigin:source.adminOrigin,sections:WEBSITE_SECTIONS,files:[...unique.values()]}),previous=await svelteRead(plan.root,name);
131
+ if(previous!==null&&previous!==content)fail('settings ownership or scope changed');
132
+ plan.files.push({path:name,content,state:previous===null?'create':'unchanged'});
133
+ for(const name of ['.universalcms/publication.config.json','.universalcms/source.config.json','.universalcms/source.generated.json'])plan.files.push({path:name,content:await svelteRead(plan.root,name),state:'unchanged'});
134
+ return{...plan,sections:[...WEBSITE_SECTIONS]};
135
+ }
@@ -0,0 +1,48 @@
1
+ /** Literal native HTML projection shared by the Svelte and Angular adapters.
2
+ * The bundled parser validates inert source; customer modules are never loaded. */
3
+ import { parse } from './vendor/astro-compiler/node/index.js';
4
+ import { extractAstroPage } from './astro-source.mjs';
5
+ import { SetupError } from './core.mjs';
6
+ const fail = () => { throw new SetupError('Native template requires a reviewed adapter. Automatic setup accepts literal editorial HTML and styles; custom components, expressions, scripts and chrome are preserved for review.'); };
7
+ const escape = value => JSON.stringify(value);
8
+ export async function transformNativeTemplate({source,slug,locale='en',websiteOrigin,framework,allowNativeChrome=false}) {
9
+ if (!['svelte','angular'].includes(framework) || typeof source !== 'string' || /[{}]|<(?:script|svelte:|ng-|router-|slot\b)|\s(?:on\w+|\[[^\]]+\]|\([^)]*\)|\*\w+)\s*=/i.test(source.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi,''))) fail();
10
+ const extracted = await extractAstroPage({source,slug,locale,websiteOrigin,allowNativeChrome});
11
+ const parsed = await parse(source,{position:true}), edits=[]; let cursor=0;
12
+ const offsets=new Map([[0,0]]); let bytes=0,chars=0;
13
+ for(const character of source){bytes+=Buffer.byteLength(character);chars+=character.length;offsets.set(bytes,chars);}
14
+ const start=node=>{let at=offsets.get(node.position?.start?.offset);if(source[at]!=='<'&&source[at-1]==='<')at--;if(!Number.isInteger(at)||!source.startsWith('<'+node.name,at))fail();return at;};
15
+ const opening=node=>{const at=start(node);let quote=null;for(let i=at;i<source.length;i++){const c=source[i];if(quote){if(c===quote)quote=null;}else if(c==='"'||c==="'")quote=c;else if(c==='>')return i+1;}fail();};
16
+ const end=node=>node.name==='img'?opening(node):offsets.get(node.position?.end?.offset);
17
+ const visit=node=>{
18
+ if(node.type!=='element'){for(const child of node.children??[])visit(child);return;}
19
+ if(node.name==='style'||node.name==='head'||extracted.inventory.nativeChrome&&['header','footer'].includes(node.name))return;
20
+ if(!/^(?:h[1-6]|p|ul|ol|img)$/.test(node.name)){for(const child of node.children??[])visit(child);return;}
21
+ const block=extracted.entry.document.blocks[cursor++];if(!block)fail();
22
+ const from=start(node),to=end(node);if(!Number.isInteger(to)||to<=from)fail();
23
+ const lookup=framework==='svelte'?`data.universalcms.bindings[${escape(block.id)}]`:`cms.binding(${escape(block.id)})`;
24
+ let native=source.slice(from,to),open=source.slice(from,opening(node)),close=node.name==='img'?'':`</${node.name}>`;
25
+ if(block.type==='image') {
26
+ open=open.replace(/\s(?:src|alt)\s*=\s*(?:"[^"]*"|'[^']*')/g,'');
27
+ const attrs=framework==='svelte'?` src={${lookup}.url} alt={${lookup}.alt}`:` [src]="${lookup.replaceAll('"',"'")}!.url" [alt]="${lookup.replaceAll('"',"'")}!.alt"`;
28
+ native=open.replace(/\s*\/?>$/,attrs+' />');
29
+ } else if(block.type==='list') {
30
+ const first=node.children.find(child=>child.type==='element'&&child.name==='li'),itemOpen=source.slice(start(first),opening(first));
31
+ const item=framework==='svelte'?`{#each ${lookup}.text.split('\\n') as item}${itemOpen}{item}</li>{/each}`:`@for (item of ${lookup.replaceAll('"',"'")}!.text.split('\\n'); track $index) {${itemOpen}{{item}}</li>}`;
32
+ native=open+item+close;
33
+ } else {
34
+ // Native wrappers retain their order. Editorial text is projected once;
35
+ // no frozen source copy is emitted into the browser bundle.
36
+ const value=framework==='svelte'?`{${lookup}.text}`:`{{${lookup.replaceAll('"',"'")}!.text}}`;
37
+ const replacements=[];let first=true;
38
+ const visitText=child=>{if(child.type==='text'&&child.value.trim()){const at=offsets.get(child.position?.start?.offset),to=offsets.get(child.position?.end?.offset);if(!Number.isInteger(at)||!Number.isInteger(to)||source.slice(at,to)!==child.value)fail();replacements.push({from:at-from,to:to-from,value:first?value:''});first=false;}else for(const nested of child.children??[])visitText(nested);};
39
+ for(const child of node.children??[])visitText(child);
40
+ if(first)fail();
41
+ for(const change of replacements.sort((a,b)=>b.from-a.from))native=native.slice(0,change.from)+change.value+native.slice(change.to);
42
+ }
43
+ edits.push({from,to,value:framework==='svelte'?`{#if ${lookup}}${native}{/if}`:`@if (${lookup.replaceAll('"',"'")}) {${native}}`});
44
+ };
45
+ visit(parsed.ast);if(cursor!==extracted.entry.document.blocks.length)fail();
46
+ let output=source;for(const edit of edits.sort((a,b)=>b.from-a.from))output=output.slice(0,edit.from)+edit.value+output.slice(edit.to);
47
+ return {...extracted,source:output};
48
+ }
@@ -5,7 +5,7 @@ export async function createTemplates(config, { existingNodeServer = false } = {
5
5
  const env = `# Set these in your PRIVATE hosting/server environment. This file contains no key.\nCMS_API_BASE_URL=${config.apiOrigin}\nCMS_SITE_ID=${config.siteId}\nCMS_ENVIRONMENT_ID=${config.environmentId}\nCMS_API_KEY=\n${config.framework === 'vite' ? 'CMS_WEBSITE_ORIGIN=http://localhost:5173\nCMS_CONNECT_PORT=4318\nCMS_CONNECT_HOST=127.0.0.1\n' : ''}`;
6
6
  const common = [
7
7
  { path: '.universalcms/server.env.example', content: env },
8
- { path: '.universalcms/SETUP.md', content: instructions(config, existingNodeServer) },
8
+ { path: '.universalcms/SETUP.md', content: stagedSetupGuide(instructions(config, existingNodeServer)) },
9
9
  ];
10
10
  const clientOptions = `baseUrl: process.env.CMS_API_BASE_URL ?? ${json(config.apiOrigin)},\n siteId: ${json(config.siteId)},\n environmentId: ${json(config.environmentId)},\n apiKey: process.env.CMS_API_KEY,\n retries: 0,`;
11
11
  if (config.framework === 'next') {
@@ -21,16 +21,180 @@ export async function createTemplates(config, { existingNodeServer = false } = {
21
21
  { path: '.universalcms/page-handler.mjs', content: handler },
22
22
  { path: '.universalcms/server.mjs', content: `import { createServer } from 'node:http';\nimport { createCmsClient } from '@universalcms/sdk';\nimport { createPageHandler } from './page-handler.mjs';\n\ntry {\n const cms = createCmsClient({\n ${clientOptions}\n });\n const port = Number(process.env.CMS_CONNECT_PORT ?? '4318');\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw Error();\n const handler = createPageHandler(cms, { locale: ${json(config.locale)}, websiteOrigin: process.env.CMS_WEBSITE_ORIGIN });\n const server = createServer({ requestTimeout: 15_000, headersTimeout: 10_000, maxHeaderSize: 8192 }, (req, res) => { void handler(req, res); });\n server.on('error', () => { console.error('CMS connector could not start. Check server configuration.'); process.exitCode = 1; });\n server.listen(port, process.env.CMS_CONNECT_HOST ?? '127.0.0.1', () => console.log('CMS connector listening. Route /api/universalcms/page through your website proxy.'));\n for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => { server.close(); server.closeAllConnections(); });\n} catch { console.error('CMS connector requires valid private server configuration.'); process.exitCode = 1; }\n` },
23
23
  { path: '.universalcms/CmsPage.jsx', content: `import React, { useEffect, useState } from 'react';\nimport { CmsDocument } from '@universalcms/react';\nimport '@universalcms/react/styles.css';\n\n// Mount in your existing router; this module contains no CMS credentials.\nexport default function CmsPage({ slug = 'home' }) {\n const [result, setResult] = useState({ slug: '', state: 'loading', entry: null });\n useEffect(() => {\n const controller = new AbortController();\n let current = true;\n setResult({ slug, state: 'loading', entry: null });\n async function load() {\n try {\n const response = await fetch('/api/universalcms/page?slug=' + encodeURIComponent(slug), { credentials: 'omit', cache: 'no-store', redirect: 'error', signal: controller.signal });\n if (response.status === 404) { if (current) setResult({ slug, state: 'missing', entry: null }); return; }\n if (!response.ok || !response.headers.get('content-type')?.startsWith('application/json')) throw Error();\n const body = await response.json();\n if (!body.entry?.document || typeof body.entry.document.title !== 'string' || !Array.isArray(body.entry.document.blocks)) throw Error();\n if (current) setResult({ slug, state: 'ready', entry: body.entry });\n } catch { if (current) setResult({ slug, state: 'error', entry: null }); }\n }\n void load();\n return () => { current = false; controller.abort(); };\n }, [slug]);\n if (result.slug !== slug || result.state === 'loading') return <p role="status">Loading content…</p>;\n if (result.state === 'missing') return <h1>Page not found</h1>;\n if (result.state === 'error') return <p role="alert">Content is temporarily unavailable.</p>;\n return <article><h1>{result.entry.document.title}</h1><CmsDocument document={result.entry.document} entryId={result.entry.id} /></article>;\n}\n` },
24
- { path: '.universalcms/vite.config.mjs', content: `import { defineConfig, loadConfigFromFile } from 'vite';\n\n// Explicit wrapper: preserves the project's config without editing it.\n// Run only when you choose: npx vite --config .universalcms/vite.config.mjs\nexport default defineConfig(async env => {\n const existing = (await loadConfigFromFile(env, undefined, process.cwd()))?.config ?? {};\n const proxies = existing.server?.proxy ?? {};\n const endpoint = '^/api/universalcms/page(?:[?]|$)';\n if (Object.hasOwn(proxies, '/api/universalcms/page') || Object.hasOwn(proxies, endpoint)) throw new Error('The CMS proxy endpoint already exists. Review it manually before using this wrapper.');\n // Vite selects the first matching proxy. The exact CMS endpoint must precede\n // an existing broad /api proxy; all unrelated routes retain their destinations.\n return { ...existing, server: { ...existing.server, proxy: { [endpoint]: { target: 'http://127.0.0.1:4318', changeOrigin: false }, ...proxies } } };\n});\n` },
24
+ { path: '.universalcms/vite.config.mjs', content: sourceAwareViteWrapper(`import { defineConfig, loadConfigFromFile } from 'vite';\n\n// Explicit wrapper: preserves the project's config without editing it.\n// Run only when you choose: npx vite --config .universalcms/vite.config.mjs\nexport default defineConfig(async env => {\n const existing = (await loadConfigFromFile(env, undefined, process.cwd()))?.config ?? {};\n const proxies = existing.server?.proxy ?? {};\n const endpoint = '^/api/universalcms/page(?:[?]|$)';\n if (Object.hasOwn(proxies, '/api/universalcms/page') || Object.hasOwn(proxies, endpoint)) throw new Error('The CMS proxy endpoint already exists. Review it manually before using this wrapper.');\n // Vite selects the first matching proxy. The exact CMS endpoint must precede\n // an existing broad /api proxy; all unrelated routes retain their destinations.\n return { ...existing, server: { ...existing.server, proxy: { [endpoint]: { target: 'http://127.0.0.1:4318', changeOrigin: false }, ...proxies } } };\n});\n`) },
25
25
  { path: 'universalcms-demo.html', content: '<!doctype html>\n<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CMS connection demo</title><meta name="robots" content="noindex,nofollow"></head><body><div id="universalcms-demo"></div><script type="module" src="/.universalcms/demo.jsx"></script></body></html>\n' },
26
26
  { path: '.universalcms/demo.jsx', content: `import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport CmsPage from './CmsPage.jsx';\nconst slug = new URLSearchParams(window.location.search).get('slug') || 'home';\ncreateRoot(document.getElementById('universalcms-demo')).render(<CmsPage slug={slug} />);\n` },
27
27
  ];
28
28
  }
29
29
 
30
+ // The optional demo wrapper must retain original-route publication protection
31
+ // after full setup. Switching to this documented wrapper cannot bypass the gate.
32
+ const sourceImport = "import { readFile } from 'node:fs/promises';\n";
33
+ const legacySourceStartup = ` let sourcePlugins = [];
34
+ try {
35
+ const configuration = JSON.parse(await readFile(new URL('./source.config.json', import.meta.url), 'utf8'));
36
+ const { universalCmsSource } = await import('@universalcms/connect/vite-source');
37
+ sourcePlugins = [universalCmsSource({ configuration })];
38
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
39
+ `;
40
+ const sourceStartup = ` const sourcePlugins = [];
41
+ let publicationText;
42
+ try { publicationText = await readFile(new URL('./publication.config.json', import.meta.url), 'utf8'); }
43
+ catch (error) {
44
+ if (error.code !== 'ENOENT') throw error;
45
+ for (const filename of ['./source.vite.config.mjs', './serve.mjs']) {
46
+ try {
47
+ const source = await readFile(new URL(filename, import.meta.url), 'utf8');
48
+ if (/@universalcms\\/connect\\/vite-publication|universalcms-publication-client/.test(source)) throw new Error('Original-route publication configuration is missing. Restore the reviewed setup before starting the website.');
49
+ } catch (inspectionError) { if (inspectionError.code !== 'ENOENT') throw inspectionError; }
50
+ }
51
+ }
52
+ if (publicationText !== undefined) {
53
+ const configuration = JSON.parse(publicationText);
54
+ const sourceConfiguration = JSON.parse(await readFile(new URL('./source.config.json', import.meta.url), 'utf8'));
55
+ let contentBindings;
56
+ try { contentBindings = JSON.parse(await readFile(new URL('./content.bindings.json', import.meta.url), 'utf8')); }
57
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
58
+ const { universalCmsPublication } = await import('@universalcms/connect/vite-publication');
59
+ sourcePlugins.push(universalCmsPublication({ configuration, sourceConfiguration, contentBindings }));
60
+ }
61
+ try {
62
+ const configuration = JSON.parse(await readFile(new URL('./source.config.json', import.meta.url), 'utf8'));
63
+ const { universalCmsSource } = await import('@universalcms/connect/vite-source');
64
+ sourcePlugins.push(universalCmsSource({ configuration }));
65
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
66
+ `;
67
+ function sourceAwareViteWrapper(content) {
68
+ return sourceImport + content.replace(' const proxies =', sourceStartup + ' const proxies =')
69
+ .replace('return { ...existing, server:', 'return { ...existing, plugins: [...sourcePlugins, ...(existing.plugins ?? [])], server:');
70
+ }
71
+ export function previousViteSourceWrapper(content) {
72
+ return content.replace(sourceImport, '').replace(sourceStartup, '')
73
+ .replace('return { ...existing, plugins: [...sourcePlugins, ...(existing.plugins ?? [])], server:', 'return { ...existing, server:');
74
+ }
75
+ export function previousPublicationViteWrapper(content) {
76
+ return content.replace(sourceStartup, legacySourceStartup);
77
+ }
78
+ export function previousDiagnosticViteWrapper(content) {
79
+ return content.replace(" const sourceConfiguration = JSON.parse(await readFile(new URL('./source.config.json', import.meta.url), 'utf8'));\n let contentBindings;\n try { contentBindings = JSON.parse(await readFile(new URL('./content.bindings.json', import.meta.url), 'utf8')); }\n catch (error) { if (error.code !== 'ENOENT') throw error; }\n", '')
80
+ .replace('universalCmsPublication({ configuration, sourceConfiguration, contentBindings })', 'universalCmsPublication({ configuration })');
81
+ }
82
+
30
83
  // Exact prior prerelease guide variant, used only to recognize an untouched
31
84
  // generated document during guided upgrades. Custom bytes still fail closed.
85
+ const priorSetupInstruction = 'Run npx --no-install universalcms-connect setup to enter and save connection values locally.';
86
+ const stagedSetupInstruction = 'After connect and setup, run npx --no-install universalcms-connect server-key to save the delivery key through hidden input. If this project has no saved connect record, setup retains its all-in-one guided flow.';
87
+ const priorPrivateInstruction = 'Guided setup saves the entered values in private .env.local.';
88
+ const stagedPrivateInstruction = 'The server-key command (or the legacy all-in-one setup flow) saves entered values in private .env.local.';
89
+ const legacyPublicationGuide = `## Connect publication to the original website URLs
90
+
91
+ Source export and importing drafts do not hide the original website content.
92
+ The scaffold does not connect existing routes to CMS publication automatically.
93
+ Review the imported public route inventory and map each original URL to its exact
94
+ CMS kind, slug and locale in the fixed site/environment. Map the home URL explicitly
95
+ (for example, / to the imported home slug); do not guess from titles or route names.
96
+ Exclude private/account, API, admin, preview and framework paths from this mapping.
97
+
98
+ For every reviewed managed public route, check published delivery before rendering
99
+ any original page content. A missing or unpublished page must return 404; a failed
100
+ delivery request must show an unavailable response without original or stale
101
+ content. When all managed pages are unpublished, all their original URLs must be
102
+ unavailable, including the home page. Never use draft content as a fallback.
103
+ Publication checks are independent of body/design mapping: preserve existing
104
+ components and interactions behind the check while mapping CMS blocks deliberately.
105
+ A source-only setup still needs this integration before publication governs pages.
106
+
107
+ Use fixtures or a disposable staging site to verify published, all-unpublished and
108
+ publish-to-unpublish transitions on the original URLs, including direct requests,
109
+ client navigation, reload and cached/prefetched routes. Test upstream failures too.
110
+ Do not publish or unpublish customer content merely to test. For static hosting,
111
+ verify the required rebuild, cache invalidation and deployment before claiming the
112
+ URLs changed. A successful API check or demo route does not verify original pages.
113
+ Report unmanaged public routes and unfinished deployments explicitly. Existing
114
+ public source exports and assets are separate from page-rendering checks.
115
+
116
+ `;
117
+ const availabilityGuide = `## Connect publication to the original website URLs
118
+
119
+ Source export and importing drafts do not hide the original website content.
120
+ The full universalcms-connect setup command separately installs publication checks
121
+ for its reviewed original URL mappings. Confirm the printed kind, slug and locale,
122
+ including / for the home page, before saving. Custom mappings belong in
123
+ .universalcms/publication.routes.json; rerun setup after adding public routes.
124
+ Exclude private/account, API, admin, preview and framework paths from this mapping.
125
+
126
+ After full setup, use your normal dev and preview commands. For a supported Vite
127
+ website, run your normal build command and then npm start (or your package-manager
128
+ equivalent) to serve the build with the generated private Node publication server.
129
+ Deploy that server with its private environment. A static-only host cannot enforce
130
+ the request-time publication checks. Next.js keeps its ordinary dev/build/start
131
+ commands and uses the generated server proxy before HTML and RSC responses.
132
+
133
+ The preceding /cms or universalcms-demo instructions apply to standalone init or
134
+ source-only installations. After full setup, verify the reviewed original URLs;
135
+ unmapped demo page URLs return 404. Their standalone page API is not the original
136
+ website's production host. The optional Vite wrapper retains installed original-
137
+ route publication checks and refuses to start when their configuration is missing.
138
+
139
+ A missing or unpublished page returns 404; failed delivery returns 503 without
140
+ original or stale content. When all managed pages are unpublished, all their original URLs must be
141
+ unavailable, including the home page. Never use draft content as a fallback.
142
+ Publication checks are independent of body/design mapping: published pages retain
143
+ their native components. CMS document edits need deliberate component mapping.
144
+ Source-only setup and unsupported custom servers still require manual integration.
145
+
146
+ Use fixtures or a disposable staging site to verify published, all-unpublished and
147
+ publish-to-unpublish transitions on the original URLs, including direct requests,
148
+ client navigation, reload and cached/prefetched routes. Test upstream failures too.
149
+ Do not publish or unpublish customer content merely to test. A successful API check
150
+ or demo route does not verify original pages. Report unmanaged public routes and
151
+ unfinished deployments explicitly. Existing public source exports and assets are
152
+ separate from page-rendering checks.
153
+
154
+ `;
155
+ const publicationGuide = availabilityGuide.replace(
156
+ 'Publication checks are independent of body/design mapping: published pages retain\ntheir native components. CMS document edits need deliberate component mapping.\nSource-only setup and unsupported custom servers still require manual integration.',
157
+ `Full setup also binds supported native JSX/TSX components to the current published
158
+ document. Existing block IDs and source baselines stay frozen across reconnects and
159
+ builds. Edited and deleted blocks update in place; new blocks appear after the
160
+ bound native content. Source-only setup and unsupported custom components require
161
+ an explicit integration through @universalcms/connect/react-publication.
162
+
163
+ Vite routes need reviewed path/sourceFile pairs in .universalcms/content.routes.json
164
+ unless a single homepage maps unambiguously to src/App.jsx or src/App.tsx. The
165
+ entire source block structure must match before imported IDs can be reused.
166
+ Native layout positions and interactions remain in their components. Arbitrary
167
+ layout reordering and private/runtime application data are not inferred.
168
+
169
+ Save draft leaves the published revision unchanged. Update live / Publish changes
170
+ public content. Open tabs check every 15 seconds and on focus; a published revision
171
+ refreshes bound content without resetting native state. Missing or failed reads
172
+ remove stale managed content. Test published A, saved draft B, then published B on
173
+ the original URL, including removed blocks and a published empty document.
174
+
175
+ Existing installations must upgrade this package, rerun setup and deploy the
176
+ updated website code and private runtime settings to each live host. A local
177
+ setup does not deploy remote websites. Verify actual changed page text separately
178
+ on local and live origins; an API or source-import check cannot prove rendering.`
179
+ );
180
+ export function previousRenderingSetupGuide(content) {
181
+ return content.replace(publicationGuide, availabilityGuide);
182
+ }
183
+ function stagedSetupGuide(content) {
184
+ return content.replace(priorSetupInstruction, stagedSetupInstruction).replace(priorPrivateInstruction, stagedPrivateInstruction)
185
+ .replace('## What remains deliberate', publicationGuide + '## What remains deliberate');
186
+ }
187
+ export function priorPublicationSetupGuide(content) {
188
+ return content.replace(publicationGuide, '');
189
+ }
190
+ export function previousPublicationSetupGuide(content) {
191
+ return content.replace(publicationGuide, legacyPublicationGuide);
192
+ }
193
+ export function previousSetupGuide(content) {
194
+ return priorPublicationSetupGuide(content).replace(stagedSetupInstruction, priorSetupInstruction).replace(stagedPrivateInstruction, priorPrivateInstruction);
195
+ }
32
196
  export function legacySetupGuide(content) {
33
- return content
197
+ return previousSetupGuide(content)
34
198
  .replace('Guided setup saves the entered values in private .env.local. The analyze, init and check commands do not load that file automatically.', 'The CLI neither saves nor reads an .env file.')
35
199
  .replace('node --env-file=.env.local .universalcms/server.mjs', 'node .universalcms/server.mjs')
36
200
  .replace('Run npx --no-install universalcms-connect setup to enter and save connection values locally. For hosted deployment, copy the nonsecret values in server.env.example into the private server/hosting', 'Copy the nonsecret values in server.env.example into the private server/hosting')
@@ -0,0 +1,53 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 [Astro contributors](https://github.com/withastro/compiler/graphs/contributors)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ """
24
+ This license applies to parts of the `internal/` subdirectory originating from
25
+ the https://cs.opensource.google/go/x/net/+/master:html/ repository:
26
+
27
+ Copyright (c) 2009 The Go Authors. All rights reserved.
28
+
29
+ Redistribution and use in source and binary forms, with or without
30
+ modification, are permitted provided that the following conditions are
31
+ met:
32
+
33
+ * Redistributions of source code must retain the above copyright
34
+ notice, this list of conditions and the following disclaimer.
35
+ * Redistributions in binary form must reproduce the above
36
+ copyright notice, this list of conditions and the following disclaimer
37
+ in the documentation and/or other materials provided with the
38
+ distribution.
39
+ * Neither the name of Google Inc. nor the names of its
40
+ contributors may be used to endorse or promote products derived from
41
+ this software without specific prior written permission.
42
+
43
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
45
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
46
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
47
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
49
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
50
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
51
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
53
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -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};
@@ -91,15 +91,15 @@ function tokens(source) {
91
91
  if (c === '/' && next === '*') { const end = source.indexOf('*/', i + 2); i = end < 0 ? source.length : end + 2; continue; }
92
92
  if (c === '/' && (!result.length || ['=', '(', ':', ',', '[', '{', '!', '?', ';', '>', 'return', 'case', 'yield'].includes(result.at(-1)?.value))) {
93
93
  // A regexp may contain quotes, comment-like text and fake source tokens.
94
- let inClass = false;
94
+ let inClass = false, closed = false;
95
95
  for (i++; i < source.length; i++) {
96
96
  if (source[i] === '\\') { i++; continue; }
97
97
  if (source[i] === '[') inClass = true;
98
98
  if (source[i] === ']') inClass = false;
99
- if (source[i] === '/' && !inClass) { i++; while (/[a-z]/i.test(source[i] ?? '') && i < source.length) i++; break; }
99
+ if (source[i] === '/' && !inClass) { closed = true; i++; while (/[a-z]/i.test(source[i] ?? '') && i < source.length) i++; break; }
100
100
  if (source[i] === '\n' || source[i] === '\r') break;
101
101
  }
102
- result.push({ type: 'dynamic', value: '' }); continue;
102
+ result.push({ type: closed ? 'regexp' : 'dynamic', value: '' }); continue;
103
103
  }
104
104
  if (c === '`') {
105
105
  i = skipTemplate(source, i);
@@ -119,6 +119,10 @@ function tokens(source) {
119
119
  while (i < source.length && /[A-Za-z0-9_$]/.test(source[i])) i++;
120
120
  result.push({ type: 'name', value: source.slice(start, i) }); continue;
121
121
  }
122
+ if (/\d/.test(c)) {
123
+ const value = source.slice(i).match(/^\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/)[0];
124
+ result.push({ type: 'number', value }); i += value.length; continue;
125
+ }
122
126
  result.push({ type: 'punctuation', value: c }); i++;
123
127
  }
124
128
  return result;
@@ -192,9 +196,59 @@ function literalConfig(source, wrapper) {
192
196
  }
193
197
  if (begin < 0) return { literals: {}, properties: {}, opaque: true, list };
194
198
  if (wrapper && list[begin]?.value === wrapper && list[begin + 1]?.value === '(') begin += 2;
199
+ let aliasEnd = null;
200
+ // create-next-app emits a typed const followed by `export default config`
201
+ // (or `module.exports = config` in its JavaScript template). Resolve that one
202
+ // inert alias, never a function call, mutable assignment, imported runtime
203
+ // value, second declaration or conditional configuration.
204
+ const commonjs = list[begin - 1]?.value === '=' && list[begin - 2]?.value === 'exports' && list[begin - 3]?.value === '.' && list[begin - 4]?.value === 'module';
205
+ if (!wrapper && list[begin]?.type === 'name' && (list[begin - 1]?.value === 'default' || commonjs)) {
206
+ const name = list[begin].value, declaration = list.findIndex(token => token.value === 'const');
207
+ const prefix = list.slice(0, declaration);
208
+ const typeImport = prefix.length === 0 || prefix[0]?.value === 'import' && prefix[1]?.value === 'type' && prefix.filter(token => token.value === 'import').length === 1 && prefix.filter(token => token.value === 'from').length === 1 && prefix.at(-1)?.value === ';' && prefix.at(-2)?.type === 'string' && prefix.at(-2)?.value === 'next' && prefix.at(-3)?.value === 'from' && prefix.slice(2,-3).every(token => token.type === 'name' || ['{','}',','].includes(token.value));
209
+ let initial = declaration + 2;
210
+ if (list[initial]?.value === ':') {
211
+ initial++;
212
+ // Ordinary type names only. Types are erased, not executed.
213
+ if (list[initial]?.type !== 'name') initial = -1;
214
+ else { initial++; while (list[initial]?.value === '.' && list[initial + 1]?.type === 'name') initial += 2; }
215
+ }
216
+ const tail = list.slice(begin + 1);
217
+ if (declaration < 0 || !typeImport || list[declaration + 1]?.value !== name || list[initial]?.value !== '=' || list[initial + 1]?.value !== '{' || tail.some(token => token.value !== ';')) return { literals: {}, properties: {}, opaque: true, list };
218
+ aliasEnd = commonjs ? begin - 4 : begin - 2;
219
+ begin = initial + 1;
220
+ }
195
221
  if (list[begin]?.value !== '{') return { literals: {}, properties: {}, opaque: true, list };
196
222
  const parsed = readObject(list, begin), literals = {};
197
223
  let opaque = parsed.opaque;
224
+ if (aliasEnd !== null) {
225
+ const literal = (expression, key, depth = 0) => {
226
+ if (depth > 24 || !expression.length) return false;
227
+ if (expression.length === 1) return ['string','number'].includes(expression[0].type) || ['true','false','null'].includes(expression[0].value) || key === 'htmlLimitedBots' && expression[0].type === 'regexp';
228
+ if (expression.length === 2 && ['-','+'].includes(expression[0].value) && expression[1].type === 'number') return true;
229
+ if (expression[0]?.value === '{') {
230
+ const object = readObject(expression,0,depth + 1);
231
+ return !object.opaque && object.end === expression.length - 1 && Object.entries(object.properties).every(([name, property]) => literal(property.expression,name,depth + 1));
232
+ }
233
+ if (expression[0]?.value === '[' && expression.at(-1)?.value === ']') {
234
+ let start = 1, nesting = 0;
235
+ for (let index = 1; index < expression.length; index++) {
236
+ const value = expression[index].value;
237
+ if (index === expression.length - 1 || value === ',' && nesting === 0) {
238
+ if (start !== index && !literal(expression.slice(start,index),key,depth + 1)) return false;
239
+ if (start === index && index !== expression.length - 1) return false;
240
+ start = index + 1;
241
+ } else if (['{','['].includes(value)) nesting++;
242
+ else if (['}',']'].includes(value)) nesting--;
243
+ else if (['(',')'].includes(value)) return false;
244
+ if (nesting < 0) return false;
245
+ }
246
+ return nesting === 0;
247
+ }
248
+ return false;
249
+ };
250
+ if (list.slice(parsed.end + 1,aliasEnd).some(token => token.value !== ';') || !Object.entries(parsed.properties).every(([name, property]) => literal(property.expression,name))) opaque = true;
251
+ }
198
252
  for (const key of ['output', 'ssr', 'preset']) {
199
253
  const expression = parsed.properties[key]?.expression;
200
254
  if (!expression) continue;
@@ -202,7 +256,7 @@ function literalConfig(source, wrapper) {
202
256
  else opaque = true;
203
257
  }
204
258
  // Assignment, a second export or condition after the object can override it.
205
- if (list.slice(parsed.end + 1).some(token => ![')', ';'].includes(token.value))) opaque = true;
259
+ if (aliasEnd === null && list.slice(parsed.end + 1).some(token => ![')', ';'].includes(token.value))) opaque = true;
206
260
  return { literals, properties: parsed.properties, opaque, list };
207
261
  }
208
262