@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.
Files changed (132) hide show
  1. package/README.md +779 -2
  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 +552 -0
  12. package/dist/connector-runtime.mjs +130 -0
  13. package/dist/content-next-setup.mjs +200 -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 +205 -0
  20. package/dist/framework-ownership.mjs +68 -0
  21. package/dist/framework.mjs +54 -0
  22. package/dist/guided.mjs +335 -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 +122 -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/setup-environment.mjs +307 -0
  71. package/dist/source-bindings.mjs +77 -0
  72. package/dist/source-check.mjs +149 -0
  73. package/dist/source-collect.mjs +80 -0
  74. package/dist/source-config.mjs +20 -0
  75. package/dist/source-extract.mjs +514 -0
  76. package/dist/source-finalize.mjs +52 -0
  77. package/dist/source-routes.mjs +65 -0
  78. package/dist/source-setup.mjs +490 -0
  79. package/dist/staged-setup.mjs +287 -0
  80. package/dist/static-runtime.mjs +67 -0
  81. package/dist/static-setup.mjs +136 -0
  82. package/dist/svelte-runtime.d.mts +27 -0
  83. package/dist/svelte-runtime.mjs +40 -0
  84. package/dist/svelte-setup.mjs +135 -0
  85. package/dist/svelte-template.mjs +48 -0
  86. package/dist/templates.mjs +248 -0
  87. package/dist/vendor/analyze-project.mjs +82 -0
  88. package/dist/vendor/astro-compiler/LICENSE +53 -0
  89. package/dist/vendor/astro-compiler/astro.wasm +0 -0
  90. package/dist/vendor/astro-compiler/chunk-W5DTLHV4.js +1 -0
  91. package/dist/vendor/astro-compiler/node/index.js +1 -0
  92. package/dist/vendor/check-delivery.mjs +158 -0
  93. package/dist/vendor/dependency-diagnostics.mjs +72 -0
  94. package/dist/vendor/inspect-installed-dependencies.mjs +125 -0
  95. package/dist/vendor/project-analysis.mjs +498 -0
  96. package/dist/vendor/react-LICENSE.txt +21 -0
  97. package/dist/vendor/react-document.mjs +124 -0
  98. package/dist/vendor/react-dom-LICENSE.txt +21 -0
  99. package/dist/vendor/react-form.mjs +63 -0
  100. package/dist/vendor/react-safety.mjs +47 -0
  101. package/dist/vendor/source-manifest-provider.mjs +104 -0
  102. package/dist/vendor/typescript-LICENSE.txt +55 -0
  103. package/dist/vendor/typescript.cjs +200276 -0
  104. package/dist/vite-handler.mjs +54 -0
  105. package/dist/vite-publication-server.d.mts +3 -0
  106. package/dist/vite-publication-server.mjs +82 -0
  107. package/dist/vite-publication.d.mts +15 -0
  108. package/dist/vite-publication.mjs +241 -0
  109. package/dist/vite-source-discovery.mjs +315 -0
  110. package/dist/vite-source.d.mts +30 -0
  111. package/dist/vite-source.mjs +141 -0
  112. package/dist/vue-runtime.d.mts +10 -0
  113. package/dist/vue-runtime.mjs +50 -0
  114. package/dist/vue-server.d.mts +2 -0
  115. package/dist/vue-server.mjs +58 -0
  116. package/dist/vue-setup.mjs +127 -0
  117. package/dist/vue-source.mjs +168 -0
  118. package/dist/website-check.mjs +41 -0
  119. package/dist/website-http.mjs +149 -0
  120. package/dist/website-model.d.mts +20 -0
  121. package/dist/website-model.mjs +155 -0
  122. package/dist/website-next.d.mts +2 -0
  123. package/dist/website-next.mjs +29 -0
  124. package/dist/website-node.d.mts +3 -0
  125. package/dist/website-node.mjs +50 -0
  126. package/dist/website-runtime.d.mts +13 -0
  127. package/dist/website-runtime.mjs +396 -0
  128. package/dist/website-schema.mjs +22175 -0
  129. package/dist/website-setup.mjs +256 -0
  130. package/dist/website.css +49 -0
  131. package/dist/wizard.mjs +298 -0
  132. package/package.json +60 -4
