@jsonpages/cli 3.0.67 → 3.0.69

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.
@@ -583,8 +583,9 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
583
583
  "scripts": {
584
584
  "dev": "vite",
585
585
  "dev:clean": "vite --force",
586
+ "prebuild": "node scripts/sync-pages-to-public.mjs",
586
587
  "build": "tsc && vite build",
587
- "dist": "bash ./src2Code.sh src index.html vite.config.ts scripts docs package.json",
588
+ "dist": "bash ./src2Code.sh src vercel.json index.html vite.config.ts scripts docs package.json",
588
589
  "preview": "vite preview",
589
590
  "bake:email": "tsx scripts/bake-email.tsx",
590
591
  "bakemail": "npm run bake:email --"
@@ -594,7 +595,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
594
595
  "@tiptap/extension-link": "^2.11.5",
595
596
  "@tiptap/react": "^2.11.5",
596
597
  "@tiptap/starter-kit": "^2.11.5",
597
- "@jsonpages/core": "^1.0.54",
598
+ "@jsonpages/core": "^1.0.56",
598
599
  "clsx": "^2.1.1",
599
600
  "lucide-react": "^0.474.0",
600
601
  "react": "^19.0.0",
@@ -890,6 +891,30 @@ main().catch((error) => {
890
891
  process.exit(1);
891
892
  });
892
893
 
894
+ END_OF_FILE_CONTENT
895
+ echo "Creating scripts/sync-pages-to-public.mjs..."
896
+ cat << 'END_OF_FILE_CONTENT' > "scripts/sync-pages-to-public.mjs"
897
+ import fs from 'fs';
898
+ import path from 'path';
899
+ import { fileURLToPath } from 'url';
900
+
901
+ const __filename = fileURLToPath(import.meta.url);
902
+ const __dirname = path.dirname(__filename);
903
+ const rootDir = path.resolve(__dirname, '..');
904
+ const sourceDir = path.join(rootDir, 'src', 'data', 'pages');
905
+ const targetDir = path.join(rootDir, 'public', 'pages');
906
+
907
+ if (!fs.existsSync(sourceDir)) {
908
+ console.warn('[sync-pages-to-public] Source directory not found:', sourceDir);
909
+ process.exit(0);
910
+ }
911
+
912
+ fs.rmSync(targetDir, { recursive: true, force: true });
913
+ fs.mkdirSync(targetDir, { recursive: true });
914
+ fs.cpSync(sourceDir, targetDir, { recursive: true });
915
+
916
+ console.log('[sync-pages-to-public] Synced pages to public/pages');
917
+
893
918
  END_OF_FILE_CONTENT
894
919
  mkdir -p "src"
895
920
  echo "Creating src/App.tsx..."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonpages/cli",
3
- "version": "3.0.67",
3
+ "version": "3.0.69",
4
4
  "description": "The Sovereign CLI Engine for JsonPages.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -151,114 +151,6 @@ program
151
151
  }
152
152
  });
153
153
 
