@jsonpages/cli 3.0.35 → 3.0.37

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.
@@ -27,6 +27,152 @@ END_OF_FILE_CONTENT
27
27
  mkdir -p "src"
28
28
  echo "Creating src/App.tsx..."
29
29
  cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
30
+ /**
31
+ * Thin Entry Point (Tenant).
32
+ * Data from getHydratedData (file-backed or draft); assets from public/assets/images.
33
+ * Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
34
+ */
35
+ import { useState, useEffect } from 'react';
36
+ import { JsonPagesEngine } from '@jsonpages/core';
37
+ import type { LibraryImageEntry } from '@jsonpages/core';
38
+ import { ComponentRegistry } from '@/lib/ComponentRegistry';
39
+ import { SECTION_SCHEMAS } from '@/lib/schemas';
40
+ import { addSectionConfig } from '@/lib/addSectionConfig';
41
+ import { getHydratedData } from '@/lib/draftStorage';
42
+ import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
43
+ import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
44
+
45
+ import siteData from '@/data/config/site.json';
46
+ import themeData from '@/data/config/theme.json';
47
+ import menuData from '@/data/config/menu.json';
48
+ import { getFilePages } from '@/lib/getFilePages';
49
+
50
+ import fontsCss from './fonts.css?inline';
51
+ import tenantCss from './index.css?inline';
52
+
53
+ // Cloud Configuration (Injected by Vercel/Netlify Env Vars)
54
+ const CLOUD_API_URL = import.meta.env.VITE_JSONPAGES_CLOUD_URL;
55
+ const CLOUD_API_KEY = import.meta.env.VITE_JSONPAGES_API_KEY;
56
+
57
+ const themeConfig = themeData as unknown as ThemeConfig;
58
+ const menuConfig = menuData as unknown as MenuConfig;
59
+ const TENANT_ID = 'alpha';
60
+
61
+ const filePages = getFilePages();
62
+ const fileSiteConfig = siteData as unknown as SiteConfig;
63
+ const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
64
+
65
+ function getInitialData() {
66
+ return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
67
+ }
68
+
69
+ function App() {
70
+ const [{ pages, siteConfig }] = useState(getInitialData);
71
+ const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
72
+
73
+ useEffect(() => {
74
+ // In Cloud mode, listing assets might be different or disabled for MVP
75
+ // For now, we keep the local fetch which will fail gracefully on Vercel (404)
76
+ fetch('/api/list-assets')
77
+ .then((r) => (r.ok ? r.json() : []))
78
+ .then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
79
+ .catch(() => setAssetsManifest([]));
80
+ }, []);
81
+
82
+ console.log("🔍 DEBUG ENV:", {
83
+ URL: import.meta.env.VITE_JSONPAGES_CLOUD_URL,
84
+ KEY: import.meta.env.VITE_JSONPAGES_API_KEY ? "PRESENT" : "MISSING"
85
+ });
86
+ const config: JsonPagesConfig = {
87
+ tenantId: TENANT_ID,
88
+ registry: ComponentRegistry as JsonPagesConfig['registry'],
89
+ schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
90
+ pages,
91
+ siteConfig,
92
+ themeConfig,
93
+ menuConfig,
94
+ themeCss: { tenant: fontsCss + '\n' + tenantCss },
95
+ addSection: addSectionConfig,
96
+ persistence: {
97
+ async saveToFile(state: ProjectState, slug: string): Promise<void> {
98
+
99
+ // ☁️ SCENARIO A: CLOUD BRIDGE (Production)
100
+ if (CLOUD_API_URL && CLOUD_API_KEY) {
101
+ console.log(`☁️ Saving ${slug} via Cloud Bridge...`);
102
+
103
+ const res = await fetch(`${CLOUD_API_URL}/save`, {
104
+ method: 'POST',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'Authorization': `Bearer ${CLOUD_API_KEY}`
108
+ },
109
+ body: JSON.stringify({
110
+ // Mapping logical slug to physical path in repo
111
+ path: `src/data/pages/${slug}.json`,
112
+ // We save the page config specifically
113
+ content: state.page,
114
+ message: `Content update for ${slug} via Visual Editor`
115
+ }),
116
+ });
117
+
118
+ if (!res.ok) {
119
+ const err = await res.json().catch(() => ({}));
120
+ throw new Error(err.error || `Cloud save failed: ${res.status}`);
121
+ }
122
+ return;
123
+ }
124
+
125
+ // 💻 SCENARIO B: LOCAL FILESYSTEM (Development)
126
+ console.log(`💻 Saving ${slug} to Local Filesystem...`);
127
+ const res = await fetch('/api/save-to-file', {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({ projectState: state, slug }),
131
+ });
132
+
133
+ const body = (await res.json().catch(() => ({}))) as { error?: string };
134
+ if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
135
+ },
136
+ },
137
+ assets: {
138
+ assetsBaseUrl: '/assets',
139
+ assetsManifest,
140
+ async onAssetUpload(file: File): Promise<string> {
141
+ // Note: Asset upload in Cloud Mode requires the R2 Bridge (Next Step in Roadmap)
142
+ // For now, this works in Local Mode.
143
+ if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
144
+ if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
145
+
146
+ const base64 = await new Promise<string>((resolve, reject) => {
147
+ const reader = new FileReader();
148
+ reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
149
+ reader.onerror = () => reject(reader.error);
150
+ reader.readAsDataURL(file);
151
+ });
152
+
153
+ const res = await fetch('/api/upload-asset', {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
157
+ });
158
+
159
+ const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
160
+ if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
161
+ if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
162
+ return body.url;
163
+ },
164
+ },
165
+ };
166
+
167
+ return <JsonPagesEngine config={config} />;
168
+ }
169
+
170
+ export default App;
171
+
172
+ END_OF_FILE_CONTENT
173
+ # SKIP: src/App.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
174
+ echo "Creating src/App_.tsx..."
175
+ cat << 'END_OF_FILE_CONTENT' > "src/App_.tsx"
30
176
  import { useState, useEffect } from 'react';
