@jsonpages/cli 3.0.25 → 3.0.28

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.
@@ -29,7 +29,7 @@ echo "Creating src/App.tsx..."
29
29
  cat << 'END_OF_FILE_CONTENT' > "src/App.tsx"
30
30
  /**
31
31
  * Thin Entry Point (Tenant).
32
- * Loads from localStorage draft when present (same key as Core autosave), else from file.
32
+ * Data from getHydratedData (file-backed or draft); assets from public/assets/images.
33
33
  */
34
34
  import { useState, useEffect } from 'react';
35
35
  import { JsonPagesEngine } from '@jsonpages/core';
@@ -41,13 +41,11 @@ import { getHydratedData } from '@/lib/draftStorage';
41
41
  import type { JsonPagesConfig } from '@jsonpages/core';
42
42
  import type { PageConfig, SiteConfig, ThemeConfig, MenuConfig } from '@/types';
43
43
 
44
- // Tenant data (file-backed fallback)
45
44
  import siteData from '@/data/config/site.json';
46
45
  import themeData from '@/data/config/theme.json';
47
46
  import menuData from '@/data/config/menu.json';
48
47
  import homeData from '@/data/pages/home.json';
49
48
 
50
- // Tenant CSS
51
49
  import tenantCss from './index.css?inline';
52
50
 
53
51
  const themeConfig = themeData as unknown as ThemeConfig;
@@ -59,6 +57,8 @@ const filePages: Record<string, PageConfig> = {
59
57
  };
60
58
  const fileSiteConfig = siteData as unknown as SiteConfig;
61
59
 
60
+ const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024; // 5MB, must match server
61
+
62
62
  function getInitialData() {
63
63
  return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
64
64
  }
