@universalcms/connect 0.1.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +694 -37
- package/dist/angular-runtime.d.mts +4 -0
- package/dist/angular-runtime.mjs +7 -0
- package/dist/angular-setup.mjs +124 -0
- package/dist/astro-runtime.d.mts +6 -0
- package/dist/astro-runtime.mjs +255 -0
- package/dist/astro-setup.mjs +215 -0
- package/dist/astro-source.mjs +451 -0
- package/dist/astro-website.d.mts +5 -0
- package/dist/astro-website.mjs +42 -0
- package/dist/cli.mjs +418 -15
- package/dist/connector-runtime.mjs +130 -0
- package/dist/content-next-setup.mjs +200 -0
- package/dist/content-registry-validation.mjs +40 -0
- package/dist/content-registry.mjs +44 -0
- package/dist/content-setup.mjs +13 -0
- package/dist/content-storefront-setup.mjs +155 -0
- package/dist/content-vite-setup.mjs +107 -0
- package/dist/core.mjs +32 -51
- package/dist/framework-ownership.mjs +68 -0
- package/dist/framework.mjs +54 -0
- package/dist/guided.mjs +335 -0
- package/dist/native-adapters.mjs +31 -0
- package/dist/native-chrome-model.d.mts +3 -0
- package/dist/native-chrome-model.mjs +14 -0
- package/dist/native-chrome-review.mjs +49 -0
- package/dist/native-chrome-source.mjs +69 -0
- package/dist/native-route-review.mjs +152 -0
- package/dist/native-routes.mjs +27 -0
- package/dist/native-website-model.d.mts +18 -0
- package/dist/native-website-model.mjs +67 -0
- package/dist/native-website-server.d.mts +1 -0
- package/dist/native-website-server.mjs +35222 -0
- package/dist/native-website-server.mjs.LEGAL.txt +56 -0
- package/dist/native-website.d.mts +3 -0
- package/dist/native-website.mjs +120 -0
- package/dist/native-website.mjs.LEGAL.txt +56 -0
- package/dist/next-publication.d.mts +12 -0
- package/dist/next-publication.mjs +107 -0
- package/dist/next-settings-layout.mjs +233 -0
- package/dist/next-source.d.mts +8 -0
- package/dist/next-source.mjs +93 -0
- package/dist/page-discovery.mjs +236 -0
- package/dist/page-mapping-review.mjs +160 -0
- package/dist/preflight-recovery.mjs +122 -0
- package/dist/preflight.mjs +138 -0
- package/dist/project-files.mjs +56 -0
- package/dist/public-bindings.d.mts +3 -0
- package/dist/public-bindings.mjs +28 -0
- package/dist/publication-browser.d.mts +5 -0
- package/dist/publication-browser.mjs +258 -0
- package/dist/publication-check.mjs +141 -0
- package/dist/publication-document.d.mts +2 -0
- package/dist/publication-document.mjs +59 -0
- package/dist/publication-next-setup.mjs +134 -0
- package/dist/publication-setup.mjs +138 -0
- package/dist/publication-verification.mjs +85 -0
- package/dist/publication.css +6 -0
- package/dist/publication.d.mts +31 -0
- package/dist/publication.mjs +320 -0
- package/dist/react-publication.d.mts +22 -0
- package/dist/react-publication.mjs +301 -0
- package/dist/react-website.d.mts +15 -0
- package/dist/react-website.mjs +735 -0
- package/dist/reviewed-commerce-ownership.mjs +26 -0
- package/dist/reviewed-commerce-settings-setup.mjs +243 -0
- package/dist/reviewed-native-ownership.mjs +118 -0
- package/dist/reviewed-storefront-source.mjs +227 -0
- package/dist/runtime-dependencies.mjs +59 -0
- package/dist/setup-environment.mjs +70 -0
- package/dist/source-bindings.mjs +77 -0
- package/dist/source-check.mjs +149 -0
- package/dist/source-collect.mjs +80 -0
- package/dist/source-config.mjs +20 -0
- package/dist/source-extract.mjs +514 -0
- package/dist/source-finalize.mjs +52 -0
- package/dist/source-routes.mjs +65 -0
- package/dist/source-setup.mjs +490 -0
- package/dist/staged-setup.mjs +287 -0
- package/dist/static-runtime.mjs +67 -0
- package/dist/static-setup.mjs +136 -0
- package/dist/svelte-runtime.d.mts +27 -0
- package/dist/svelte-runtime.mjs +40 -0
- package/dist/svelte-setup.mjs +135 -0
- package/dist/svelte-template.mjs +48 -0
- package/dist/templates.mjs +167 -3
- package/dist/vendor/astro-compiler/LICENSE +53 -0
- package/dist/vendor/astro-compiler/astro.wasm +0 -0
- package/dist/vendor/astro-compiler/chunk-W5DTLHV4.js +1 -0
- package/dist/vendor/astro-compiler/node/index.js +1 -0
- package/dist/vendor/project-analysis.mjs +56 -4
- package/dist/vendor/react-LICENSE.txt +21 -0
- package/dist/vendor/react-document.mjs +124 -0
- package/dist/vendor/react-dom-LICENSE.txt +21 -0
- package/dist/vendor/react-form.mjs +63 -0
- package/dist/vendor/react-safety.mjs +47 -0
- package/dist/vendor/source-manifest-provider.mjs +104 -0
- package/dist/vendor/typescript-LICENSE.txt +55 -0
- package/dist/vendor/typescript.cjs +200276 -0
- package/dist/vite-publication-server.d.mts +3 -0
- package/dist/vite-publication-server.mjs +82 -0
- package/dist/vite-publication.d.mts +15 -0
- package/dist/vite-publication.mjs +241 -0
- package/dist/vite-source-discovery.mjs +315 -0
- package/dist/vite-source.d.mts +30 -0
- package/dist/vite-source.mjs +141 -0
- package/dist/vue-runtime.d.mts +10 -0
- package/dist/vue-runtime.mjs +50 -0
- package/dist/vue-server.d.mts +2 -0
- package/dist/vue-server.mjs +58 -0
- package/dist/vue-setup.mjs +127 -0
- package/dist/vue-source.mjs +168 -0
- package/dist/website-check.mjs +41 -0
- package/dist/website-http.mjs +149 -0
- package/dist/website-model.d.mts +20 -0
- package/dist/website-model.mjs +155 -0
- package/dist/website-next.d.mts +2 -0
- package/dist/website-next.mjs +29 -0
- package/dist/website-node.d.mts +3 -0
- package/dist/website-node.mjs +50 -0
- package/dist/website-runtime.d.mts +13 -0
- package/dist/website-runtime.mjs +396 -0
- package/dist/website-schema.mjs +22175 -0
- package/dist/website-setup.mjs +256 -0
- package/dist/website.css +49 -0
- package/dist/wizard.mjs +237 -38
- package/package.json +43 -7
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from 'node:readline/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { lstat } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { analyze, planSetup, applyPlan, checkConnection, readProjectFile, projectRoot, SetupError } from './core.mjs';
|
|
4
6
|
import { runSetup } from './wizard.mjs';
|
|
7
|
+
import { preflightSetup } from './preflight.mjs';
|
|
8
|
+
import { inspectPageCandidates } from './page-discovery.mjs';
|
|
9
|
+
import { runPageMappingReview } from './page-mapping-review.mjs';
|
|
10
|
+
import { inspectNativeRouteCandidates, runNativeRouteReview } from './native-route-review.mjs';
|
|
11
|
+
import { setupReviewDiagnostic, setupRecovery } from './preflight-recovery.mjs';
|
|
12
|
+
import { runConnect, runServerKey, runPrepareServerKey, prepareSourceKeyCheck, readConnection } from './staged-setup.mjs';
|
|
13
|
+
import { readSetupEnvironment } from './setup-environment.mjs';
|
|
14
|
+
import { checkSource } from './source-check.mjs';
|
|
15
|
+
import { checkPublication } from './publication-check.mjs';
|
|
16
|
+
import { finalizeSource } from './source-finalize.mjs';
|
|
17
|
+
import { setupWebsiteSettings } from './website-setup.mjs';
|
|
18
|
+
import { checkWebsiteSettings } from './website-check.mjs';
|
|
19
|
+
import { WEBSITE_SECTIONS } from './website-http.mjs';
|
|
20
|
+
import { setupSource, applySourcePlan, syncSource, readSourceSettings } from './source-setup.mjs';
|
|
21
|
+
import { processIo, runGuided } from './guided.mjs';
|
|
5
22
|
|
|
6
|
-
const help = `Universal CMS connector 0.
|
|
7
|
-
Usage: universalcms-connect <setup|analyze|init|check> [options]
|
|
23
|
+
const help = `Universal CMS connector 0.13.0
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
Quick start (recommended) — run in your website folder:
|
|
26
|
+
npx @universalcms/connect@latest --cms https://YOUR_CMS_ADDRESS
|
|
10
27
|
|
|
28
|
+
Opens your browser to approve the website, installs this package, connects
|
|
29
|
+
your pages, header/footer and settings, saves the website key privately and
|
|
30
|
+
guides the restart, content import and final check. Run it again any time to
|
|
31
|
+
continue where it stopped.
|
|
32
|
+
|
|
33
|
+
--cms <origin> Your Universal CMS address (or UNIVERSALCMS_URL)
|
|
34
|
+
--code <code> One-time code from Developer setup → Connect without a browser
|
|
35
|
+
--details Show every file and technical message
|
|
36
|
+
|
|
37
|
+
Individual steps (advanced):
|
|
38
|
+
Usage: universalcms-connect <start|inspect-pages|review-pages|preflight|review-routes|review-layout|connect|setup|setup-settings|check-settings|server-key|setup-source|sync-source|finalize-source|analyze|init|check|check-source|check-publication> [options]
|
|
39
|
+
|
|
40
|
+
inspect-pages Discover page URLs and screen candidates without writes
|
|
41
|
+
review-pages Record local page/screen ownership intent; does not install
|
|
42
|
+
preflight Plan original-route setup using public values; no writes or keys
|
|
43
|
+
review-routes Review native Next application routes before full setup
|
|
44
|
+
review-layout Review supported native header/footer slots before full setup
|
|
45
|
+
connect Save public website connection values, without a key
|
|
46
|
+
setup Prepare pages and required header/footer settings, then key input
|
|
47
|
+
setup-settings Install or repair reviewed website settings adapters (--apply)
|
|
48
|
+
check-settings Check each settings adapter and key access without credentials
|
|
49
|
+
server-key Save the delivery key privately through hidden input
|
|
50
|
+
setup-source Prepare source export for a supported native framework
|
|
51
|
+
sync-source Refresh the source snapshot before a production build
|
|
52
|
+
check-source Read the website source export without credentials
|
|
53
|
+
check-publication Verify the managed adapter and an original website URL
|
|
54
|
+
finalize-source Close bootstrap source export after CMS import (--apply)
|
|
55
|
+
|
|
56
|
+
--check-routes Audit local Next App Router paths against the source export
|
|
11
57
|
--project <directory> Website folder (default current directory)
|
|
12
58
|
--framework <next|vite> Confirm a supported template
|
|
13
59
|
--api-origin <origin> Hosted CMS API HTTPS origin
|
|
@@ -15,28 +61,209 @@ Usage: universalcms-connect <setup|analyze|init|check> [options]
|
|
|
15
61
|
--environment <uuid> Environment ID from Developer setup
|
|
16
62
|
--locale <locale> Published content locale (default en)
|
|
17
63
|
--mount </cms> Free Next.js route prefix (default /cms)
|
|
18
|
-
--
|
|
19
|
-
--
|
|
64
|
+
--original-routes-only Connect original pages without adding a new SDK demo
|
|
65
|
+
--prepare-only Save private server-key settings before adapter setup
|
|
66
|
+
--skip-key-validation Skip live key verification with CMS API
|
|
67
|
+
--require-server-key Require matching private key settings for setup-source
|
|
68
|
+
--website-origin <url> Exact website origin (setup or check-source)
|
|
69
|
+
--admin-origin <url> Exact CMS admin origin (setup or check-source)
|
|
20
70
|
--apply Explicitly create the reviewed additive files
|
|
21
71
|
--dry-run Print a plan only (the default)
|
|
22
72
|
--non-interactive Never ask questions
|
|
23
73
|
--slug <slug> Published page used by check
|
|
74
|
+
--path </path> Original URL path used by check-publication
|
|
24
75
|
|
|
25
76
|
Analyze and init read bounded project evidence without executing project code.
|
|
77
|
+
Inspect-pages discovers candidates across framework conventions without claiming
|
|
78
|
+
that they have usable content bindings. Screens sharing a URL remain screens;
|
|
79
|
+
no URL is invented for them. Review-pages records CMS-page, connected-screen or
|
|
80
|
+
native ownership intent in .universalcms/pages.review.json after confirmation.
|
|
81
|
+
This review does not change website rendering or satisfy preflight. Unsupported
|
|
82
|
+
bindings still require a supported framework adapter before installation.
|
|
83
|
+
Both commands stay local: no source upload, key access, AI or network requests.
|
|
84
|
+
Preflight checks the actual framework, local runtime and original-route plan
|
|
85
|
+
before key preparation. Use the six public connection flags from Developer setup,
|
|
86
|
+
or an existing saved public connection. It never reads private environment files,
|
|
87
|
+
executes website code, makes network requests or changes files. Review-required
|
|
88
|
+
returns a nonzero exit status. Planned does not mean installed or live verified;
|
|
89
|
+
fresh header/footer settings still require their separate installation review.
|
|
26
90
|
Init never edits existing files, installs packages, imports content or publishes.
|
|
27
91
|
Check makes one private published-page request. CMS_API_KEY comes only from the
|
|
28
92
|
process environment or a hidden terminal prompt; it is never saved. Use an
|
|
29
93
|
environment-scoped server delivery key from Developer setup, not a provider key.
|
|
30
94
|
No AI or provider credentials are required. All analysis stays on this computer.
|
|
31
|
-
|
|
32
|
-
|
|
95
|
+
Run connect, then setup. Connect saves only public values in
|
|
96
|
+
.universalcms/connection.json; setup prepares integration files, then offers
|
|
97
|
+
to save a server key through hidden input. You can defer and run server-key later.
|
|
98
|
+
Setup includes a required header/footer and website settings review. Unsupported
|
|
99
|
+
or deferred settings leave SETUP INCOMPLETE; run setup-settings --apply after a
|
|
100
|
+
reviewed adapter is ready. Original website links remain alongside saved CMS links.
|
|
101
|
+
The key is saved in Git-ignored, owner-only .env.local. No key is accepted
|
|
102
|
+
as a command argument. These steps do not verify delivery or publish content.
|
|
103
|
+
For key-first or source-only setup, use server-key --prepare-only with the public
|
|
104
|
+
connection flags from Developer setup. It saves private delivery settings before
|
|
105
|
+
an adapter exists. It does not install rendering or verify key authorization.
|
|
106
|
+
Replacing a key preserves existing website integration. Restart or deploy, then
|
|
107
|
+
run check-settings. Navigation access permits reading saved header/footer links;
|
|
108
|
+
it does not add a missing website renderer or create menu links automatically.
|
|
109
|
+
Without a saved connect record, setup retains the all-in-one guided flow with
|
|
110
|
+
hidden key input. Existing custom files and unrelated settings are preserved.
|
|
111
|
+
Developer setup provides connector installation, private-key preparation and
|
|
112
|
+
full setup --original-routes-only for reviewed original page rendering. It validates
|
|
113
|
+
the complete source/publication/binding plan before saving. Source-only preparation
|
|
114
|
+
remains a separate import utility and cannot control publication visibility.
|
|
115
|
+
After importing source into CMS drafts, run finalize-source --apply, restart or
|
|
116
|
+
deploy, then check-publication. This closes the bootstrap content export. A checked
|
|
117
|
+
adapter and route still require actual rendered-content verification in a browser.
|
|
118
|
+
Fresh full setups use the bundled renderer; existing SDK demos retain dependency checks.
|
|
119
|
+
Analyze only prints a report. It does not create /api/cms-source or connect a site.
|
|
120
|
+
Next.js setup also prepares /api/cms-source for public static page copy. New static
|
|
121
|
+
pages are discovered on each development request and the production prebuild hook.
|
|
122
|
+
Dynamic/database content, private routes and unresolved components need explicit
|
|
123
|
+
review; diagnostics never claim those are complete. React/Vite can discover a
|
|
124
|
+
mounted static App or flat literal React Router pages. Other layouts need reviewed
|
|
125
|
+
.universalcms/source.entries.json and .universalcms/content.routes.json mappings.
|
|
126
|
+
Setup validates the local framework, source and rendering before saving integration
|
|
127
|
+
files or requesting a key. Unsupported frameworks get a manual integration guide.
|
|
128
|
+
No empty export or guessed content is generated. Full Vite setup pins localhost's
|
|
129
|
+
registered port; stop any old server on that port before restarting the website.
|
|
130
|
+
Setup-source is a plan by default; review then use --apply. It uses only the four
|
|
131
|
+
public scope/origin flags, or saved source configuration; no key or AI is needed.
|
|
132
|
+
For key-first setup, add --require-server-key and the exact --api-origin. This
|
|
133
|
+
checks the saved private key and connection scope before source files are saved;
|
|
134
|
+
the key is never sent to the source endpoint and authorization remains unverified.
|
|
135
|
+
After saving, setup-source checks the live endpoint for up to 10 seconds. Saved
|
|
136
|
+
files alone do not mean a running server loaded them; stop and restart an old
|
|
137
|
+
server using the displayed project command, then run the scoped check-source.
|
|
138
|
+
Full setup checks the running publication adapter for up to 10 seconds after
|
|
139
|
+
saving. CONNECTION NOT READY means the files were saved but the running website
|
|
140
|
+
still needs attention. Restart or deploy, then run the displayed check-publication
|
|
141
|
+
before checking the connection in the CMS. Setup never stops a server or changes
|
|
142
|
+
the registered port automatically.
|
|
143
|
+
Check-source uses --website-origin, --admin-origin, --site and --environment, or
|
|
144
|
+
saved local public values. All four explicit flags bypass saved settings; no init
|
|
145
|
+
or supported scaffold framework is required. It sends no key, cookies or content
|
|
146
|
+
to the CMS and changes no files. It checks the source envelope, scope and CORS,
|
|
147
|
+
not full block-schema validation, complete inventory or actual browser access.
|
|
33
148
|
`;
|
|
34
149
|
|
|
150
|
+
const publicSourceValues = config => Object.fromEntries(['websiteOrigin', 'adminOrigin', 'siteId', 'environmentId'].map(name => [name, config[name]]));
|
|
151
|
+
const shellQuote = value => `'${value.replaceAll("'", "'\\''")}'`;
|
|
152
|
+
function sourceCheckCommand(config) {
|
|
153
|
+
return `npx --no-install universalcms-connect check-source --website-origin ${shellQuote(config.websiteOrigin)} --admin-origin ${shellQuote(config.adminOrigin)} --site ${shellQuote(config.siteId)} --environment ${shellQuote(config.environmentId)}`;
|
|
154
|
+
}
|
|
155
|
+
function publicationCheckCommand(config) {
|
|
156
|
+
return `npx --no-install universalcms-connect check-publication --website-origin ${shellQuote(config.websiteOrigin)} --admin-origin ${shellQuote(config.adminOrigin)} --site ${shellQuote(config.siteId)} --environment ${shellQuote(config.environmentId)}`;
|
|
157
|
+
}
|
|
158
|
+
async function reportReviewedSourceAlternative(project, options) {
|
|
159
|
+
try {
|
|
160
|
+
const saved = await readSetupEnvironment(project), connection = await readConnection(project), source = await readSourceSettings(project);
|
|
161
|
+
const binding = { ...saved.defaults, ...source, ...(connection ?? {}), ...Object.fromEntries(Object.entries(options).filter(([name, value]) => value !== undefined && ['apiOrigin', 'siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].includes(name))) };
|
|
162
|
+
const plan = await setupSource(project, Object.fromEntries(['siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].map(name => [name, binding[name]])));
|
|
163
|
+
if (plan.diagnostics.sourceOnly !== true || plan.diagnostics.publicationRendering !== false) return;
|
|
164
|
+
if (saved.keyConfigured) await prepareSourceKeyCheck(project, Object.fromEntries(['apiOrigin', 'siteId', 'environmentId', 'locale', 'websiteOrigin', 'adminOrigin'].map(name => [name, binding[name]])));
|
|
165
|
+
const keyFlags = saved.keyConfigured ? ` --require-server-key --api-origin ${shellQuote(binding.apiOrigin)}` : '';
|
|
166
|
+
process.stdout.write('Full setup remains unsupported for this website\'s native renderer. Its reviewed public content can be imported with the separate command below; this does not connect published CMS edits to the original design.\n');
|
|
167
|
+
process.stdout.write(`npx --no-install universalcms-connect setup-source --apply${keyFlags} --website-origin ${shellQuote(plan.config.websiteOrigin)} --admin-origin ${shellQuote(plan.config.adminOrigin)} --site ${shellQuote(plan.config.siteId)} --environment ${shellQuote(plan.config.environmentId)} --locale ${shellQuote(plan.config.locale)}\n`);
|
|
168
|
+
} catch { /* The original setup error remains authoritative. Never suggest an unvalidated alternative. */ }
|
|
169
|
+
}
|
|
170
|
+
async function sourceRestartCommand(plan) {
|
|
171
|
+
try {
|
|
172
|
+
const root = await projectRoot(plan.root);
|
|
173
|
+
const manifest = JSON.parse(await readProjectFile(root, 'package.json'));
|
|
174
|
+
const script = manifest?.scripts?.dev;
|
|
175
|
+
// Display a fixed package-manager command only for a recognized dev script.
|
|
176
|
+
// Never evaluate project scripts or copy their arbitrary shell text to output.
|
|
177
|
+
if (typeof script !== 'string') return null;
|
|
178
|
+
const vite = /^vite(?:\s+--(?:host(?:\s+(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1))?|port\s+\d{1,5}|strictPort|open|https))* --config \.universalcms\/source\.vite\.config\.mjs$/.test(script);
|
|
179
|
+
const next = /^next dev(?:\s+(?:--(?:turbo|turbopack|webpack)|(?:--port|-p)\s+\d{1,5}|(?:--hostname|-H)\s+(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)))*$/.test(script);
|
|
180
|
+
const astro = /^astro dev(?: --(?:host(?: (?:localhost|127\.0\.0\.1|0\.0\.0\.0))?|port [0-9]{1,5}|open))* --config \.universalcms\/astro\.config\.mjs$/.test(script);
|
|
181
|
+
if (!(plan.framework === 'vite' ? vite : plan.framework === 'next' ? next : plan.framework === 'astro' ? astro : vite || next || astro)) return null;
|
|
182
|
+
const declared = typeof manifest.packageManager === 'string' && /^(npm|pnpm|yarn|bun)@\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9.-]+)?$/.exec(manifest.packageManager);
|
|
183
|
+
if (manifest.packageManager !== undefined) return declared ? `${declared[1]} run dev` : null;
|
|
184
|
+
const managers = new Set();
|
|
185
|
+
for (const [filename, manager] of [['package-lock.json', 'npm'], ['npm-shrinkwrap.json', 'npm'], ['pnpm-lock.yaml', 'pnpm'], ['yarn.lock', 'yarn'], ['bun.lock', 'bun'], ['bun.lockb', 'bun']]) {
|
|
186
|
+
try {
|
|
187
|
+
const stat = await lstat(join(root, filename));
|
|
188
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return null;
|
|
189
|
+
managers.add(manager);
|
|
190
|
+
} catch (error) { if (error.code !== 'ENOENT') return null; }
|
|
191
|
+
}
|
|
192
|
+
return managers.size <= 1 ? `${[...managers][0] ?? 'npm'} run dev` : null;
|
|
193
|
+
} catch { return null; }
|
|
194
|
+
}
|
|
195
|
+
async function sourceStopInstruction(plan) {
|
|
196
|
+
let astro = plan.framework === 'astro';
|
|
197
|
+
if (!plan.framework) {
|
|
198
|
+
try { const manifest = JSON.parse(await readProjectFile(await projectRoot(plan.root), 'package.json')); astro = Boolean(manifest.dependencies?.astro ?? manifest.devDependencies?.astro); } catch { /* Keep generic restart guidance. */ }
|
|
199
|
+
}
|
|
200
|
+
return astro
|
|
201
|
+
? 'Astro 7 may run in the background. From this exact website folder run: npx --no-install astro dev stop. Ctrl+C stops a foreground server only. Confirm the registered port is free before restarting.'
|
|
202
|
+
: 'Stop the existing website server (Ctrl+C in its terminal).';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function reportSourceReadiness(plan) {
|
|
206
|
+
process.stdout.write('Checking the live source endpoint (up to 10 seconds; no credentials).\n');
|
|
207
|
+
const outcome = await checkSource(plan.root, publicSourceValues(plan.config));
|
|
208
|
+
process.stdout.write(`${outcome.checked ? 'SOURCE READY' : 'SOURCE NOT READY'} (${outcome.status}): ${outcome.message}\n`);
|
|
209
|
+
if (!outcome.checked) {
|
|
210
|
+
if (['missing-route', 'not-json'].includes(outcome.status)) process.stdout.write('Files are saved, but the running server has not loaded the source endpoint at the configured origin.\n');
|
|
211
|
+
const restart = await sourceRestartCommand(plan);
|
|
212
|
+
process.stdout.write(restart
|
|
213
|
+
? `${await sourceStopInstruction(plan)} Then from the website folder run: ${restart}\n`
|
|
214
|
+
: 'Stop the existing website server, then restart it with the project\'s normal command or deploy the updated website.\n');
|
|
215
|
+
process.stdout.write('Rerun the check below and confirm it passes before Connect & fetch content.\n');
|
|
216
|
+
}
|
|
217
|
+
process.stdout.write(`From the website folder, verify this exact scope:\n${sourceCheckCommand(plan.config)}\nPublished rendering remains unverified.\n`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function reportSetupReadiness(project) {
|
|
221
|
+
let config;
|
|
222
|
+
try {
|
|
223
|
+
config = publicSourceValues(await readSourceSettings(project));
|
|
224
|
+
if (Object.values(config).some(value => typeof value !== 'string' || !value)) throw Error();
|
|
225
|
+
} catch {
|
|
226
|
+
process.stdout.write('CONNECTION NOT READY: Integration files were saved, but their public connection settings could not be read. Review the saved setup in this website folder before checking the running website.\n');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
process.stdout.write('Integration files saved. Checking the running publication adapter (up to 10 seconds; no credentials).\n');
|
|
230
|
+
process.stdout.write('Checking the running header/footer settings adapter (up to 10 seconds; no credentials).\n');
|
|
231
|
+
const [outcome, settings] = await Promise.all([checkPublication(project, config), checkWebsiteSettings(project, config)]);
|
|
232
|
+
process.stdout.write(`${outcome.checked ? 'PUBLICATION CHECK PASSED' : 'CONNECTION NOT READY'} (${outcome.status}): ${outcome.message}\n`);
|
|
233
|
+
if (!outcome.checked) {
|
|
234
|
+
if (['missing-adapter', 'invalid-response'].includes(outcome.status)) process.stdout.write('Saved files do not prove that the server at this URL loaded them. An older server may still be serving the website. Removing integration files or reinstalling packages does not stop that process.\n');
|
|
235
|
+
const url = new URL(config.websiteOrigin);
|
|
236
|
+
if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
|
237
|
+
const restart = await sourceRestartCommand({ root: project });
|
|
238
|
+
process.stdout.write(`Use the registered website origin ${url.origin} (port ${url.port || '80'}). If the port is in use, stop the old website server in its terminal before starting this project. Do not start a second server on another port.\n`);
|
|
239
|
+
process.stdout.write(`${await sourceStopInstruction({ root: project })}\n`);
|
|
240
|
+
process.stdout.write(restart ? `From this website folder, restart with: ${restart}\n` : 'Restart with this project\'s normal website command after reviewing its server configuration.\n');
|
|
241
|
+
} else process.stdout.write('Deploy the updated integration and private server configuration to this exact website origin, then verify the running deployment.\n');
|
|
242
|
+
process.stdout.write('Run the check below after restart or deployment and confirm it passes before checking the connection in Universal CMS.\n');
|
|
243
|
+
}
|
|
244
|
+
process.stdout.write(`Verify this exact website and environment:\n${publicationCheckCommand(config)}\nA source-export check alone cannot verify publication control. Actual rendered content and access from the CMS browser still need verification. No content was imported or published.\n`);
|
|
245
|
+
const navigation = settings.sections?.navigation;
|
|
246
|
+
if (!settings.checked || navigation?.installed === false || !navigation || navigation.state === 'unavailable') {
|
|
247
|
+
process.stdout.write('HEADER/FOOTER NOT READY: The running website has not verified its navigation adapter. Run setup-settings --apply if it is missing, then restart or deploy and run check-settings. Replacing a key alone cannot install the adapter.\n');
|
|
248
|
+
} else if (navigation.state === 'denied') {
|
|
249
|
+
process.stdout.write('HEADER/FOOTER ACCESS DENIED: The current server key cannot read Navigation. Review the intended key access yourself; setup has not changed it. Restart or deploy after an authorized key update, then run check-settings.\n');
|
|
250
|
+
} else if (navigation.state === 'missing') {
|
|
251
|
+
process.stdout.write('HEADER/FOOTER ADAPTER CHECK PASSED: No navigation configuration is saved for this connection. Save the intended links in CMS Header & footer, then verify their placement in the actual website.\n');
|
|
252
|
+
} else {
|
|
253
|
+
process.stdout.write('HEADER/FOOTER ADAPTER CHECK PASSED: Navigation configuration is available. Verify saved header and footer links in the actual desktop and mobile website; this diagnostic does not verify appearance.\n');
|
|
254
|
+
}
|
|
255
|
+
if (settings.checked) {
|
|
256
|
+
const missing = WEBSITE_SECTIONS.filter(name => settings.sections?.[name]?.installed !== true);
|
|
257
|
+
if (missing.length) process.stdout.write(`SETTINGS INCOMPLETE: Website adapters are not installed for ${missing.join(', ')}. Key access alone does not install them.\n`);
|
|
258
|
+
}
|
|
259
|
+
process.stdout.write('Configuration check: npx --no-install universalcms-connect check-settings\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
35
262
|
function argumentsFor(args) {
|
|
36
263
|
const command = args.shift();
|
|
37
|
-
if (!['setup', 'analyze', 'init', 'check'].includes(command)) throw new SetupError('Choose setup, analyze, init or check. Use --help for instructions.');
|
|
38
|
-
const flags = new Set(['--apply', '--dry-run', '--non-interactive']);
|
|
39
|
-
const names = new Set(['--project', '--framework', '--api-origin', '--site', '--environment', '--locale', '--mount', '--slug', '--website-origin', '--admin-origin']);
|
|
264
|
+
if (!['inspect-pages', 'review-pages', 'preflight', 'review-routes', 'review-layout', 'connect', 'setup', 'server-key', 'setup-source', 'sync-source', 'finalize-source', 'analyze', 'init', 'check', 'check-source', 'check-publication', 'setup-settings', 'check-settings'].includes(command)) throw new SetupError('Choose inspect-pages, review-pages, preflight, review-routes, review-layout, connect, setup, setup-settings, check-settings, server-key, setup-source, sync-source, finalize-source, analyze, init, check, check-source or check-publication. Use --help for instructions.');
|
|
265
|
+
const flags = new Set(['--apply', '--dry-run', '--non-interactive', '--check-routes', '--original-routes-only', '--prepare-only', '--require-server-key', '--skip-key-validation']);
|
|
266
|
+
const names = new Set(['--project', '--framework', '--api-origin', '--site', '--environment', '--locale', '--mount', '--slug', '--path', '--website-origin', '--admin-origin']);
|
|
40
267
|
const options = {};
|
|
41
268
|
for (let index = 0; index < args.length; index++) {
|
|
42
269
|
const name = args[index];
|
|
@@ -49,7 +276,7 @@ function argumentsFor(args) {
|
|
|
49
276
|
}
|
|
50
277
|
}
|
|
51
278
|
if (options['--apply'] && options['--dry-run']) throw new SetupError('Choose either --apply or --dry-run.');
|
|
52
|
-
const allowed = command === 'setup' ? ['--project', '--api-origin', '--site', '--environment', '--locale', '--mount', '--website-origin', '--admin-origin'] : command === 'analyze' ? ['--project'] : command === 'check' ? ['--project', '--slug', '--non-interactive'] : [...flags, ...names].filter(name => !['--slug', '--website-origin', '--admin-origin'].includes(name));
|
|
279
|
+
const allowed = ['inspect-pages','review-pages'].includes(command) ? ['--project','--locale','--website-origin','--dry-run','--non-interactive'] : ['review-routes','review-layout'].includes(command) ? ['--project','--dry-run','--non-interactive'] : command === 'preflight' ? ['--project', '--api-origin', '--site', '--environment', '--locale', '--website-origin', '--admin-origin'] : command === 'setup-settings' ? ['--project','--apply','--dry-run','--non-interactive'] : command === 'check-settings' ? ['--project','--site','--environment','--website-origin','--admin-origin','--non-interactive'] : command === 'connect' ? ['--project', '--api-origin', '--site', '--environment', '--locale', '--website-origin', '--admin-origin'] : command === 'server-key' ? ['--project', '--prepare-only', '--skip-key-validation', ...(options['--prepare-only'] ? ['--api-origin', '--site', '--environment', '--locale', '--website-origin', '--admin-origin'] : [])] : command === 'setup' ? ['--project', '--api-origin', '--site', '--environment', '--locale', '--mount', '--website-origin', '--admin-origin', '--original-routes-only'] : command === 'setup-source' ? ['--project', '--site', '--environment', '--website-origin', '--admin-origin', '--locale', '--apply', '--dry-run', '--non-interactive', '--require-server-key', ...(options['--require-server-key'] ? ['--api-origin'] : [])] : command === 'sync-source' ? ['--project'] : command === 'finalize-source' ? ['--project', '--apply', '--dry-run'] : command === 'check-publication' ? ['--project', '--site', '--environment', '--website-origin', '--admin-origin', '--path', '--non-interactive'] : command === 'check-source' ? ['--project', '--site', '--environment', '--website-origin', '--admin-origin', '--non-interactive', '--check-routes'] : command === 'analyze' ? ['--project'] : command === 'check' ? ['--project', '--slug', '--non-interactive'] : [...flags, ...names].filter(name => !['--slug', '--path', '--website-origin', '--admin-origin', '--check-routes', '--original-routes-only', '--prepare-only', '--require-server-key', '--skip-key-validation'].includes(name));
|
|
53
280
|
if (Object.keys(options).some(name => !allowed.includes(name))) throw new SetupError('An option is not supported for this command.');
|
|
54
281
|
return { command, options };
|
|
55
282
|
}
|
|
@@ -60,7 +287,6 @@ async function hiddenKey(prompt = 'Server delivery key (hidden; not saved): ') {
|
|
|
60
287
|
if (!input.isTTY || typeof input.setRawMode !== 'function') return undefined;
|
|
61
288
|
const previousRaw = input.isRaw;
|
|
62
289
|
input.setRawMode(true); input.resume();
|
|
63
|
-
output.write(prompt);
|
|
64
290
|
try {
|
|
65
291
|
return await new Promise((resolve, reject) => {
|
|
66
292
|
let value = '';
|
|
@@ -83,6 +309,7 @@ async function hiddenKey(prompt = 'Server delivery key (hidden; not saved): ') {
|
|
|
83
309
|
};
|
|
84
310
|
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.once(signal, onSignal);
|
|
85
311
|
input.on('data', onData); input.on('end', onEnd);
|
|
312
|
+
output.write(prompt);
|
|
86
313
|
});
|
|
87
314
|
} finally { input.setRawMode(previousRaw); input.pause(); output.write('\n'); }
|
|
88
315
|
}
|
|
@@ -102,19 +329,195 @@ async function ask(prompt) {
|
|
|
102
329
|
} finally { input.off('SIGINT', cancel); input.off('close', cancel); input.close(); }
|
|
103
330
|
}
|
|
104
331
|
|
|
332
|
+
function guidedArguments(args) {
|
|
333
|
+
if (args[0] === 'start') args = args.slice(1);
|
|
334
|
+
const names = new Set(['--cms', '--code', '--project']), options = {};
|
|
335
|
+
for (let index = 0; index < args.length; index++) {
|
|
336
|
+
const name = args[index];
|
|
337
|
+
if (Object.hasOwn(options, name)) throw new SetupError('Each option can be given once.');
|
|
338
|
+
if (name === '--details') { options[name] = true; continue; }
|
|
339
|
+
if (!names.has(name)) throw new SetupError(`Unknown option ${JSON.stringify(name.slice(0, 40))}. Use --help for instructions.`);
|
|
340
|
+
const value = args[++index];
|
|
341
|
+
if (!value || value.startsWith('--')) throw new SetupError('An option is missing its value.');
|
|
342
|
+
options[name] = value;
|
|
343
|
+
}
|
|
344
|
+
return options;
|
|
345
|
+
}
|
|
346
|
+
|
|
105
347
|
async function main() {
|
|
106
348
|
if (process.argv.slice(2).some(value => value === '--help' || value === '-h')) { process.stdout.write(help); return; }
|
|
349
|
+
const first = process.argv[2];
|
|
350
|
+
if (first === undefined || first === 'start' || first.startsWith('--')) {
|
|
351
|
+
// The one-time pairing code is the only accepted credential-like argument:
|
|
352
|
+
// it expires in ten minutes, works once and never reaches project files.
|
|
353
|
+
const options = guidedArguments(process.argv.slice(2));
|
|
354
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('This setup needs an interactive terminal. Run it directly in your website folder.');
|
|
355
|
+
await runGuided(options['--project'] ?? process.cwd(), { cms: options['--cms'], code: options['--code'], details: options['--details'] === true }, processIo({ ask, write: value => process.stdout.write(value) }));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
107
358
|
const { command, options } = argumentsFor(process.argv.slice(2));
|
|
108
359
|
const project = options['--project'] ?? process.cwd();
|
|
360
|
+
if (command === 'inspect-pages' || command === 'review-pages') {
|
|
361
|
+
const mappingOptions = { locale: options['--locale'] ?? 'en', ...(options['--website-origin'] ? { websiteOrigin: options['--website-origin'] } : {}) };
|
|
362
|
+
if (command === 'inspect-pages' || options['--dry-run'] || options['--non-interactive']) {
|
|
363
|
+
const inventory = await inspectPageCandidates(project, mappingOptions);
|
|
364
|
+
process.stdout.write(JSON.stringify({ ...inventory, installed: false, bindingsVerified: false, liveVerified: false,
|
|
365
|
+
message: 'Discovery only. Candidates are not verified content bindings. Review-pages can record ownership intent; it does not install website routes, resolve preflight blockers, or publish content.' }, null, 2) + '\n');
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('Page mapping review requires an interactive terminal. Use inspect-pages or review-pages --dry-run to inspect without writes.');
|
|
369
|
+
await runPageMappingReview(project, { ask, write: value => process.stdout.write(value) }, mappingOptions);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (command === 'review-layout') {
|
|
373
|
+
const {nativeFramework} = await import('./native-adapters.mjs');
|
|
374
|
+
const reviewRoot = await projectRoot(project);
|
|
375
|
+
let reviewManifest; try { reviewManifest = JSON.parse(await readProjectFile(reviewRoot,'package.json')); } catch { throw new SetupError('A valid website package.json is required for layout review.'); }
|
|
376
|
+
if(nativeFramework(reviewManifest)) {
|
|
377
|
+
const {planNativeChromeReview,runNativeChromeReview} = await import('./native-chrome-review.mjs');
|
|
378
|
+
if(options['--dry-run'] || options['--non-interactive']) {
|
|
379
|
+
const plan=await planNativeChromeReview(project);
|
|
380
|
+
process.stdout.write('Native layout plan: preserve existing native links, controls and classes; add empty settings slots and a hashed review declaration.\n');
|
|
381
|
+
for(const page of plan.pages)process.stdout.write(` ${page.change}: ${page.path} (${page.slots.length} settings slots)\n`);
|
|
382
|
+
process.stdout.write('Plan only. No files changed. Run review-layout in an interactive terminal to review and save.\n');return;
|
|
383
|
+
}
|
|
384
|
+
if(!process.stdin.isTTY || !process.stderr.isTTY)throw new SetupError('Layout review requires an interactive terminal. Use --dry-run to inspect without writes.');
|
|
385
|
+
await runNativeChromeReview(project,{write:value=>process.stdout.write(value),ask});return;
|
|
386
|
+
}
|
|
387
|
+
const { planSettingsLayoutReview } = await import('./next-settings-layout.mjs');
|
|
388
|
+
const plan = await planSettingsLayoutReview(project);
|
|
389
|
+
process.stdout.write('Review existing website header/footer slots. Native controls and links are preserved; CMS settings consumers will be installed during full setup. This command saves a public review declaration only.\n');
|
|
390
|
+
for (const slot of plan.slots) process.stdout.write(` ${slot.kind}: ${slot.path} — append ${slot.kind === 'header' ? 'CMS branding, navigation and language links' : 'CMS navigation, social links and consent controls'}; preserve existing native content.\n`);
|
|
391
|
+
process.stdout.write(` Review covers ${plan.files.filter(file => file.state === 'unchanged').length} existing source/configuration files.\n`);
|
|
392
|
+
for (const file of plan.files.filter(file => ['create','update'].includes(file.state))) process.stdout.write(` ${file.state}: ${file.path}\n`);
|
|
393
|
+
if (options['--dry-run'] || options['--non-interactive']) {
|
|
394
|
+
process.stdout.write('Plan only. No files changed. Run review-layout in an interactive terminal to review and save.\n');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('Layout review requires an interactive terminal. Use --dry-run to inspect without writes.');
|
|
398
|
+
const answer = (await ask('Save this header/footer layout review? [y/N] ')).trim().toLowerCase();
|
|
399
|
+
if (!['y','yes'].includes(answer)) { process.stdout.write('Layout review cancelled. No files changed.\n'); return; }
|
|
400
|
+
if (JSON.stringify(await planSettingsLayoutReview(project)) !== JSON.stringify(plan)) throw new SetupError('The layout changed during review. No declaration was saved.');
|
|
401
|
+
await applyPlan(plan);
|
|
402
|
+
process.stdout.write('Layout review saved. Rerun scoped preflight, then complete full setup and the running-website checks. This review alone does not install an endpoint or verify settings.\n');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (command === 'review-routes') {
|
|
406
|
+
if (options['--dry-run'] || options['--non-interactive']) {
|
|
407
|
+
const {root, ...result} = await inspectNativeRouteCandidates(project);
|
|
408
|
+
process.stdout.write(JSON.stringify({ ...result, installed: false, message: 'Plan only. Run review-routes in an interactive terminal to select and confirm native routes.' }, null, 2) + '\n');
|
|
409
|
+
} else {
|
|
410
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('Route review requires an interactive terminal. Use --dry-run to inspect without writes.');
|
|
411
|
+
await runNativeRouteReview(project, { ask, write: value => process.stdout.write(value) });
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (command === 'preflight') {
|
|
416
|
+
const result = await preflightSetup(project, {
|
|
417
|
+
apiOrigin: options['--api-origin'], siteId: options['--site'], environmentId: options['--environment'],
|
|
418
|
+
locale: options['--locale'], websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'],
|
|
419
|
+
});
|
|
420
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
421
|
+
if (result.status !== 'planned') process.exitCode = 1;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (command === 'connect' || command === 'server-key') {
|
|
425
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('Connect and server-key need an interactive terminal. No credentials are accepted as command-line options.');
|
|
426
|
+
const io = { ask, hidden: hiddenKey, write: value => process.stdout.write(value) };
|
|
427
|
+
const skipValidation = options['--skip-key-validation'] === true;
|
|
428
|
+
if (command === 'server-key' && options['--prepare-only']) await runPrepareServerKey(project, {
|
|
429
|
+
apiOrigin: options['--api-origin'], siteId: options['--site'], environmentId: options['--environment'],
|
|
430
|
+
locale: options['--locale'], websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'],
|
|
431
|
+
}, io);
|
|
432
|
+
else if (command === 'server-key') await runServerKey(project, io, {
|
|
433
|
+
validateKey: !skipValidation,
|
|
434
|
+
});
|
|
435
|
+
else await runConnect(project, {
|
|
436
|
+
apiOrigin: options['--api-origin'], siteId: options['--site'], environmentId: options['--environment'],
|
|
437
|
+
locale: options['--locale'], websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'],
|
|
438
|
+
}, io);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (command === 'setup-settings') {
|
|
442
|
+
process.stdout.write(`${JSON.stringify(await setupWebsiteSettings(project, {apply: options['--apply'] === true}),null,2)}\n`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (command === 'check-settings') {
|
|
446
|
+
const result=await checkWebsiteSettings(project,{siteId:options['--site'],environmentId:options['--environment'],websiteOrigin:options['--website-origin'],adminOrigin:options['--admin-origin']});
|
|
447
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);if(!result.checked||WEBSITE_SECTIONS.some(name=>result.sections?.[name]?.installed!==true))process.exitCode=1;return;
|
|
448
|
+
}
|
|
449
|
+
if (command === 'sync-source') {
|
|
450
|
+
process.stdout.write(`${JSON.stringify(await syncSource(project))}\n`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (command === 'finalize-source') {
|
|
454
|
+
process.stdout.write(`${JSON.stringify(await finalizeSource(project, { apply: options['--apply'] === true }))}\n`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (command === 'check-publication') {
|
|
458
|
+
const result = await checkPublication(project, { websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'], siteId: options['--site'], environmentId: options['--environment'], path: options['--path'] });
|
|
459
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
460
|
+
if (!result.checked) process.exitCode = 1;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (command === 'setup-source') {
|
|
464
|
+
if (options['--require-server-key'] && !options['--api-origin']) throw new SetupError('Key-first source setup requires --api-origin from Developer setup so the prepared key cannot be rebound to a different CMS API.');
|
|
465
|
+
const saved = await readSourceSettings(project);
|
|
466
|
+
const required = [['--website-origin', 'Website origin', saved.websiteOrigin], ['--admin-origin', 'CMS admin origin', saved.adminOrigin], ['--site', 'Website UUID', saved.siteId], ['--environment', 'Environment UUID', saved.environmentId]];
|
|
467
|
+
for (const [flag, label, fallback] of required) {
|
|
468
|
+
if (!options[flag] && !fallback && !options['--non-interactive'] && process.stdin.isTTY) options[flag] = (await ask(`${label}: `)).trim();
|
|
469
|
+
}
|
|
470
|
+
const keyCheck = options['--require-server-key'] ? await prepareSourceKeyCheck(project, {
|
|
471
|
+
apiOrigin: options['--api-origin'], siteId: options['--site'] ?? saved.siteId, environmentId: options['--environment'] ?? saved.environmentId,
|
|
472
|
+
locale: options['--locale'] ?? saved.locale ?? 'en', websiteOrigin: options['--website-origin'] ?? saved.websiteOrigin, adminOrigin: options['--admin-origin'] ?? saved.adminOrigin,
|
|
473
|
+
}) : null;
|
|
474
|
+
const plan = await setupSource(project, { websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'], siteId: options['--site'], environmentId: options['--environment'], locale: options['--locale'] });
|
|
475
|
+
if (keyCheck) process.stdout.write('SERVER KEY PREPARED: Saved private settings match this source setup. Key authorization and published rendering remain unverified; no key is sent to the source endpoint.\n');
|
|
476
|
+
process.stdout.write('CONTENT IMPORT ONLY: This command prepares the public source endpoint. It does not connect published CMS edits to the original website pages, install publication checks, or request a server key.\n');
|
|
477
|
+
process.stdout.write(plan.framework === 'astro'
|
|
478
|
+
? 'Astro exports the reviewed literal public pages through its Node server. Private, dynamic or custom routes require a reviewed adapter; source exclusions are not guessed. No credentials or CMS writes are used.\n'
|
|
479
|
+
: plan.framework === 'vite' ? 'Vite export serves reviewed public pages/posts from .universalcms/source.entries.json. No credentials or CMS writes are used.\n' : 'Automatic export includes public static page copy. Exclude private paths in source.config.json; dynamic content and unresolved components need review. No credentials or CMS writes are used.\n');
|
|
480
|
+
for (const file of plan.files) process.stdout.write(` ${file.state}: ${file.path}\n`);
|
|
481
|
+
process.stdout.write(`${JSON.stringify(plan.diagnostics)}\n`);
|
|
482
|
+
if (plan.retainedAdapter) process.stdout.write('Existing source adapter retained. Its reviewed base mappings are preserved; verify that the adapter serves the refreshed automatic snapshot with check-source.\n');
|
|
483
|
+
if (options['--apply']) {
|
|
484
|
+
if (keyCheck) await keyCheck();
|
|
485
|
+
const result = await applySourcePlan(plan, keyCheck ? { beforeWrite: keyCheck } : undefined);
|
|
486
|
+
process.stdout.write(`Source setup saved: ${result.created.length} created; ${result.updated.length} updated.\n`);
|
|
487
|
+
await reportSourceReadiness(plan);
|
|
488
|
+
} else process.stdout.write('Plan only: no files changed. Review the public-content scope and run again with --apply.\n');
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
109
491
|
if (command === 'setup') {
|
|
110
492
|
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('Guided setup needs an interactive terminal. Use init for non-interactive scaffold generation.');
|
|
111
|
-
|
|
493
|
+
const setupOptions = {
|
|
494
|
+
originalRoutesOnly: options['--original-routes-only'],
|
|
112
495
|
apiOrigin: options['--api-origin'], siteId: options['--site'], environmentId: options['--environment'],
|
|
113
496
|
locale: options['--locale'], mount: options['--mount'], websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'],
|
|
114
|
-
}
|
|
497
|
+
};
|
|
498
|
+
try {
|
|
499
|
+
const result = await runSetup(project, setupOptions, { ask, hidden: hiddenKey, write: value => process.stdout.write(value) });
|
|
500
|
+
if (result.settingsInstalled === false) process.exitCode = 1;
|
|
501
|
+
else if (result.saved) await reportSetupReadiness(project);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
const blocker = setupReviewDiagnostic(error);
|
|
505
|
+
if (blocker) process.stderr.write(`${JSON.stringify({ status: 'review-required', blockers: [blocker], recovery: setupRecovery({ phase: blocker.stage, framework: 'Next.js', blocker }) }, null, 2)}\n`);
|
|
506
|
+
if (error instanceof SetupError && /custom history\/location router|Vite source export is missing|content route mapping/.test(error.message)) await reportReviewedSourceAlternative(project, setupOptions);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
115
509
|
return;
|
|
116
510
|
}
|
|
117
511
|
if (command === 'analyze') { process.stdout.write(`${JSON.stringify(await analyze(project), null, 2)}\n`); return; }
|
|
512
|
+
if (command === 'check-source') {
|
|
513
|
+
const result = await checkSource(project, {
|
|
514
|
+
websiteOrigin: options['--website-origin'], adminOrigin: options['--admin-origin'],
|
|
515
|
+
siteId: options['--site'], environmentId: options['--environment'], checkRoutes: options['--check-routes'],
|
|
516
|
+
});
|
|
517
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
518
|
+
if (!result.checked) process.exitCode = 1;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
118
521
|
if (command === 'check') {
|
|
119
522
|
if (!options['--slug']) throw new SetupError('Check requires --slug with a published page slug.');
|
|
120
523
|
const env = { CMS_API_KEY: process.env.CMS_API_KEY, CMS_API_BASE_URL: process.env.CMS_API_BASE_URL };
|