31
177
  import { JsonPagesEngine } from '@jsonpages/core';
32
178
  import type { LibraryImageEntry } from '@jsonpages/core';
@@ -3448,24 +3594,50 @@ echo "Creating src/data/config/menu.json..."
3448
3594
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/menu.json"
3449
3595
  {
3450
3596
  "main": [
3451
- { "label": "Architecture", "href": "#architecture" },
3452
- { "label": "CMS", "href": "#cms" },
3453
- { "label": "Versioning", "href": "#git" },
3454
- { "label": "Developer", "href": "#devex" }
3597
+ {
3598
+ "label": "Architecture",
3599
+ "href": "#architecture"
3600
+ },
3601
+ {
3602
+ "label": "CMS",
3603
+ "href": "#cms"
3604
+ },
3605
+ {
3606
+ "label": "Versioning",
3607
+ "href": "#git"
3608
+ },
3609
+ {
3610
+ "label": "Developer",
3611
+ "href": "#devex"
3612
+ }
3455
3613
  ]
3456
3614
  }
3457
-
3458
3615
  END_OF_FILE_CONTENT
3459
3616
  # SKIP: src/data/config/menu.json:Zone.Identifier è un file binario e non può essere convertito in testo.
3460
3617
  echo "Creating src/data/config/site.json..."
3461
3618
  cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
3462
3619
  {
3463
- "identity": { "title": "JsonPages", "logoUrl": "/logo.svg" },
3620
+ "identity": {
3621
+ "title": "JsonPages",
3622
+ "logoUrl": "/logo.svg"
3623
+ },
3464
3624
  "pages": [
3465
- { "slug": "home", "label": "Home" },
3466
- { "slug": "docs", "label": "Docs" },
3467
- { "slug": "architecture", "label": "Architecture" },
3468
- { "slug": "usage", "label": "Usage" }
3625
+ {
3626
+ "slug": "home",
3627
+ "label": "Home"
3628
+ },
3629
+ {
3630
+ "slug": "docs",
3631
+ "label": "Docs"
3632
+ },
3633
+ {
3634
+ "slug": "architecture",
3635
+ "label": "Architecture"
3636
+ },
3637
+ {
3638
+ "slug": "usage",
3639
+ "label": "Usage"
3640
+ }
3469
3641
  ],
3470
3642
  "header": {
3471
3643
  "id": "global-header",
@@ -3475,14 +3647,27 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
3475
3647
  "logoHighlight": "Pages",
3476
3648
  "logoIconText": "{ }",
3477
3649
  "links": [
3478
- { "label": "Architecture", "href": "#architecture" },
3479
- { "label": "CMS", "href": "#cms" },
3480
- { "label": "Versioning", "href": "#git" },
3481
- { "label": "Developer", "href": "#devex" },
3482
- { "label": "Docs", "href": "/docs" }
3650
+ {
3651
+ "label": "Architecture",
3652
+ "href": "#architecture"
3653
+ },
3654
+ {
3655
+ "label": "CMS",
3656
+ "href": "#cms"
3657
+ },
3658
+ {
3659
+ "label": "Versioning",
3660
+ "href": "#git"
3661
+ },
3662
+ {
3663
+ "label": "Developer",
3664
+ "href": "#devex"
3665
+ }
3483
3666
  ]
3484
3667
  },
3485
- "settings": { "sticky": true }
3668
+ "settings": {
3669
+ "sticky": true
3670
+ }
3486
3671
  },
