@olonjs/cli 3.0.157 → 3.0.158
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.
|
@@ -1817,9 +1817,9 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
1817
1817
|
"@tiptap/extension-link": "^2.11.5",
|
|
1818
1818
|
"@tiptap/react": "^2.11.5",
|
|
1819
1819
|
"@tiptap/starter-kit": "^2.11.5",
|
|
1820
|
-
"@olonjs/core": "^1.1.
|
|
1821
|
-
"@olonjs/react": "^0.1.
|
|
1822
|
-
"@olonjs/studio": "^0.1.
|
|
1820
|
+
"@olonjs/core": "^1.1.26",
|
|
1821
|
+
"@olonjs/react": "^0.1.9",
|
|
1822
|
+
"@olonjs/studio": "^0.1.9",
|
|
1823
1823
|
"class-variance-authority": "^0.7.1",
|
|
1824
1824
|
"clsx": "^2.1.1",
|
|
1825
1825
|
"lucide-react": "^0.474.0",
|
|
@@ -1817,9 +1817,9 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
1817
1817
|
"@tiptap/extension-link": "^2.11.5",
|
|
1818
1818
|
"@tiptap/react": "^2.11.5",
|
|
1819
1819
|
"@tiptap/starter-kit": "^2.11.5",
|
|
1820
|
-
"@olonjs/core": "^1.1.
|
|
1821
|
-
"@olonjs/react": "^0.1.
|
|
1822
|
-
"@olonjs/studio": "^0.1.
|
|
1820
|
+
"@olonjs/core": "^1.1.26",
|
|
1821
|
+
"@olonjs/react": "^0.1.9",
|
|
1822
|
+
"@olonjs/studio": "^0.1.9",
|
|
1823
1823
|
"class-variance-authority": "^0.7.1",
|
|
1824
1824
|
"clsx": "^2.1.1",
|
|
1825
1825
|
"lucide-react": "^0.474.0",
|
|
@@ -491,10 +491,10 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
491
491
|
"dist:dna": "npm run dist"
|
|
492
492
|
},
|
|
493
493
|
"dependencies": {
|
|
494
|
-
"@olonjs/core": "^1.1.
|
|
495
|
-
"@olonjs/next": "^0.0.
|
|
496
|
-
"@olonjs/react": "^0.1.
|
|
497
|
-
"@olonjs/studio": "^0.1.
|
|
494
|
+
"@olonjs/core": "^1.1.26",
|
|
495
|
+
"@olonjs/next": "^0.0.6",
|
|
496
|
+
"@olonjs/react": "^0.1.9",
|
|
497
|
+
"@olonjs/studio": "^0.1.9",
|
|
498
498
|
"clsx": "^2.1.1",
|
|
499
499
|
"lucide-react": "^0.474.0",
|
|
500
500
|
"next": "^15.5.0",
|
|
@@ -529,6 +529,151 @@ export default config;
|
|
|
529
529
|
|
|
530
530
|
END_OF_FILE_CONTENT
|
|
531
531
|
mkdir -p "scripts"
|
|
532
|
+
echo "Creating scripts/generate-SystemsArchitect-next.test.mjs..."
|
|
533
|
+
cat << 'END_OF_FILE_CONTENT' > "scripts/generate-SystemsArchitect-next.test.mjs"
|
|
534
|
+
/**
|
|
535
|
+
* Static gates for generate_SystemsArchitect_next.sh (TDD for the Next SystemsArchitect generator).
|
|
536
|
+
* Run: node --test scripts/generate-SystemsArchitect-next.test.mjs
|
|
537
|
+
*/
|
|
538
|
+
import assert from 'node:assert/strict';
|
|
539
|
+
import fs from 'node:fs';
|
|
540
|
+
import path from 'node:path';
|
|
541
|
+
import { describe, it } from 'node:test';
|
|
542
|
+
import { fileURLToPath } from 'node:url';
|
|
543
|
+
|
|
544
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
545
|
+
const SCRIPT = path.resolve(__dirname, '../templates/generate_SystemsArchitect_next.sh');
|
|
546
|
+
|
|
547
|
+
function readScript() {
|
|
548
|
+
assert.ok(fs.existsSync(SCRIPT), `missing ${SCRIPT}`);
|
|
549
|
+
return fs.readFileSync(SCRIPT, 'utf8');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
describe('generate_SystemsArchitect_next.sh harness gates', () => {
|
|
553
|
+
it('exists and is a bash script', () => {
|
|
554
|
+
const src = readScript();
|
|
555
|
+
assert.match(src, /^#!\/usr\/bin\/env bash|^#!\/bin\/bash/m);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('must not target Vite-only surfaces', () => {
|
|
559
|
+
const src = readScript();
|
|
560
|
+
assert.doesNotMatch(src, /cat > index\.html/);
|
|
561
|
+
assert.doesNotMatch(src, /cat > src\/index\.css/);
|
|
562
|
+
assert.doesNotMatch(src, /cat > src\/App\.tsx/);
|
|
563
|
+
assert.doesNotMatch(src, /from ['"]@\/components\/ThemeProvider['"]/);
|
|
564
|
+
assert.doesNotMatch(src, /useTheme\s*\(/);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('must write Next theme bridge to app/globals.css', () => {
|
|
568
|
+
const src = readScript();
|
|
569
|
+
assert.match(src, /cat > app\/globals\.css/);
|
|
570
|
+
assert.match(src, /\[data-theme=["']light["']\]/);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('must verify Next admin wiring instead of App.tsx', () => {
|
|
574
|
+
const src = readScript();
|
|
575
|
+
assert.match(src, /AdminStudioClient/);
|
|
576
|
+
assert.doesNotMatch(src, /verifying App\.tsx/);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('must cd to tenant root (parent of templates/) before writing files', () => {
|
|
580
|
+
const src = readScript();
|
|
581
|
+
assert.match(src, /cd "\$\(cd "\$\(dirname "\$\{BASH_SOURCE\[0\]\}"\)\/\.\." && pwd\)"/);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('must clean DNA demo capsules before writing SystemsArchitect', () => {
|
|
585
|
+
const src = readScript();
|
|
586
|
+
assert.match(src, /Cleaning demo capsules/);
|
|
587
|
+
assert.match(src, /rm -rf \\\s*\n\s*src\/components\/books-list/m);
|
|
588
|
+
assert.match(src, /cat > src\/lib\/VisitorSection\.tsx/);
|
|
589
|
+
assert.doesNotMatch(src, /from '@\/components\/books-list'/);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('must install react-markdown deps used by post-detail', () => {
|
|
593
|
+
const src = readScript();
|
|
594
|
+
assert.match(src, /npm install[^\n]*react-markdown/);
|
|
595
|
+
assert.match(src, /from 'react-markdown'/);
|
|
596
|
+
assert.match(src, /from 'remark-gfm'/);
|
|
597
|
+
assert.match(src, /from 'rehype-sanitize'/);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('must not generate empty-tenant registry wiring (cleanup rm path ok)', () => {
|
|
601
|
+
const src = readScript();
|
|
602
|
+
assert.doesNotMatch(src, /@\/components\/empty-tenant/);
|
|
603
|
+
assert.doesNotMatch(src, /['"]empty-tenant['"]/);
|
|
604
|
+
assert.match(src, /src\/components\/empty-tenant/);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('must force shadcn radix base non-interactively', () => {
|
|
608
|
+
const src = readScript();
|
|
609
|
+
assert.match(src, /shadcn@latest init[^\n]*--base radix/);
|
|
610
|
+
assert.match(src, /shadcn@latest init[^\n]*--defaults/);
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
END_OF_FILE_CONTENT
|
|
615
|
+
echo "Creating scripts/generate-inkwell-next.test.mjs..."
|
|
616
|
+
cat << 'END_OF_FILE_CONTENT' > "scripts/generate-inkwell-next.test.mjs"
|
|
617
|
+
/**
|
|
618
|
+
* Static gates for generate_inkwell_next.sh (TDD for the Next Inkwell generator).
|
|
619
|
+
* Run: node --test scripts/generate-inkwell-next.test.mjs
|
|
620
|
+
*/
|
|
621
|
+
import assert from 'node:assert/strict';
|
|
622
|
+
import fs from 'node:fs';
|
|
623
|
+
import path from 'node:path';
|
|
624
|
+
import { describe, it } from 'node:test';
|
|
625
|
+
import { fileURLToPath } from 'node:url';
|
|
626
|
+
|
|
627
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
628
|
+
const SCRIPT = path.resolve(__dirname, '../templates/generate_inkwell_next.sh');
|
|
629
|
+
|
|
630
|
+
function readScript() {
|
|
631
|
+
assert.ok(fs.existsSync(SCRIPT), `missing ${SCRIPT}`);
|
|
632
|
+
return fs.readFileSync(SCRIPT, 'utf8');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
describe('generate_inkwell_next.sh harness gates', () => {
|
|
636
|
+
it('exists and is a bash script', () => {
|
|
637
|
+
const src = readScript();
|
|
638
|
+
assert.match(src, /^#!\/usr\/bin\/env bash|^#!\/bin\/bash/m);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it('must not target Vite-only surfaces', () => {
|
|
642
|
+
const src = readScript();
|
|
643
|
+
assert.doesNotMatch(src, /cat > index\.html/);
|
|
644
|
+
assert.doesNotMatch(src, /cat > src\/index\.css/);
|
|
645
|
+
assert.doesNotMatch(src, /cat > src\/App\.tsx/);
|
|
646
|
+
assert.doesNotMatch(src, /from ['"]@\/components\/ThemeProvider['"]/);
|
|
647
|
+
assert.doesNotMatch(src, /useTheme\s*\(/);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('must write Next theme bridge to app/globals.css', () => {
|
|
651
|
+
const src = readScript();
|
|
652
|
+
assert.match(src, /cat > app\/globals\.css/);
|
|
653
|
+
assert.match(src, /\[data-theme=["']light["']\]/);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('must verify Next admin wiring instead of App.tsx', () => {
|
|
657
|
+
const src = readScript();
|
|
658
|
+
assert.match(src, /AdminStudioClient/);
|
|
659
|
+
assert.doesNotMatch(src, /verifying App\.tsx/);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('must cd to tenant root (parent of templates/) before writing files', () => {
|
|
663
|
+
const src = readScript();
|
|
664
|
+
assert.match(src, /cd "\$\(cd "\$\(dirname "\$\{BASH_SOURCE\[0\]\}"\)\/\.\." && pwd\)"/);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('must clean DNA demo capsules before writing Inkwell', () => {
|
|
668
|
+
const src = readScript();
|
|
669
|
+
assert.match(src, /Cleaning demo capsules/);
|
|
670
|
+
assert.match(src, /rm -rf \\\s*\n[\s\S]*?src\/components\/books-list/m);
|
|
671
|
+
assert.match(src, /cat > src\/lib\/VisitorSection\.tsx/);
|
|
672
|
+
assert.doesNotMatch(src, /from '@\/components\/books-list'/);
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
END_OF_FILE_CONTENT
|
|
532
677
|
echo "Creating scripts/sync-pages-to-public.mjs..."
|
|
533
678
|
cat << 'END_OF_FILE_CONTENT' > "scripts/sync-pages-to-public.mjs"
|
|
534
679
|
import fs from 'fs';
|
|
@@ -580,6 +725,7 @@ console.log('[sync-pages-to-public] Synced pages, collections, and site config t
|
|
|
580
725
|
|
|
581
726
|
END_OF_FILE_CONTENT
|
|
582
727
|
mkdir -p "src"
|
|
728
|
+
# SKIP: src/App.tsx is binary and cannot be embedded as text.
|
|
583
729
|
mkdir -p "src/collections"
|
|
584
730
|
mkdir -p "src/collections/autori"
|
|
585
731
|
echo "Creating src/collections/autori/index.ts..."
|
|
@@ -827,7 +973,7 @@ import type {
|
|
|
827
973
|
} from '@olonjs/core';
|
|
828
974
|
import { AdminStudioClient } from '@/components/admin/AdminStudioClient';
|
|
829
975
|
import { useCloudSave } from '@/lib/admin/useCloudSave';
|
|
830
|
-
import {
|
|
976
|
+
import { getCloudPolicy, TENANT_ID } from '@/lib/env/tenantEnv';
|
|
831
977
|
|
|
832
978
|
const ColdSaveDrawer = lazy(() =>
|
|
833
979
|
import('@/components/admin/ColdSaveDrawer').then((m) => ({ default: m.ColdSaveDrawer })),
|
|
@@ -846,6 +992,7 @@ export type AdminStudioWithCloudProps = {
|
|
|
846
992
|
* Local save when cloud credentials are absent; cold save when Save2Repo is enabled.
|
|
847
993
|
*/
|
|
848
994
|
export function AdminStudioWithCloud(props: AdminStudioWithCloudProps) {
|
|
995
|
+
const cloudPolicy = getCloudPolicy();
|
|
849
996
|
const { cloudSaveUi, runCloudSave, closeCloudDrawer, retryCloudSave } = useCloudSave({
|
|
850
997
|
apiUrl: cloudPolicy.apiUrl,
|
|
851
998
|
apiKey: cloudPolicy.apiKey,
|
|
@@ -2766,6 +2913,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/libri/[slug].json"
|
|
|
2766
2913
|
}
|
|
2767
2914
|
|
|
2768
2915
|
END_OF_FILE_CONTENT
|
|
2916
|
+
# SKIP: src/index.css is binary and cannot be embedded as text.
|
|
2769
2917
|
mkdir -p "src/lib"
|
|
2770
2918
|
echo "Creating src/lib/CollectionRegistry.ts..."
|
|
2771
2919
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/CollectionRegistry.ts"
|
|
@@ -3458,6 +3606,58 @@ export function buildServerApiCandidates(raw: string): string[] {
|
|
|
3458
3606
|
return Array.from(new Set([withApi, base]));
|
|
3459
3607
|
}
|
|
3460
3608
|
|
|
3609
|
+
END_OF_FILE_CONTENT
|
|
3610
|
+
echo "Creating src/lib/env/tenantEnv.test.ts..."
|
|
3611
|
+
cat << 'END_OF_FILE_CONTENT' > "src/lib/env/tenantEnv.test.ts"
|
|
3612
|
+
import assert from 'node:assert/strict';
|
|
3613
|
+
import { describe, it } from 'vitest';
|
|
3614
|
+
import { getCloudPolicy, readCloudEnvFromNext } from './tenantEnv';
|
|
3615
|
+
|
|
3616
|
+
describe('readCloudEnvFromNext', () => {
|
|
3617
|
+
it('maps OLONJS public env to cloud + Save2Repo policy inputs', () => {
|
|
3618
|
+
const input = readCloudEnvFromNext({
|
|
3619
|
+
NEXT_PUBLIC_OLONJS_CLOUD_URL: ' https://api.example.com ',
|
|
3620
|
+
NEXT_PUBLIC_OLONJS_API_KEY: ' key ',
|
|
3621
|
+
NEXT_PUBLIC_OLONJS_SAVE2REPO: 'true',
|
|
3622
|
+
});
|
|
3623
|
+
assert.equal(input.apiUrl, 'https://api.example.com');
|
|
3624
|
+
assert.equal(input.apiKey, 'key');
|
|
3625
|
+
assert.equal(input.save2RepoFlag, true);
|
|
3626
|
+
});
|
|
3627
|
+
|
|
3628
|
+
it('treats SAVE2REPO other than exact "true" as off', () => {
|
|
3629
|
+
const input = readCloudEnvFromNext({
|
|
3630
|
+
NEXT_PUBLIC_OLONJS_CLOUD_URL: 'https://api.example.com',
|
|
3631
|
+
NEXT_PUBLIC_OLONJS_API_KEY: 'key',
|
|
3632
|
+
NEXT_PUBLIC_OLONJS_SAVE2REPO: '1',
|
|
3633
|
+
});
|
|
3634
|
+
assert.equal(input.save2RepoFlag, false);
|
|
3635
|
+
});
|
|
3636
|
+
});
|
|
3637
|
+
|
|
3638
|
+
describe('getCloudPolicy', () => {
|
|
3639
|
+
it('enables cold save and disables local save when credentials + Save2Repo', () => {
|
|
3640
|
+
const policy = getCloudPolicy({
|
|
3641
|
+
NEXT_PUBLIC_OLONJS_CLOUD_URL: 'https://api.example.com',
|
|
3642
|
+
NEXT_PUBLIC_OLONJS_API_KEY: 'key',
|
|
3643
|
+
NEXT_PUBLIC_OLONJS_SAVE2REPO: 'true',
|
|
3644
|
+
});
|
|
3645
|
+
assert.equal(policy.isCloudMode, true);
|
|
3646
|
+
assert.equal(policy.bootSource, 'static');
|
|
3647
|
+
assert.equal(policy.showLocalSave, false);
|
|
3648
|
+
assert.equal(policy.showColdSave, true);
|
|
3649
|
+
});
|
|
3650
|
+
|
|
3651
|
+
it('stays local when credentials are missing', () => {
|
|
3652
|
+
const policy = getCloudPolicy({
|
|
3653
|
+
NEXT_PUBLIC_OLONJS_SAVE2REPO: 'true',
|
|
3654
|
+
});
|
|
3655
|
+
assert.equal(policy.isCloudMode, false);
|
|
3656
|
+
assert.equal(policy.showLocalSave, true);
|
|
3657
|
+
assert.equal(policy.showColdSave, false);
|
|
3658
|
+
});
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3461
3661
|
END_OF_FILE_CONTENT
|
|
3462
3662
|
echo "Creating src/lib/env/tenantEnv.ts..."
|
|
3463
3663
|
cat << 'END_OF_FILE_CONTENT' > "src/lib/env/tenantEnv.ts"
|
|
@@ -3468,18 +3668,43 @@ import { resolveCloudPolicy, type CloudEnvInput, type CloudPolicy } from '@olonj
|
|
|
3468
3668
|
* Prefer NEXT_PUBLIC_OLONJS_* ; also accept NEXT_PUBLIC_JSONPAGES_* and
|
|
3469
3669
|
* NEXT_PUBLIC_SAVE2REPO (alpha-style flag name under the Next public prefix).
|
|
3470
3670
|
*
|
|
3471
|
-
*
|
|
3472
|
-
*
|
|
3671
|
+
* CRITICAL: default path MUST use direct `process.env.NEXT_PUBLIC_*` property
|
|
3672
|
+
* access so the Next client bundler can inline values at build time.
|
|
3673
|
+
* Indexing via `const env = process.env; env.NEXT_PUBLIC_…` stays empty in the
|
|
3674
|
+
* browser → Studio always thinks it is Local → save-to-file → EROFS on Vercel.
|
|
3675
|
+
*
|
|
3676
|
+
* Pass `env` only in unit tests.
|
|
3473
3677
|
*/
|
|
3474
3678
|
export function readCloudEnvFromNext(
|
|
3475
|
-
env
|
|
3679
|
+
env?: Record<string, string | undefined>,
|
|
3476
3680
|
): CloudEnvInput {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3681
|
+
if (env) {
|
|
3682
|
+
const apiUrl =
|
|
3683
|
+
(env.NEXT_PUBLIC_OLONJS_CLOUD_URL ?? env.NEXT_PUBLIC_JSONPAGES_CLOUD_URL ?? '').trim();
|
|
3684
|
+
const apiKey =
|
|
3685
|
+
(env.NEXT_PUBLIC_OLONJS_API_KEY ?? env.NEXT_PUBLIC_JSONPAGES_API_KEY ?? '').trim();
|
|
3686
|
+
const save2RepoRaw =
|
|
3687
|
+
env.NEXT_PUBLIC_OLONJS_SAVE2REPO ?? env.NEXT_PUBLIC_SAVE2REPO ?? '';
|
|
3688
|
+
return {
|
|
3689
|
+
apiUrl,
|
|
3690
|
+
apiKey,
|
|
3691
|
+
save2RepoFlag: save2RepoRaw === 'true',
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
const apiUrl = (
|
|
3696
|
+
process.env.NEXT_PUBLIC_OLONJS_CLOUD_URL ??
|
|
3697
|
+
process.env.NEXT_PUBLIC_JSONPAGES_CLOUD_URL ??
|
|
3698
|
+
''
|
|
3699
|
+
).trim();
|
|
3700
|
+
const apiKey = (
|
|
3701
|
+
process.env.NEXT_PUBLIC_OLONJS_API_KEY ??
|
|
3702
|
+
process.env.NEXT_PUBLIC_JSONPAGES_API_KEY ??
|
|
3703
|
+
''
|
|
3704
|
+
).trim();
|
|
3481
3705
|
const save2RepoRaw =
|
|
3482
|
-
env.NEXT_PUBLIC_OLONJS_SAVE2REPO ?? env.NEXT_PUBLIC_SAVE2REPO ?? '';
|
|
3706
|
+
process.env.NEXT_PUBLIC_OLONJS_SAVE2REPO ?? process.env.NEXT_PUBLIC_SAVE2REPO ?? '';
|
|
3707
|
+
|
|
3483
3708
|
return {
|
|
3484
3709
|
apiUrl,
|
|
3485
3710
|
apiKey,
|
|
@@ -3487,8 +3712,13 @@ export function readCloudEnvFromNext(
|
|
|
3487
3712
|
};
|
|
3488
3713
|
}
|
|
3489
3714
|
|
|
3490
|
-
/**
|
|
3491
|
-
export
|
|
3715
|
+
/** Resolve policy (call from client components; do not cache across env shapes in tests). */
|
|
3716
|
+
export function getCloudPolicy(env?: Record<string, string | undefined>): CloudPolicy {
|
|
3717
|
+
return resolveCloudPolicy(readCloudEnvFromNext(env));
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
/** Module policy for the Next admin island (build-time inlined NEXT_PUBLIC_*). */
|
|
3721
|
+
export const cloudPolicy: CloudPolicy = getCloudPolicy();
|
|
3492
3722
|
|
|
3493
3723
|
export const CLOUD_API_URL = cloudPolicy.apiUrl;
|
|
3494
3724
|
export const CLOUD_API_KEY = cloudPolicy.apiKey;
|