@jsonpages/cli 3.0.27 → 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.
- package/assets/src_tenant_alpha.sh +54 -79
- package/package.json +29 -29
|
@@ -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
|
-
*
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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:
|
|
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.
|
|
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
|
+
}
|