3487
3672
  "footer": {
3488
3673
  "id": "global-footer",
@@ -3492,16 +3677,29 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/site.json"
3492
3677
  "brandHighlight": "Pages",
3493
3678
  "copyright": "© 2026 JsonPages. Source-available · Free to use.",
3494
3679
  "links": [
3495
- { "label": "Docs", "href": "/docs" },
3496
- { "label": "NPM", "href": "#" },
3497
- { "label": "GitHub", "href": "#" },
3498
- { "label": "Changelog", "href": "#" }
3680
+ {
3681
+ "label": "Docs",
3682
+ "href": "/docs"
3683
+ },
3684
+ {
3685
+ "label": "NPM",
3686
+ "href": "#"
3687
+ },
3688
+ {
3689
+ "label": "GitHub",
3690
+ "href": "#"
3691
+ },
3692
+ {
3693
+ "label": "Changelog",
3694
+ "href": "#"
3695
+ }
3499
3696
  ]
3500
3697
  },
3501
- "settings": { "showLogo": true }
3698
+ "settings": {
3699
+ "showLogo": true
3700
+ }
3502
3701
  }
3503
3702
  }
3504
-
3505
3703
  END_OF_FILE_CONTENT
3506
3704
  # SKIP: src/data/config/site.json:Zone.Identifier è un file binario e non può essere convertito in testo.
3507
3705
  echo "Creating src/data/config/theme.json..."
@@ -3510,20 +3708,20 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
3510
3708
  "name": "JsonPages Landing",
3511
3709
  "tokens": {
3512
3710
  "colors": {
3513
- "primary": "#3b82f6",
3514
- "secondary": "#22d3ee",
3515
- "accent": "#60a5fa",
3516
- "background": "#060d1b",
3517
- "surface": "#0b1529",
3518
- "surfaceAlt": "#101e38",
3519
- "text": "#e2e8f0",
3520
- "textMuted": "#94a3b8",
3521
- "border": "#162a4d"
3711
+ "primary": "#3b82f6",
3712
+ "secondary": "#22d3ee",
3713
+ "accent": "#60a5fa",
3714
+ "background": "#060d1b",
3715
+ "surface": "#0b1529",
3716
+ "surfaceAlt": "#101e38",
3717
+ "text": "#e2e8f0",
3718
+ "textMuted": "#94a3b8",
3719
+ "border": "#162a4d"
3522
3720
  },
3523
3721
  "typography": {
3524
3722
  "fontFamily": {
3525
3723
  "primary": "'Instrument Sans', system-ui, sans-serif",
3526
- "mono": "'JetBrains Mono', monospace",
3724
+ "mono": "'JetBrains Mono', monospace",
3527
3725
  "display": "'Bricolage Grotesque', system-ui, sans-serif"
3528
3726
  }
3529
3727
  },
@@ -3534,7 +3732,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
3534
3732
  }
3535
3733
  }
3536
3734
  }
3537
-
3538
3735
  END_OF_FILE_CONTENT
3539
3736
  # SKIP: src/data/config/theme.json:Zone.Identifier è un file binario e non può essere convertito in testo.
3540
3737
  mkdir -p "src/data/pages"