@@ -67,17 +67,6 @@ function App() {
67
67
  const [{ pages, siteConfig }] = useState(getInitialData);
68
68
  const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
69
69
 
70
- // #region agent log
71
- useEffect(() => {
72
- fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx',message:'App mounted',data:{},timestamp:Date.now(),hypothesisId:'H1,H3'})}).catch(()=>{});
73
- const onSubmit = (e: Event) => {
74
- fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx:submit',message:'form submit captured',data:{targetTag:(e.target as HTMLElement)?.tagName},timestamp:Date.now(),hypothesisId:'H4'})}).catch(()=>{});
75
- };
76
- document.addEventListener('submit', onSubmit, true);
77
- return () => document.removeEventListener('submit', onSubmit, true);
78
- }, []);
79
- // #endregion
80
-
81
70
  useEffect(() => {
82
71
  fetch('/api/list-assets')
83
72
  .then((r) => (r.ok ? r.json() : []))
@@ -95,51 +84,45 @@ function App() {
95
84
  menuConfig,
96
85
  themeCss: { tenant: tenantCss },
97
86
  addSection: addSectionConfig,
98
- persistence: {
99
- async flushUploadedAssets(urls: string[]): Promise<Record<string, string>> {
100
- const res = await fetch('/api/flush-uploaded-assets', {
101
- method: 'POST',
102
- headers: { 'Content-Type': 'application/json' },
103
- body: JSON.stringify({ urls }),
104
- });
105
- if (!res.ok) throw new Error(`Flush failed: ${res.status}`);
106
- const { urlMap } = (await res.json()) as { urlMap?: Record<string, string> };
107
- return urlMap ?? {};
108
- },
109
- },
110
87
  assets: {
111
88
  assetsBaseUrl: '/assets',
112
89
  assetsManifest,
113
90
  async onAssetUpload(file: File): Promise<string> {
114
- // #region agent log
115
- fetch('http://127.0.0.1:7588/ingest/86d71502-47e1-433c-9b6d-5a1390d00813',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'34bba5'},body:JSON.stringify({sessionId:'34bba5',location:'App.tsx:onAssetUpload',message:'onAssetUpload entry',data:{fileName:file.name},timestamp:Date.now(),hypothesisId:'H2,H5'})}).catch(()=>{});
116
- // #endregion
117
- const base64 = await new Promise<string>((resolve, reject) => {
118
- const reader = new FileReader();
119
- reader.onload = () => {
120
- const dataUrl = reader.result as string;
121
- const base64 = dataUrl.includes(',') ? dataUrl.split(',')[1] : '';
122
- resolve(base64 || '');
123
- };
124
- reader.onerror = () => reject(reader.error);
125
- reader.readAsDataURL(file);
126
- });
127
- const res = await fetch('/api/upload-asset', {
128
- method: 'POST',
129
- headers: { 'Content-Type': 'application/json' },
130
- body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
131
- });
132
- if (!res.ok) {
133
- const err = await res.json().catch(() => ({}));
134
- throw new Error((err as { error?: string }).error || `Upload failed: ${res.status}`);
135
- }
136
- const { url } = (await res.json()) as { url: string };
137
- // In-memory upload only: do not update assetsManifest here to avoid reload.
138
- // Preview in Upload tab uses this URL; Library tab will get new items on next list-assets load.
139
- // Disk write can be added later on Save.
140
- return url;
91
+ if (!file.type.startsWith('image/')) {
92
+ throw new Error('Invalid file type. Allowed: JPEG, PNG, WebP, GIF, SVG, AVIF.');
93
+ }
94
+ if (file.size > MAX_UPLOAD_SIZE_BYTES) {
95
+ throw new Error(`File too large. Maximum size is ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
96
+ }
97
+ const base64 = await new Promise<string>((resolve, reject) => {
98
+ const reader = new FileReader();
99
+ reader.onload = () => {
100
+ const dataUrl = reader.result as string;
101
+ const b64 = dataUrl.includes(',') ? dataUrl.split(',')[1] : '';
102
+ resolve(b64 || '');
103
+ };
104
+ reader.onerror = () => reject(reader.error);
105
+ reader.readAsDataURL(file);
106
+ });
107
+ const res = await fetch('/api/upload-asset', {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({
111
+ filename: file.name,
112
+ mimeType: file.type || undefined,
113
+ data: base64,
114
+ }),
115
+ });
116
+ const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
117
+ if (!res.ok) {
118
+ throw new Error(body.error || `Upload failed: ${res.status}`);
119
+ }
120
+ if (typeof body.url !== 'string') {
121
+ throw new Error('Invalid server response: missing url');
122
+ }
123
+ return body.url;
124
+ },
141
125
  },
142
- },
143
126
  };
144
127
 
145
128
  return <JsonPagesEngine config={config} />;
@@ -1070,11 +1053,6 @@ export const ImageBreak: React.FC<{ data: ImageBreakData; settings?: ImageBreakS
1070
1053
  </div>
1071
1054
  )}
1072
1055
  </div>
1073
- {data.label && (
1074
- <div className="jp-section-label sr-only" data-jp-field="label">
1075
- {data.label}
1076
- </div>
1077
- )}
1078
1056
  </>
1079
1057
  ) : (
1080
1058
  <div className="flex flex-col items-center justify-center py-24 text-[var(--local-text-muted)]">
@@ -1097,19 +1075,16 @@ END_OF_FILE_CONTENT
1097
1075
  echo "Creating src/components/image-break/schema.ts..."
1098
1076
  cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/schema.ts"
1099
1077
  import { z } from 'zod';
1100
- import { BaseSectionData } from '@/lib/base-schemas';
1101
-
1102
- const ImageSelectionSchema = z.object({
1103
- url: z.string(),
1104
- alt: z.string().optional(),
1105
- }).describe('ui:image-picker');
1078
+ import { BaseSectionData, ImageSelectionSchema } from '@/lib/base-schemas';
1106
1079
 
1107
1080
  export const ImageBreakSchema = BaseSectionData.extend({
1108
- label: z.string().optional().describe('ui:text'),
1109
1081
  image: ImageSelectionSchema.default({ url: '', alt: '' }),
1110
- caption: z.string().optional().describe('ui:textarea'),
1082
+ caption: z.string().optional().describe('ui:text'),
1111
1083
  });
1112
1084
 
1085
+ export const ImageBreakSettingsSchema = z.object({
1086
+ height: z.enum(['sm', 'md', 'lg', 'full']).default('md').describe('ui:select'),
1087
+ });
1113
1088
  END_OF_FILE_CONTENT
1114
1089
  echo "Creating src/components/image-break/types.ts..."
1115
1090
  cat << 'END_OF_FILE_CONTENT' > "src/components/image-break/types.ts"
@@ -2503,18 +2478,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
2503
2478
  },
2504
2479
  "settings": {}
2505
2480
  },
2506
- {
2507
- "id": "image-break-1",
2508
- "type": "image-break",
2509
- "data": {
2510
- "image": {
2511
- "url": "https://images.unsplash.com/photo-1464226184884-fa280b87c399?w=800",
2512
- "alt": "Verdure fresche dall'orto"
2513
- },
2514
- "caption": "Il nostro orto a pochi passi dalla cucina — raccolto ogni mattina all'alba"
2515
- },
2516
- "settings": {}
2517
- },
2481
+
2518
2482
  {
2519
2483
  "id": "problem-statement",
2520
2484
  "type": "problem-statement",
@@ -3268,6 +3232,17 @@ echo "Creating src/lib/base-schemas.ts..."
3268
3232
  cat << 'END_OF_FILE_CONTENT' > "src/lib/base-schemas.ts"
3269
3233
  import { z } from 'zod';
3270
3234
 
3235
+ /**
3236
+ * Image picker field: object { url, alt? } with ui:image-picker for Form Factory.
3237
+ * Use in section data and resolve with resolveAssetUrl(url, tenantId) in View.
3238
+ */
3239
+ export const ImageSelectionSchema = z
3240
+ .object({
3241
+ url: z.string(),
3242
+ alt: z.string().optional(),
3243
+ })
3244
+ .describe('ui:image-picker');
3245
+
3271
3246
  /**
3272
3247
  * Base schemas shared by section capsules (CIP governance).
3273
3248
  * Capsules extend these for consistent anchorId, array items, and settings.
package/package.json CHANGED
@@ -1,29 +1,29 @@
1
- {
2
- "name": "@jsonpages/cli",
3
- "version": "3.0.25",
4
- "description": "JsonPages CLI - Sovereign Projection Engine",
5
- "type": "module",
6
- "bin": {
7
- "jsonpages": "./src/index.js"
8
- },
9
- "files": [
10
- "src",
11
- "assets/src_tenant_alpha.sh"
12
- ],
13
- "scripts": {
14
- "build": "tsc -p ."
15
- },
16
- "dependencies": {
17
- "@jsonpages/stack": "^1.0.0",
18
- "chalk": "^5.3.0",
19
- "commander": "^12.1.0",
20
- "execa": "^9.0.2",
21
- "fs-extra": "^11.2.0",
22
- "ora": "^8.0.1"
23
- },
24
- "devDependencies": {
25
- "@types/fs-extra": "^11.0.4",
26
- "@types/node": "^22.13.1",
27
- "typescript": "^5.7.3"
28
- }
29
- }
1
+ {
2
+ "name": "@jsonpages/cli",
3
+ "version": "3.0.28",
4
+ "description": "JsonPages CLI - Sovereign Projection Engine",
5
+ "type": "module",
6
+ "bin": {
7
+ "jsonpages": "./src/index.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "assets/src_tenant_alpha.sh"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p ."
15
+ },
16
+ "dependencies": {
17
+ "@jsonpages/stack": "^1.0.0",
18
+ "chalk": "^5.3.0",
19
+ "commander": "^12.1.0",
20
+ "execa": "^9.0.2",
21
+ "fs-extra": "^11.2.0",
22
+ "ora": "^8.0.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/fs-extra": "^11.0.4",
26
+ "@types/node": "^22.13.1",
27
+ "typescript": "^5.7.3"
28
+ }
29
+ }