@@ -0,0 +1,298 @@
1
+ import { analyze, applyPlan, CONFIG_FILE, planSetup, projectRoot, readProjectFile, SetupError, validateConfig, validatePlan } from './core.mjs';
2
+ import { applyEnvironment, planEnvironment, readSetupEnvironment, validateEnvironmentPlan, verifyEnvironment } from './setup-environment.mjs';
3
+ import { setupSource, readSourceSettings, validateSourcePlan, applySourcePlan } from './source-setup.mjs';
4
+ import { planPublicationSetup, planInstalledPublication } from './publication-setup.mjs';
5
+ import { planContentSetup } from './content-setup.mjs';
6
+ import { CONNECTION_FILE, readConnection, assertConnectionMatches, assertSavedConnection, runServerKey, validateConnection } from './staged-setup.mjs';
7
+ import { assertRuntimeDependencies } from './runtime-dependencies.mjs';
8
+ import { assertConnectorRuntime } from './connector-runtime.mjs';
9
+ import { inspectSetupFramework } from './framework.mjs';
10
+ import { planWebsiteSettings, assertPlannedSettingsCompatibility } from './website-setup.mjs';
11
+ import { WEBSITE_SECTIONS } from './website-http.mjs';
12
+ import { isNativeFramework } from './native-adapters.mjs';
13
+
14
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15
+ const key = /^cms_server\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[A-Za-z0-9_-]{43}$/;
16
+ function origin(value) {
17
+ try {
18
+ const url = new URL(value);
19
+ return value.length <= 2048 && !/[\s\\]/.test(value) && !url.username && !url.password && !url.search && !url.hash && url.pathname === '/' &&
20
+ (url.protocol === 'https:' || (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))) ? url.origin : null;
21
+ } catch { return null; }
22
+ }
23
+ function locale(value) { try { return value.length <= 35 ? Intl.getCanonicalLocales(value)[0] : null; } catch { return null; } }
24
+
25
+ // Settings depend on the reviewed, installed content registry. Keep this a
26
+ // separate review in the same command instead of guessing a future filesystem
27
+ // or silently completing setup after publication alone. It never receives a key.
28
+ async function installWebsiteSettings(root, io, expected) {
29
+ let plan;
30
+ const connectionBefore = await readConnection(root);
31
+ const checkScope = async () => {
32
+ const connection = await readConnection(root), source = await readSourceSettings(root);
33
+ if (JSON.stringify(connection) !== JSON.stringify(connectionBefore) ||
34
+ connection && ['apiOrigin', 'siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].some(name => connection[name] !== expected[name]) ||
35
+ ['siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].some(name => source[name] !== expected[name])) {
36
+ throw new SetupError('The saved public connection changed during header/footer setup. Review the website scope and rerun setup-settings --apply.');
37
+ }
38
+ };
39
+ try {
40
+ await checkScope();
41
+ const framework = await inspectSetupFramework(root);
42
+ await assertConnectorRuntime(root, framework.framework);
43
+ plan = await planWebsiteSettings(root);
44
+ }
45
+ catch (error) {
46
+ const reason = error instanceof SetupError ? ` ${error.message}` : '';
47
+ throw new SetupError(`SETUP INCOMPLETE: Original-page integration was saved, but header/footer integration could not be installed.${reason} Preserve the saved files and finish the reviewed website settings adapter, then run npx --no-install universalcms-connect setup-settings --apply. Do not replace a server key to repair a missing adapter.`);
48
+ }
49
+ if (['apiOrigin', 'siteId', 'environmentId', 'websiteOrigin'].some(name => plan.config[name] !== expected[name]) || plan.config.routes.some(route => route.locale !== expected.locale)) throw new SetupError('The saved publication scope changed before header/footer setup. Review the website connection before installing settings.');
50
+ if (!plan.sections.includes('navigation')) throw new SetupError('SETUP INCOMPLETE: The reviewed settings adapter does not install header/footer navigation. Finish its native navigation slots before connecting the website.');
51
+ const missingSections = WEBSITE_SECTIONS.filter(name => !plan.sections.includes(name));
52
+ const changed = plan.files.some(file => file.state !== 'unchanged');
53
+ if (changed) {
54
+ io.write('\nHEADER & FOOTER INTEGRATION REQUIRED: Review the website settings files below. Saved CMS links are added alongside original website links; access still requires Navigation on the website server key.\n');
55
+ for (const file of plan.files) io.write(` ${file.state}: ${file.path}\n`);
56
+ io.write(`Installed settings sections after this step: ${plan.sections.join(', ')}. This step changes website integration files only; it does not create, replace or grant access to a server key.\n`);
57
+ if (missingSections.length) io.write(`Unsupported settings adapters: ${missingSections.join(', ')}. Full setup will remain incomplete until reviewed website integrations are available for these sections.\n`);
58
+ const answer = (await io.ask('Install header/footer and website settings? [Y/n] ')).trim().toLowerCase();
59
+ if (answer && !['y', 'yes'].includes(answer)) {
60
+ io.write('SETUP INCOMPLETE: Original-page files remain saved, but header/footer integration was deferred. Run npx --no-install universalcms-connect setup-settings --apply before checking the connection. Your server key was not changed by this step.\n');
61
+ return false;
62
+ }
63
+ }
64
+ // Replanning verifies layout/route observations as well as reviewed file
65
+ // bytes. Reject any confirmation-time change rather than install a new plan
66
+ // which the user never reviewed.
67
+ const current = await planWebsiteSettings(root);
68
+ if (JSON.stringify(current) !== JSON.stringify(plan)) throw new SetupError('The website settings plan changed during review. Header/footer setup remains incomplete; review the website files and rerun setup-settings --apply.');
69
+ const framework = await inspectSetupFramework(root);
70
+ await assertConnectorRuntime(root, framework.framework);
71
+ await checkScope();
72
+ await validatePlan(plan);
73
+ await applyPlan(plan);
74
+ io.write('Header/footer integration is installed. Restart or deploy the website, then run npx --no-install universalcms-connect check-settings. Verify a saved header link and a saved footer link in the actual desktop and mobile menus; file installation alone does not verify their appearance.\n');
75
+ if (missingSections.length) {
76
+ io.write(`SETUP INCOMPLETE: Website settings adapters are not installed for ${missingSections.join(', ')}. Key access cannot install these adapters. Preserve the installed files and finish reviewed integration before claiming full setup.\n`);
77
+ return false;
78
+ }
79
+ return true;
80
+ }
81
+
82
+ /** Interactive effects are injected so cancellation and secret handling are testable. */
83
+ export async function runSetup(directory, options, io) {
84
+ if (options.originalRoutesOnly !== undefined && typeof options.originalRoutesOnly !== 'boolean') throw new SetupError('Choose a valid original-route setup mode.');
85
+ const root = await projectRoot(directory);
86
+ let connection = await readConnection(root), connectionPlan;
87
+ if (connection) {
88
+ const publicOptions = Object.fromEntries(Object.entries(options).filter(([name, value]) => value !== undefined && ['apiOrigin', 'siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].includes(name)));
89
+ if (JSON.stringify(validateConnection({ ...connection, ...publicOptions })) !== JSON.stringify(connection)) throw new SetupError('Setup options differ from the saved connection. Review the existing integration before changing scope or origins.');
90
+ await assertSavedConnection(root, connection);
91
+ options = { ...options, ...connection };
92
+ }
93
+ io.write(`Website project: ${JSON.stringify(root)}\n`);
94
+ io.write('Setup prepares public source export, publication checks and published-content rendering for the reviewed original website URLs, followed by a required header/footer and website settings review.\n');
95
+ io.write(connection
96
+ ? 'Using the saved public connection. After saving a supported delivery template, setup offers a hidden server-key prompt.\n'
97
+ : options.originalRoutesOnly
98
+ ? 'Setup will review the complete original-route integration, save its public connection and then offer hidden server-key input. No AI is used.\n'
99
+ : 'No saved connect record was found. Guided setup asks for public connection values and, when a supported delivery template is prepared, a hidden server key.\n');
100
+ const analysis = await analyze(root);
101
+ const detected = await inspectSetupFramework(root, { analysis });
102
+ await assertConnectorRuntime(root, detected.framework);
103
+ io.write(`Detected framework: ${detected.label}. Validating source routes and rendering before saving files or asking for a key.\n`);
104
+ const saved = await readSetupEnvironment(root);
105
+ let prior;
106
+ const configText = await readProjectFile(root, CONFIG_FILE);
107
+ if (configText) {
108
+ try { prior = validateConfig(JSON.parse(configText)); }
109
+ catch { throw new SetupError('The saved connector configuration is invalid. Restore it before running setup.'); }
110
+ }
111
+ if (options.originalRoutesOnly && !prior) {
112
+ for (const filename of ['.universalcms/CmsPage.jsx', '.universalcms/demo.jsx', 'universalcms-demo.html']) {
113
+ if (await readProjectFile(root, filename) !== null) throw new SetupError('Existing delivery demo files have no connector configuration. Restore their reviewed configuration or complete a developer repair before original-route setup; existing files were preserved.');
114
+ }
115
+ }
116
+ // Only the environment helper's sanitized defaults may reach terminal text.
117
+ const framework = detected.framework;
118
+ const sourceDefaults = await readSourceSettings(root);
119
+ const defaults = { ...saved.defaults, ...sourceDefaults, ...(connection ?? {}) };
120
+ const cacheComponents = framework === 'next' && (await Promise.all(['js', 'mjs', 'cjs', 'ts'].map(extension => readProjectFile(root, `next.config.${extension}`)))).some(source => source && /\bcacheComponents\b/.test(source));
121
+ const field = async (name, label, validate, fallback, message) => {
122
+ if (options[name] !== undefined) {
123
+ const value = validate(options[name]);
124
+ if (!value) throw new SetupError(message);
125
+ return value;
126
+ }
127
+ while (true) {
128
+ const answer = (await io.ask(`${label}${fallback ? ` [${fallback}]` : ''}: `)).trim();
129
+ const value = validate(answer || fallback || '');
130
+ if (value) return value;
131
+ io.write(`${message}\n`);
132
+ }
133
+ };
134
+ io.write('Connect this website without AI. Enter the connection values from Developer setup.\n');
135
+ const apiOrigin = await field('apiOrigin', 'CMS API origin', origin, defaults.apiOrigin, 'Use an HTTPS origin, or HTTP localhost for development.');
136
+ const siteId = await field('siteId', 'Website ID', value => uuid.test(value) && value.toLowerCase(), defaults.siteId, 'Enter a valid website UUID.');
137
+ const environmentId = await field('environmentId', 'Environment ID', value => uuid.test(value) && value.toLowerCase(), defaults.environmentId, 'Enter a valid environment UUID.');
138
+ const contentLocale = await field('locale', 'Content locale', locale, defaults.locale || 'en', 'Enter a valid content locale, such as en.');
139
+ const websiteOrigin = await field('websiteOrigin', 'Website origin', origin, defaults.websiteOrigin || (framework === 'vite' ? 'http://localhost:5173' : framework === 'astro' ? 'http://localhost:4321' : undefined), 'Use the exact website HTTPS origin, or HTTP localhost during development.');
140
+ const adminOrigin = await field('adminOrigin', 'CMS admin origin', origin, defaults.adminOrigin, 'Use the exact CMS admin HTTPS origin, or HTTP localhost during development.');
141
+ if (options.originalRoutesOnly && !connection) {
142
+ connection = validateConnection({ version: 1, apiOrigin, siteId, environmentId, locale: contentLocale, websiteOrigin, adminOrigin });
143
+ await assertConnectionMatches(root, connection);
144
+ connectionPlan = { root, files: [{ path: CONNECTION_FILE, content: `${JSON.stringify(connection, null, 2)}\n`, state: 'create' }] };
145
+ }
146
+ const mount = options.mount ?? prior?.mount ?? '/cms';
147
+ let plan, source, publication, content, environment, previouslyInstalledSettings = false;
148
+ // The original-route renderer is bundled with the connector. Keep existing
149
+ // demo files/dependencies intact, but do not require a new SDK demo to connect.
150
+ if ((framework === 'astro' || isNativeFramework(framework)) && prior) throw new SetupError('Existing delivery demo configuration needs review; native framework setup preserves it.');
151
+ if (framework !== 'astro' && !isNativeFramework(framework) && !cacheComponents && (!options.originalRoutesOnly || prior)) {
152
+ try { plan = await planSetup(root, { apiOrigin, siteId, environmentId, locale: contentLocale, mount }, { reconfigure: true }); }
153
+ catch (error) {
154
+ const optionalDemoConflict = !prior && error instanceof SetupError && /^(?:Route groups, parallel routes or dynamic root routes need a manual route review before setup\.|The chosen Next\.js mount already exists\.)/.test(error.message);
155
+ if (framework !== 'next' || !(error instanceof SetupError) || !optionalDemoConflict && !/Automatic setup requires Next\.js 16 with a confirmed Node runtime|Cache Components/.test(error.message)) throw error;
156
+ io.write('The optional CMS demo route was skipped. Public source and original-route publication setup will validate this website independently.\n');
157
+ }
158
+ }
159
+ if (await readProjectFile(root, '.universalcms/website.integration.json') !== null) {
160
+ // A settings adapter composes publication and content. Revalidate the owned
161
+ // installation; running the older publication-only planners here would
162
+ // downgrade its proxy, Vite server or native page wrappers on every rerun.
163
+ await assertConnectionMatches(root, validateConnection({ version: 1, apiOrigin, siteId, environmentId, locale: contentLocale, websiteOrigin, adminOrigin }));
164
+ publication = { ...await planInstalledPublication(root), framework };
165
+ previouslyInstalledSettings = true;
166
+ io.write('Existing header/footer, publication and content integration is being checked and preserved. New native routes or customized files require a reviewed adapter update.\n');
167
+ } else {
168
+ source = await setupSource(root, { siteId, environmentId, websiteOrigin, adminOrigin, locale: contentLocale, publication: true });
169
+ publication = await planPublicationSetup(root, { apiOrigin, siteId, environmentId, websiteOrigin }, source);
170
+ content = await planContentSetup(root, publication, source);
171
+ await assertPlannedSettingsCompatibility(root, content, publication);
172
+ }
173
+ if (plan || prior) await assertRuntimeDependencies(root, framework, adminOrigin);
174
+ const hasDelivery = Boolean(plan || publication);
175
+ if (cacheComponents && publication) io.write('The reviewed Next plan disables Cache Components for request-time published content and renders metadata in the initial HTML. Original pages are also checked before HTML or RSC delivery.\n');
176
+ if (hasDelivery && !connection) {
177
+ let apiKey;
178
+ while (true) {
179
+ const answer = await io.hidden(saved.keyConfigured ? 'Server delivery key (hidden; Enter keeps the saved key): ' : 'Server delivery key (hidden): ');
180
+ if (!answer && saved.keyConfigured) break;
181
+ if (key.test(answer)) { apiKey = answer; break; }
182
+ io.write('Enter a valid server delivery key from Developer setup. It is not an AI provider key.\n');
183
+ }
184
+ environment = await planEnvironment(root, { apiOrigin, siteId, environmentId, locale: contentLocale, websiteOrigin, adminOrigin, ...(apiKey ? { apiKey } : {}) });
185
+ apiKey = undefined;
186
+ }
187
+ let slug;
188
+ while (hasDelivery && !connection) {
189
+ slug = (await io.ask('Published page slug to verify (optional; Enter skips): ')).trim();
190
+ if (!slug || (slug.length <= 160 && /^[a-z0-9]+(?:[-/][a-z0-9]+)*$/.test(slug))) break;
191
+ io.write('Enter a page slug without a leading slash, or leave it empty.\n');
192
+ }
193
+ io.write(`\nWebsite: ${siteId}\nEnvironment: ${environmentId}\n${apiOrigin ? `API: ${apiOrigin}\n` : ''}Locale: ${contentLocale}\n`);
194
+ for (const file of connectionPlan?.files ?? []) io.write(` ${file.state}: ${file.path}\n`);
195
+ for (const file of plan?.files ?? []) io.write(` ${file.state}: ${file.path}\n`);
196
+ for (const file of publication?.files ?? []) io.write(` ${file.state}: ${file.path}\n`);
197
+ for (const file of content?.files ?? []) io.write(` ${file.state}: ${file.path}\n`);
198
+ if (publication) {
199
+ io.write('Review these original URLs before saving. Each one will return 404 until its matching CMS entry is published:\n');
200
+ for (const route of publication.config.routes) io.write(` ${route.path} -> ${route.kind} / ${route.slug} / ${route.locale}\n`);
201
+ io.write('For different URL mappings, cancel and edit .universalcms/publication.routes.json before rerunning. Published pages retain their native design; errors show unavailable content.\n');
202
+ io.write('The reviewed source blocks will render from published CMS documents. Native interactions remain in their components; new CMS blocks appear after the bound content. Draft saves do not change public pages.\n');
203
+ }
204
+ for (const file of environment?.files ?? []) io.write(` ${file.action}: ${file.path}\n`);
205
+ if (environment) io.write('The delivery key is saved only in private .env.local (owner access), excluded from Git.\n');
206
+ if (connection && hasDelivery) io.write('First save the integration files. Setup will then offer to save the delivery key through hidden input; you can also defer it and run server-key later.\n');
207
+ if (source) {
208
+ for (const file of source.files) io.write(` ${file.state}: ${file.path}\n`);
209
+ io.write(framework === 'astro'
210
+ ? 'Astro source export uses the reviewed literal public pages and preserves their original layout and style. Private, dynamic or custom routes require a reviewed adapter.\n'
211
+ : framework === 'vite'
212
+ ? 'Source export uses the reviewed public pages/posts in .universalcms/source.entries.json. The normal dev and preview commands will serve /api/cms-source after restarting.\n'
213
+ : 'Automatic source export copies public static page text. Exclude private paths in .universalcms/source.config.json. Dynamic content and unresolved components require explicit mapping and review.\n');
214
+ io.write(`Source snapshot diagnostics (import data only; publication and content bindings are reviewed separately):\n${JSON.stringify(source.diagnostics)}\n`);
215
+ if (source.retainedAdapter) io.write('Existing source adapter retained. Its reviewed base mappings are preserved; verify that it serves the refreshed automatic snapshot with check-source.\n');
216
+ }
217
+ // The one-command flow presents the same reviewed files and routes in plain
218
+ // language; it receives only paths, states and routes, never file contents.
219
+ const confirmation = io.review
220
+ ? (await io.review({
221
+ framework: detected.label,
222
+ routes: (publication?.config.routes ?? []).map(route => ({ path: route.path, kind: route.kind, slug: route.slug })),
223
+ files: [...connectionPlan?.files ?? [], ...plan?.files ?? [], ...publication?.files ?? [], ...content?.files ?? [], ...source?.files ?? []].map(file => ({ path: file.path, state: file.state }))
224
+ .concat((environment?.files ?? []).map(file => ({ path: file.path, state: file.action }))),
225
+ }) ? 'y' : 'n')
226
+ : (await io.ask('Save this setup? [Y/n] ')).trim().toLowerCase();
227
+ if (confirmation && confirmation !== 'y' && confirmation !== 'yes') {
228
+ io.write('Setup cancelled. No files changed.\n'); return { saved: false };
229
+ }
230
+ if (plan) await validatePlan(plan);
231
+ if (publication) await validatePlan(publication);
232
+ if (content) {
233
+ await assertPlannedSettingsCompatibility(root, content, publication);
234
+ await validatePlan(content);
235
+ }
236
+ if (environment) await validateEnvironmentPlan(environment);
237
+ if (source) await validateSourcePlan(source);
238
+ if (connectionPlan) {
239
+ await validatePlan(connectionPlan);
240
+ await assertConnectionMatches(root, connection);
241
+ } else if (connection) await assertSavedConnection(root, connection);
242
+ if (options.originalRoutesOnly) {
243
+ if (await readProjectFile(root, CONFIG_FILE) !== configText) throw new SetupError('The delivery demo configuration changed while reviewing setup. No new integration files or keys were saved; review the existing files and rerun setup.');
244
+ if (!prior) for (const filename of ['.universalcms/CmsPage.jsx', '.universalcms/demo.jsx', 'universalcms-demo.html']) {
245
+ if (await readProjectFile(root, filename) !== null) throw new SetupError('Delivery demo files appeared while reviewing original-route setup. Existing files were preserved; review them before rerunning setup.');
246
+ }
247
+ }
248
+ await inspectSetupFramework(root);
249
+ await assertConnectorRuntime(root, framework);
250
+ if (plan || prior) await assertRuntimeDependencies(root, framework, adminOrigin);
251
+ if (environment) await applyEnvironment(environment);
252
+ let scaffold = { created: [], updated: [] };
253
+ try { if (plan) scaffold = await applyPlan(plan); if (source) { const applied = await applySourcePlan(source); scaffold.created.push(...applied.created); scaffold.updated.push(...applied.updated); } if (publication) { const applied = await applyPlan(publication); scaffold.created.push(...applied.created); scaffold.updated.push(...applied.updated); } if (content) { const applied = await applyPlan(content); scaffold.created.push(...applied.created); scaffold.updated.push(...applied.updated); } if (connectionPlan) { const applied = await applyPlan(connectionPlan); scaffold.created.push(...applied.created); scaffold.updated.push(...applied.updated); } }
254
+ catch { throw new SetupError('Setup did not finish saving all generated files. Existing custom edits were preserved; inspect any saved connection/source files and rerun setup.'); }
255
+ io.write(`Saved connection settings. Created ${scaffold.created.length} files; updated ${scaffold.updated.length} generated files.\n`);
256
+ if (!await installWebsiteSettings(root, io, { apiOrigin, siteId, environmentId, locale: contentLocale, websiteOrigin, adminOrigin })) return { saved: true, settingsInstalled: false };
257
+ if (slug) {
258
+ const verification = await verifyEnvironment(environment, { slug });
259
+ io.write(`${verification.message}\n`);
260
+ if (!verification.verified) io.write('Settings were saved, but delivery is not verified. Correct the connection or publish the requested page, then rerun setup.\n');
261
+ } else if (hasDelivery) io.write(connection ? 'Delivery was not checked. The server-key step follows the source instructions below.\n' : 'Delivery was not checked. Rerun setup with a published page slug when ready.\n');
262
+ if (plan || prior) io.write('Runtime packages and renderer stylesheet are installed and compatible.\n');
263
+ io.write('Restart the website using its normal command.\n');
264
+ if (plan) io.write('See .universalcms/SETUP.md for rendering and deployment steps.\n');
265
+ io.write(publication
266
+ ? 'Original URL publication checks are installed, including the mapped home page. Missing or unpublished pages return 404; failed delivery returns 503 without original content. Restart or deploy the updated website and verify both states.\n'
267
+ : 'SETUP INCOMPLETE: Original website URLs still need publication checks. The saved scaffold alone cannot control their availability.\n');
268
+ if (content || previouslyInstalledSettings) io.write('Published-content bindings are installed on the reviewed routes. Publish a revised document in disposable staging and verify changed text, removed blocks and draft isolation. Open pages check for published revisions every 15 seconds and on focus. Deploy the same updated code and private runtime configuration to each live host; local setup does not deploy a remote website.\n');
269
+ if (source) io.write(isNativeFramework(framework)
270
+ ? 'Source export, published native pages and website settings use the reviewed private Node server. Run the generated package scripts and verify the exact registered origin. Static-only hosting cannot run this server. No content was imported or published.\n'
271
+ : framework === 'astro'
272
+ ? 'Astro source export and original routes use the reviewed Node server. Run the updated dev command, or build then start for production; static-only hosting cannot serve the managed routes. The frozen original source snapshot remains stable after binding. No content was imported or published.\n'
273
+ : framework === 'vite'
274
+ ? 'Public source export is prepared. Restart with your normal dev command, then click Connect & fetch content. Static hosting still requires a server for /api/cms-source; vite build alone cannot serve an API. No content was imported or published.\n'
275
+ : 'Public static source export is prepared. New static pages are discovered on each development request; production builds refresh the bundled snapshot with the prebuild hook. Review the diagnostics for omitted routes and unresolved content. No content was imported or published.\n');
276
+ const quote = value => `'${value.replaceAll("'", "'\\''")}'`;
277
+ io.write(`After preparing and starting the source endpoint, run:\nnpx --no-install universalcms-connect check-source${framework === 'next' ? ' --check-routes' : ''} --website-origin ${quote(websiteOrigin || defaults.websiteOrigin || 'YOUR_WEBSITE_ORIGIN')} --admin-origin ${quote(adminOrigin || defaults.adminOrigin || 'YOUR_CMS_ADMIN_ORIGIN')} --site ${siteId} --environment ${environmentId}\n`);
278
+ io.write('Verify the manifest and access from the CMS browser before clicking Connect & fetch content. No server key is used for that source read.\n');
279
+ if (hasDelivery && connection) {
280
+ io.write(saved.keyConfigured
281
+ ? '\nSERVER KEY: Your existing local delivery key was preserved. You can keep it or replace it through hidden input.\n'
282
+ : '\nSERVER KEY REQUIRED: The delivery files are prepared. Create a server delivery key in Developer setup, or use one you saved securely.\n');
283
+ io.write('Enter the key now or choose n to save it later. The integration files are already saved; canceling key entry leaves them in place.\n');
284
+ try {
285
+ const answer = (await io.ask('Save a server key now? [Y/n] ')).trim().toLowerCase();
286
+ if (answer && !['y', 'yes'].includes(answer)) {
287
+ io.write('Server key deferred. Integration files remain saved.\nNext run: npx --no-install universalcms-connect server-key\n');
288
+ } else {
289
+ const result = await runServerKey(root, io);
290
+ if (!result.saved) io.write('Integration files remain saved. To retry: npx --no-install universalcms-connect server-key\n');
291
+ }
292
+ } catch (error) {
293
+ io.write('Integration files remain saved. The server-key step did not finish; review private settings before retrying: npx --no-install universalcms-connect server-key\n');
294
+ throw error;
295
+ }
296
+ }
297
+ return { saved: true };
298
+ }
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@universalcms/connect",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.13.0",
4
+ "description": "Deterministic framework-aware Universal CMS setup for reviewed native websites",
5
+ "type": "module",
6
+ "bin": {
7
+ "universalcms-connect": "dist/cli.mjs"
8
+ },
9
+ "exports": {
10
+ "./node": "./dist/vite-handler.mjs",
11
+ "./next-source": "./dist/next-source.mjs",
12
+ "./vite-source": "./dist/vite-source.mjs",
13
+ "./publication": "./dist/publication.mjs",
14
+ "./next-publication": "./dist/next-publication.mjs",
15
+ "./vite-publication": "./dist/vite-publication.mjs",
16
+ "./vite-publication-server": "./dist/vite-publication-server.mjs",
17
+ "./react-publication": "./dist/react-publication.mjs",
18
+ "./publication.css": "./dist/publication.css",
19
+ "./website-runtime": "./dist/website-runtime.mjs",
20
+ "./website-node": "./dist/website-node.mjs",
21
+ "./website-next": "./dist/website-next.mjs",
22
+ "./react-website": "./dist/react-website.mjs",
23
+ "./astro-runtime": "./dist/astro-runtime.mjs",
24
+ "./astro-website": "./dist/astro-website.mjs",
25
+ "./native-website": "./dist/native-website.mjs",
26
+ "./native-website-server": "./dist/native-website-server.mjs",
27
+ "./vue-runtime": "./dist/vue-runtime.mjs",
28
+ "./vue-server": "./dist/vue-server.mjs",
29
+ "./angular-runtime": "./dist/angular-runtime.mjs",
30
+ "./svelte-runtime": "./dist/svelte-runtime.mjs",
31
+ "./static-runtime": "./dist/static-runtime.mjs",
32
+ "./website.css": "./dist/website.css",
33
+ "./public-bindings": "./dist/public-bindings.mjs"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "engines": {
40
+ "node": ">=22.12 <27"
41
+ },
42
+ "scripts": {
43
+ "build": "node scripts/build.mjs",
44
+ "typecheck": "node --check src/cli.mjs && node --check src/core.mjs && node --check src/project-files.mjs && node --check src/templates.mjs && node --check src/vite-handler.mjs && node --check src/wizard.mjs && node --check src/guided.mjs && node --check src/framework.mjs && node --check src/framework-ownership.mjs && node --check src/preflight.mjs && node --check src/preflight-recovery.mjs && node --check src/native-route-review.mjs && node --check src/page-discovery.mjs && node --check src/page-mapping-review.mjs && node --check src/native-chrome-model.mjs && node --check src/native-chrome-source.mjs && node --check src/native-chrome-review.mjs && node --check src/next-settings-layout.mjs && node --check src/vite-source-discovery.mjs && node --check src/reviewed-storefront-source.mjs && node --check src/reviewed-commerce-ownership.mjs && node --check src/native-routes.mjs && node --check src/reviewed-native-ownership.mjs && node --check src/runtime-dependencies.mjs && node --check src/connector-runtime.mjs && node --check src/staged-setup.mjs && node --check src/setup-environment.mjs && node --check src/source-check.mjs && node --check src/source-finalize.mjs && node --check src/source-routes.mjs && node --check src/source-config.mjs && node --check src/source-collect.mjs && node --check src/source-extract.mjs && node --check src/source-setup.mjs && node --check src/next-source.mjs && node --check src/vite-source.mjs && node --check src/publication.mjs && node --check src/publication-verification.mjs && node --check src/publication-check.mjs && node --check src/publication-document.mjs && node --check src/publication-browser.mjs && node --check src/publication-setup.mjs && node --check src/publication-next-setup.mjs && node --check src/next-publication.mjs && node --check src/vite-publication.mjs && node --check src/vite-publication-server.mjs && node --check src/source-bindings.mjs && node --check src/content-registry.mjs && node --check src/content-registry-validation.mjs && node --check src/content-next-setup.mjs && node --check src/content-vite-setup.mjs && node --check src/content-storefront-setup.mjs && node --check src/content-setup.mjs && tsc --noEmit -p tsconfig.react.json && node --check src/website-http.mjs && node --check src/website-node.mjs && node --check src/website-next.mjs && node --check src/website-setup.mjs && node --check src/reviewed-commerce-settings-setup.mjs && node --check src/website-runtime.mjs && node --check src/website-check.mjs && node --check src/astro-source.mjs && node --check src/astro-setup.mjs && node --check src/astro-runtime.mjs && node --check src/native-adapters.mjs && node --check src/native-website-model.mjs && node --check src/vue-source.mjs && node --check src/vue-setup.mjs && node --check src/vue-runtime.mjs && node --check src/vue-server.mjs && node --check src/svelte-template.mjs && node --check src/svelte-setup.mjs && node --check src/svelte-runtime.mjs && node --check src/angular-setup.mjs && node --check src/angular-runtime.mjs && node --check src/static-setup.mjs && node --check src/static-runtime.mjs",
45
+ "prepack": "node scripts/build.mjs"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "^19.2.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "react": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@astrojs/compiler": "2.13.0",
60
+ "@universalcms/react": "workspace:*"
61
+ }
62
+ }