154
- /** Returns full vite.config.ts content with /api/save-to-file, /api/upload-asset, /api/list-assets. Inlined so generated tenant always has save-to-disk. */
155
- function getViteConfigWithSaveToFile() {
156
- return `/**
157
- * Generated by @jsonpages/cli. Dev server API: /api/save-to-file, /api/upload-asset, /api/list-assets.
158
- */
159
- import { defineConfig } from 'vite';
160
- import react from '@vitejs/plugin-react';
161
- import tailwindcss from '@tailwindcss/vite';
162
- import path from 'path';
163
- import fs from 'fs';
164
- import { fileURLToPath } from 'url';
165
-
166
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
167
-
168
- const ASSETS_IMAGES_DIR = path.resolve(__dirname, 'public', 'assets', 'images');
169
- const DATA_CONFIG_DIR = path.resolve(__dirname, 'src', 'data', 'config');
170
- const DATA_PAGES_DIR = path.resolve(__dirname, 'src', 'data', 'pages');
171
- const IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif']);
172
- const IMAGE_MIMES = new Set([
173
- 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml', 'image/avif',
174
- ]);
175
- const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024; // 5MB
176
-
177
- function safeFilename(original, mimeType) {
178
- const base = (original.replace(/\\.[^.]+$/, '').replace(/[^a-zA-Z0-9-_]/g, '_').slice(0, 128)) || 'image';
179
- const ext = original.includes('.') ? path.extname(original).toLowerCase() : (mimeType?.startsWith('image/') ? \`.\${(mimeType.split('/')[1] || 'png').replace('jpeg', 'jpg')}\` : '.png');
180
- return \`\${Date.now()}-\${base}\${IMAGE_EXT.has(ext) ? ext : '.png'}\`;
181
- }
182
-
183
- function listImagesInDir(dir, urlPrefix) {
184
- const list = [];
185
- if (!fs.existsSync(dir)) return list;
186
- for (const name of fs.readdirSync(dir)) {
187
- if (IMAGE_EXT.has(path.extname(name).toLowerCase())) list.push({ id: name, url: \`\${urlPrefix}/\${name}\`, alt: name, tags: [] });
188
- }
189
- return list;
190
- }
191
-
192
- function sendJson(res, status, body) {
193
- res.writeHead(status, { 'Content-Type': 'application/json' });
194
- res.end(JSON.stringify(body));
195
- }
196
-
197
- export default defineConfig({
198
- plugins: [
199
- react(),
200
- tailwindcss(),
201
- {
202
- name: 'upload-asset-api',
203
- configureServer(server) {
204
- server.middlewares.use((req, res, next) => {
205
- if (req.method === 'GET' && req.url === '/api/list-assets') {
206
- try { sendJson(res, 200, listImagesInDir(ASSETS_IMAGES_DIR, '/assets/images')); } catch (e) { sendJson(res, 500, { error: e?.message || 'List failed' }); }
207
- return;
208
- }
209
- const pathname = (req.url || '').split('?')[0];
210
- if (req.method === 'POST' && pathname === '/api/save-to-file') {
211
- const chunks = [];
212
- req.on('data', (chunk) => chunks.push(chunk));
213
- req.on('end', () => {
214
- try {
215
- const raw = Buffer.concat(chunks).toString('utf8');
216
- if (!raw.trim()) { sendJson(res, 400, { error: 'Empty body' }); return; }
217
- const body = JSON.parse(raw);
218
- const { projectState, slug } = body;
219
- if (!projectState || typeof slug !== 'string') { sendJson(res, 400, { error: 'Missing projectState or slug' }); return; }
220
- if (!fs.existsSync(DATA_CONFIG_DIR)) fs.mkdirSync(DATA_CONFIG_DIR, { recursive: true });
221
- if (!fs.existsSync(DATA_PAGES_DIR)) fs.mkdirSync(DATA_PAGES_DIR, { recursive: true });
222
- if (projectState.site != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'site.json'), JSON.stringify(projectState.site, null, 2), 'utf8');
223
- if (projectState.theme != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'theme.json'), JSON.stringify(projectState.theme, null, 2), 'utf8');
224
- if (projectState.menu != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'menu.json'), JSON.stringify(projectState.menu, null, 2), 'utf8');
225
- if (projectState.page != null) {
226
- const safeSlug = (slug.replace(/[^a-zA-Z0-9-_]/g, '_') || 'page');
227
- fs.writeFileSync(path.join(DATA_PAGES_DIR, \`\${safeSlug}.json\`), JSON.stringify(projectState.page, null, 2), 'utf8');
228
- }
229
- sendJson(res, 200, { ok: true });
230
- } catch (e) { sendJson(res, 500, { error: e?.message || 'Save to file failed' }); }
231
- });
232
- req.on('error', () => sendJson(res, 500, { error: 'Request error' }));
233
- return;
234
- }
235
- if (req.method !== 'POST' || req.url !== '/api/upload-asset') return next();
236
- const chunks = [];
237
- req.on('data', (chunk) => chunks.push(chunk));
238
- req.on('end', () => {
239
- try {
240
- const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
241
- const { filename, mimeType, data } = body;
242
- if (!filename || typeof data !== 'string') { sendJson(res, 400, { error: 'Missing filename or data' }); return; }
243
- const buf = Buffer.from(data, 'base64');
244
- if (buf.length > MAX_FILE_SIZE_BYTES) { sendJson(res, 413, { error: 'File too large. Max 5MB.' }); return; }
245
- if (mimeType && !IMAGE_MIMES.has(mimeType)) { sendJson(res, 400, { error: 'Invalid file type' }); return; }
246
- const name = safeFilename(filename, mimeType);
247
- if (!fs.existsSync(ASSETS_IMAGES_DIR)) fs.mkdirSync(ASSETS_IMAGES_DIR, { recursive: true });
248
- fs.writeFileSync(path.join(ASSETS_IMAGES_DIR, name), buf);
249
- sendJson(res, 200, { url: \`/assets/images/\${name}\` });
250
- } catch (e) { sendJson(res, 500, { error: e?.message || 'Upload failed' }); }
251
- });
252
- req.on('error', () => sendJson(res, 500, { error: 'Request error' }));
253
- });
254
- },
255
- },
256
- ],
257
- resolve: { alias: { '@': path.resolve(__dirname, './src') } },
258
- });
259
- `;
260
- }
261
-
262
154
  async function injectInfraFiles(targetDir, name) {
263
155
  const pkg = {
264
156
  name: name,
@@ -273,10 +165,6 @@ async function injectInfraFiles(targetDir, name) {
273
165
  };
274
166
  await fs.writeJson(path.join(targetDir, 'package.json'), pkg, { spaces: 2 });
275
167
 
276
- // Vite config with dev server API: /api/save-to-file, /api/upload-asset, /api/list-assets (always inline so it works when CLI is installed from npm)
277
- const viteConfig = getViteConfigWithSaveToFile();
278
- await fs.writeFile(path.join(targetDir, 'vite.config.ts'), viteConfig);
279
-
280
168
  await fs.ensureDir(path.join(targetDir, 'public', 'assets', 'images'));
281
169
  await fs.ensureDir(path.join(targetDir, 'src', 'data', 'config'));
282
170
  await fs.ensureDir(path.join(targetDir, 'src', 'data', 'pages'));
@@ -331,4 +219,4 @@ async function injectInfraFiles(targetDir, name) {
331
219
  await fs.writeJson(path.join(targetDir, 'components.json'), shadcnConfig, { spaces: 2 });
332
220
  }
333
221
 
334
- program.parse();
222
+ program.parse();