@@ -3912,13 +4109,35 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
3912
4109
  "titleHighlight": "Zero Runtime Overhead.",
3913
4110
  "description": "The @jsonpages/core package is a headless, schema-driven runtime. It handles routing, hydration, and the admin interface, leaving your Tenant code pure and framework-agnostic.",
3914
4111
  "ctas": [
3915
- { "id": "cta-1", "label": "Read the Docs", "href": "#devex", "variant": "primary" },
3916
- { "id": "cta-2", "label": "View on NPM", "href": "#", "variant": "secondary" }
4112
+ {
4113
+ "id": "cta-1",
4114
+ "label": "Read the Docs",
4115
+ "href": "#devex",
4116
+ "variant": "primary"
4117
+ },
4118
+ {
4119
+ "id": "cta-2",
4120
+ "label": "View on NPM",
4121
+ "href": "#",
4122
+ "variant": "secondary"
4123
+ }
3917
4124
  ],
3918
4125
  "metrics": [
3919
- { "id": "m-1", "val": "3KB", "label": "Core Overhead" },
3920
- { "id": "m-2", "val": "100%", "label": "Type Safety" },
3921
- { "id": "m-3", "val": "MIT", "label": "License" }
4126
+ {
4127
+ "id": "m-1",
4128
+ "val": "3KB",
4129
+ "label": "Core Overhead"
4130
+ },
4131
+ {
4132
+ "id": "m-2",
4133
+ "val": "100%",
4134
+ "label": "Type Safety"
4135
+ },
4136
+ {
4137
+ "id": "m-3",
4138
+ "val": "MIT",
4139
+ "label": "License"
4140
+ }
3922
4141
  ]
3923
4142
  },
3924
4143
  "settings": {}
@@ -3999,11 +4218,26 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
3999
4218
  "titleHighlight": "Branch it. Diff it. Roll it back.",
4000
4219
  "description": "Because every page is a plain JSON file, your content workflow is identical to your code workflow. No database exports, no CMS snapshots, no proprietary backups. Just git.",
4001
4220
  "points": [
4002
- { "id": "p-1", "text": "Content changes appear in pull requests like any code change" },
4003
- { "id": "p-2", "text": "Branch a page, preview it, merge when approved" },
4004
- { "id": "p-3", "text": "Full diff history on every field of every section" },
4005
- { "id": "p-4", "text": "Roll back any page to any commit in seconds" },
4006
- { "id": "p-5", "text": "CI/CD pipelines trigger on content changes, not just code" }
4221
+ {
4222
+ "id": "p-1",
4223
+ "text": "Content changes appear in pull requests like any code change"
4224
+ },
4225
+ {
4226
+ "id": "p-2",
4227
+ "text": "Branch a page, preview it, merge when approved"
4228
+ },
4229
+ {
4230
+ "id": "p-3",
4231
+ "text": "Full diff history on every field of every section"
4232
+ },
4233
+ {
4234
+ "id": "p-4",
4235
+ "text": "Roll back any page to any commit in seconds"
4236
+ },
4237
+ {
4238
+ "id": "p-5",
4239
+ "text": "CI/CD pipelines trigger on content changes, not just code"
4240
+ }
4007
4241
  ]
4008
4242
  },
4009
4243
  "settings": {}
@@ -4017,9 +4251,18 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
4017
4251
  "title": "Your App.tsx is incredibly thin.",
4018
4252
  "description": "You don't write routing logic. You don't write admin panels. You just import the Engine and pass your configuration.",
4019
4253
  "features": [
4020
- { "id": "f-1", "text": "Automatic Routing based on JSON files" },
4021
- { "id": "f-2", "text": "Hot Module Replacement (HMR)" },
4022
- { "id": "f-3", "text": "Full TypeScript support" }
4254
+ {
4255
+ "id": "f-1",
4256
+ "text": "Automatic Routing based on JSON files"
4257
+ },
4258
+ {
4259
+ "id": "f-2",
4260
+ "text": "Hot Module Replacement (HMR)"
4261
+ },
4262
+ {
4263
+ "id": "f-3",
4264
+ "text": "Full TypeScript support"
4265
+ }
4023
4266
  ]
4024
4267
  },
4025
4268
  "settings": {}
@@ -4033,9 +4276,24 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
4033
4276
  "title": "A new tenant in under 5 minutes.",
