@universalcms/connect 0.13.3 → 0.13.4
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 +6 -4
- package/dist/cli.mjs +4 -3
- package/dist/guided.mjs +38 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @universalcms/connect
|
|
2
2
|
|
|
3
3
|
Local, deterministic website setup without AI. Universal CMS stays hosted; this
|
|
4
|
-
package creates a website integration, not a CMS installation. Version 0.13.
|
|
4
|
+
package creates a website integration, not a CMS installation. Version 0.13.4 is
|
|
5
5
|
prepared locally; this repository does not establish its public npm availability.
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
@@ -20,6 +20,8 @@ connection (Next.js): the CMS manages menus, branding, SEO, analytics, scripts,
|
|
|
20
20
|
consent and redirects while the site renders every page. Add
|
|
21
21
|
`<CmsNavigation location="header" />` from `@universalcms/connect/react-website`
|
|
22
22
|
where CMS menus should appear.
|
|
23
|
+
Add `--reset` to disconnect the folder first and connect it to another website
|
|
24
|
+
or organization.
|
|
23
25
|
The individual commands below remain for developers and reviewed integrations.
|
|
24
26
|
|
|
25
27
|
Supported templates: **Next.js 16 App Router on Node**, **React 19.2+ with
|
|
@@ -262,7 +264,7 @@ custom adapter still requires review, not forced overwriting.
|
|
|
262
264
|
For an operator's downloaded artifact, the installation is:
|
|
263
265
|
|
|
264
266
|
```sh
|
|
265
|
-
npm install --save-prod ./vendor/universalcms-connect-0.13.
|
|
267
|
+
npm install --save-prod ./vendor/universalcms-connect-0.13.4.tgz
|
|
266
268
|
```
|
|
267
269
|
|
|
268
270
|
Use your project's package manager. These paths are examples for downloaded
|
|
@@ -539,7 +541,7 @@ npx --no-install universalcms-connect init \
|
|
|
539
541
|
--environment 22222222-2222-4222-8222-222222222222
|
|
540
542
|
```
|
|
541
543
|
|
|
542
|
-
After a public release, `npx @universalcms/connect@0.13.
|
|
544
|
+
After a public release, `npx @universalcms/connect@0.13.4 init` is the equivalent
|
|
543
545
|
registry entry point. Copy the real IDs/API origin from **Developer setup**.
|
|
544
546
|
Missing values are prompted in a terminal; `--non-interactive` fails instead.
|
|
545
547
|
Init prints a plan. Run the same command with `--apply` after reviewing it.
|
|
@@ -589,7 +591,7 @@ The CMS button reads the website's **`/api/cms-source`** endpoint. Current Next.
|
|
|
589
591
|
prepares delivery scaffolding. React/Vite setup serves its reviewed public manifest through the normal dev command.
|
|
590
592
|
Custom exporters need their explicit adapter; a delivery check alone is insufficient.
|
|
591
593
|
|
|
592
|
-
Use the CMS's reviewed 0.13.
|
|
594
|
+
Use the CMS's reviewed 0.13.4 package for the staged commands and current source
|
|
593
595
|
discovery. Review source diagnostics and complete public
|
|
594
596
|
mappings for unsupported pages. Restart the website using its normal server command, then run:
|
|
595
597
|
|
package/dist/cli.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import { WEBSITE_SECTIONS } from './website-http.mjs';
|
|
|
20
20
|
import { setupSource, applySourcePlan, syncSource, readSourceSettings } from './source-setup.mjs';
|
|
21
21
|
import { processIo, runCreate, runGuided } from './guided.mjs';
|
|
22
22
|
|
|
23
|
-
const help = `Universal CMS connector 0.13.
|
|
23
|
+
const help = `Universal CMS connector 0.13.4
|
|
24
24
|
|
|
25
25
|
Quick start (recommended) — run in your website folder:
|
|
26
26
|
npx @universalcms/connect@latest --cms https://YOUR_CMS_ADDRESS
|
|
@@ -33,6 +33,7 @@ Quick start (recommended) — run in your website folder:
|
|
|
33
33
|
--cms <origin> Your Universal CMS address (or UNIVERSALCMS_URL)
|
|
34
34
|
--code <code> One-time code from Developer setup → Connect without a browser
|
|
35
35
|
--details Show every file and technical message
|
|
36
|
+
--reset Disconnect this folder first, then connect it to another website or organization
|
|
36
37
|
|
|
37
38
|
New website managed entirely from the CMS (pages, posts, menus, design):
|
|
38
39
|
npx @universalcms/connect@latest create my-website --cms https://YOUR_CMS_ADDRESS
|
|
@@ -338,7 +339,7 @@ function guidedArguments(args) {
|
|
|
338
339
|
for (let index = 0; index < args.length; index++) {
|
|
339
340
|
const name = args[index];
|
|
340
341
|
if (Object.hasOwn(options, name)) throw new SetupError('Each option can be given once.');
|
|
341
|
-
if (name === '--details') { options[name] = true; continue; }
|
|
342
|
+
if (name === '--details' || name === '--reset') { options[name] = true; continue; }
|
|
342
343
|
if (!names.has(name)) throw new SetupError(`Unknown option ${JSON.stringify(name.slice(0, 40))}. Use --help for instructions.`);
|
|
343
344
|
const value = args[++index];
|
|
344
345
|
if (!value || value.startsWith('--')) throw new SetupError('An option is missing its value.');
|
|
@@ -363,7 +364,7 @@ async function main() {
|
|
|
363
364
|
// it expires in ten minutes, works once and never reaches project files.
|
|
364
365
|
const options = guidedArguments(process.argv.slice(2));
|
|
365
366
|
if (!process.stdin.isTTY || !process.stderr.isTTY) throw new SetupError('This setup needs an interactive terminal. Run it directly in your website folder.');
|
|
366
|
-
await runGuided(options['--project'] ?? process.cwd(), { cms: options['--cms'], code: options['--code'], details: options['--details'] === true }, processIo({ ask, write: value => process.stdout.write(value) }));
|
|
367
|
+
await runGuided(options['--project'] ?? process.cwd(), { cms: options['--cms'], code: options['--code'], details: options['--details'] === true, reset: options['--reset'] === true }, processIo({ ask, write: value => process.stdout.write(value) }));
|
|
367
368
|
return;
|
|
368
369
|
}
|
|
369
370
|
const { command, options } = argumentsFor(process.argv.slice(2));
|
package/dist/guided.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
10
10
|
import { createServer } from 'node:http';
|
|
11
11
|
import { spawn } from 'node:child_process';
|
|
12
|
-
import { cp, lstat, mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { chmod, cp, lstat, mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { analyze, applyPlan, projectRoot, readProjectFile, SetupError } from './core.mjs';
|
|
@@ -378,6 +378,7 @@ export async function runGuided(directory, options, io) {
|
|
|
378
378
|
const analysis = await analyze(root).catch(() => ({}));
|
|
379
379
|
const framework = typeof analysis.framework === 'string' && analysis.framework !== 'Unknown' ? analysis.framework : null;
|
|
380
380
|
io.write(`✔ Found ${framework ? `a ${framework} website` : 'a website'} in ${path.basename(root)}\n`);
|
|
381
|
+
if (options.reset) await resetConnection(root, io);
|
|
381
382
|
|
|
382
383
|
// A Universal CMS theme website only needs pairing and its private key.
|
|
383
384
|
if (await readProjectFile(root, THEME_FILE) !== null) {
|
|
@@ -429,7 +430,7 @@ export async function runGuided(directory, options, io) {
|
|
|
429
430
|
: await waitForBrowserApproval({ cms, io, request: { origin: saved?.websiteOrigin ?? suggestedOrigin(manifest, framework) ?? '', project: path.basename(root).slice(0, 80), framework: framework ?? '' } });
|
|
430
431
|
let mode = 'full';
|
|
431
432
|
const mismatch = connection => saved && (saved.siteId !== connection.siteId || saved.environmentId !== connection.environmentId);
|
|
432
|
-
const different = 'This folder is already connected to a different website or environment in the CMS. Choose the same website when approving, or
|
|
433
|
+
const different = 'This folder is already connected to a different website or environment in the CMS. Choose the same website when approving, or run the command again with --reset to connect it to the other website. Nothing was changed.';
|
|
433
434
|
// Check (and safely fix) the website before redeeming, so a blocked site
|
|
434
435
|
// never leaves an unused key behind. The approval expires on its own.
|
|
435
436
|
if (approval.scope && !installed) {
|
|
@@ -471,6 +472,41 @@ export const THEME_FILE = '.universalcms/theme.json';
|
|
|
471
472
|
export const THEME_WEBSITE_FILE = '.universalcms/website.json';
|
|
472
473
|
const THEME_PORT = 3100;
|
|
473
474
|
|
|
475
|
+
const RESET_FILES = ['.universalcms/connection.json', '.universalcms/settings-only.json', '.universalcms/discovered.json', '.universalcms/website.json'];
|
|
476
|
+
const RESET_ENV = /^\s*(?:export\s+)?(CMS_API_BASE_URL|CMS_SITE_ID|CMS_ENVIRONMENT_ID|CMS_LOCALE|CMS_API_KEY|CMS_WEBSITE_ORIGIN|CMS_SOURCE_ORIGIN|CMS_ADMIN_ORIGIN)\s*=/;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* `--reset`: disconnect this folder from its CMS website so it can be connected
|
|
480
|
+
* to another website or organization. Removes only the saved connection and the
|
|
481
|
+
* private CMS_* values; generated integration code stays and is updated when
|
|
482
|
+
* the folder is connected again in the same run.
|
|
483
|
+
*/
|
|
484
|
+
export async function resetConnection(directory, io) {
|
|
485
|
+
const root = await projectRoot(directory);
|
|
486
|
+
const settingsOnly = await readProjectFile(root, SETTINGS_ONLY_FILE) !== null, theme = await readProjectFile(root, THEME_FILE) !== null;
|
|
487
|
+
const full = !settingsOnly && !theme && (await readProjectFile(root, '.universalcms/publication.config.json') !== null || await readProjectFile(root, '.universalcms/website.integration.json') !== null);
|
|
488
|
+
if (full) fail('This folder has a full page integration (pages are bound to CMS content for one website). Moving it to another website needs your developer: the page bindings and imported content belong to the current website. Nothing was changed.');
|
|
489
|
+
const present = [];
|
|
490
|
+
for (const file of RESET_FILES) if (await readProjectFile(root, file) !== null) present.push(file);
|
|
491
|
+
const envText = await readProjectFile(root, '.env.local');
|
|
492
|
+
const envLines = envText === null ? [] : envText.split(/\r?\n/).filter(line => RESET_ENV.test(line));
|
|
493
|
+
const saved = await readConnection(root).catch(() => null);
|
|
494
|
+
if (!present.length && !envLines.length) { io.write('This folder is not connected to a CMS website. Nothing to reset.\n'); return false; }
|
|
495
|
+
io.write(`\nDisconnect this folder${saved ? ` from website ${saved.siteId} (${saved.websiteOrigin})` : ''}:\n`);
|
|
496
|
+
for (const file of present) io.write(` remove: ${file}\n`);
|
|
497
|
+
if (envLines.length) io.write(` remove from .env.local: ${envLines.map(line => line.split('=')[0].replace(/^\s*(export\s+)?/, '').trim()).join(', ')}\n`);
|
|
498
|
+
io.write('Your pages, design and other settings stay as they are. You will connect it to a website right after this.\n');
|
|
499
|
+
if (!yes(await io.ask('Disconnect? [Y/n] '))) fail('Nothing was changed.');
|
|
500
|
+
for (const file of present) await rm(path.join(root, file), { force: true });
|
|
501
|
+
if (envLines.length) {
|
|
502
|
+
const kept = envText.split(/(\r?\n)/).reduce((out, part, index, parts) => { if (index % 2 === 0 && !RESET_ENV.test(part)) out.push(part + (parts[index + 1] ?? '')); return out; }, []).join('');
|
|
503
|
+
await writeFile(path.join(root, '.env.local'), kept, { mode: 0o600 });
|
|
504
|
+
await chmod(path.join(root, '.env.local'), 0o600);
|
|
505
|
+
}
|
|
506
|
+
io.write('✔ Disconnected. The old website key still works until you revoke it in that website\'s Developer setup → Advanced → website keys.\n');
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
474
510
|
/** Save the public theme connection and the private key after pairing. */
|
|
475
511
|
async function connectTheme(root, paired, io) {
|
|
476
512
|
const { connection } = paired;
|