4034
4277
  "description": "Install the CLI once. From there, scaffolding a fully-typed, production-ready tenant — with all capsules, registry, schemas, and config wired up — is a single command.",
4035
4278
  "steps": [
4036
- { "id": "s-1", "num": "1", "title": "Install the CLI globally", "description": "One-time install. The CLI lives alongside your other dev tools." },
4037
- { "id": "s-2", "num": "2", "title": "Scaffold a new tenant", "description": "Generates the full TBP structure: capsules, registry, schemas, config, and data files — all typed and ready." },
4038
- { "id": "s-3", "num": "3", "title": "Start building", "description": "Run npm run dev and your tenant is live with Studio, HMR, and full CMS editing." }
4279
+ {
4280
+ "id": "s-1",
4281
+ "num": "1",
4282
+ "title": "Install the CLI globally",
4283
+ "description": "One-time install. The CLI lives alongside your other dev tools."
4284
+ },
4285
+ {
4286
+ "id": "s-2",
4287
+ "num": "2",
4288
+ "title": "Scaffold a new tenant",
4289
+ "description": "Generates the full TBP structure: capsules, registry, schemas, config, and data files — all typed and ready."
4290
+ },
4291
+ {
4292
+ "id": "s-3",
4293
+ "num": "3",
4294
+ "title": "Start building",
4295
+ "description": "Run npm run dev and your tenant is live with Studio, HMR, and full CMS editing."
4296
+ }
4039
4297
  ]
4040
4298
  },
4041
4299
  "settings": {}
@@ -4049,15 +4307,24 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
4049
4307
  "description": "Stop building page builders. Start building Systems.",
4050
4308
  "cliCommand": "npx @jsonpages/cli@latest new tenant",
4051
4309
  "ctas": [
4052
- { "id": "cta-docs", "label": "Read the Docs", "href": "#devex", "variant": "primary" },
4053
- { "id": "cta-npm", "label": "View on NPM", "href": "#", "variant": "secondary" }
4310
+ {
4311
+ "id": "cta-docs",
4312
+ "label": "Read the Docs",
4313
+ "href": "#devex",
4314
+ "variant": "primary"
4315
+ },
4316
+ {
4317
+ "id": "cta-npm",
4318
+ "label": "View on NPM",
4319
+ "href": "#",
4320
+ "variant": "secondary"
4321
+ }
4054
4322
  ]
4055
4323
  },
4056
4324
  "settings": {}
4057
4325
  }
4058
4326
  ]
4059
4327
  }
4060
-
4061
4328
  END_OF_FILE_CONTENT
4062
4329
  # SKIP: src/data/pages/home.json:Zone.Identifier è un file binario e non può essere convertito in testo.
4063
4330
  echo "Creating src/fonts.css..."
@@ -4601,6 +4868,25 @@ export function cn(...inputs: ClassValue[]) {
4601
4868
  END_OF_FILE_CONTENT
4602
4869
  echo "Creating src/main.tsx..."
4603
4870
  cat << 'END_OF_FILE_CONTENT' > "src/main.tsx"
4871
+ import '@/types'; // TBP: load type augmentation from capsule-driven types
4872
+ import React from 'react';
4873
+ import ReactDOM from 'react-dom/client';
4874
+ import App from './App';
4875
+ // ... resto del file
4876
+
4877
+ ReactDOM.createRoot(document.getElementById('root')!).render(
4878
+ <React.StrictMode>
4879
+ <App />
4880
+ </React.StrictMode>
4881
+ );
4882
+
4883
+
4884
+
4885
+
4886
+ END_OF_FILE_CONTENT
4887
+ # SKIP: src/main.tsx:Zone.Identifier è un file binario e non può essere convertito in testo.
4888
+ echo "Creating src/main_.tsx..."
4889
+ cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
4604
4890
  import '@/types'; // TBP: load type augmentation from capsule-driven types
4605
4891
  import React from 'react';
4606
4892
  import ReactDOM from 'react-dom/client';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonpages/cli",
3
- "version": "3.0.35",
3
+ "version": "3.0.37",
4
4
  "description": "JsonPages CLI - Sovereign Projection Engine",
5
5
  "type": "module",
6
6
  "bin": {