@jsonpages/cli 3.0.66 → 3.0.68
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 +142 -2
- package/package.json +1 -1
- package/src/index.js +1 -113
|
@@ -584,7 +584,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
584
584
|
"dev": "vite",
|
|
585
585
|
"dev:clean": "vite --force",
|
|
586
586
|
"build": "tsc && vite build",
|
|
587
|
-
"dist": "bash ./src2Code.sh src index.html scripts docs package.json",
|
|
587
|
+
"dist": "bash ./src2Code.sh src index.html vite.config.ts scripts docs package.json",
|
|
588
588
|
"preview": "vite preview",
|
|
589
589
|
"bake:email": "tsx scripts/bake-email.tsx",
|
|
590
590
|
"bakemail": "npm run bake:email --"
|
|
@@ -594,7 +594,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
594
594
|
"@tiptap/extension-link": "^2.11.5",
|
|
595
595
|
"@tiptap/react": "^2.11.5",
|
|
596
596
|
"@tiptap/starter-kit": "^2.11.5",
|
|
597
|
-
"@jsonpages/core": "^1.0.
|
|
597
|
+
"@jsonpages/core": "^1.0.55",
|
|
598
598
|
"clsx": "^2.1.1",
|
|
599
599
|
"lucide-react": "^0.474.0",
|
|
600
600
|
"react": "^19.0.0",
|
|
@@ -7502,4 +7502,144 @@ declare module '*?inline' {
|
|
|
7502
7502
|
|
|
7503
7503
|
|
|
7504
7504
|
|
|
7505
|
+
END_OF_FILE_CONTENT
|
|
7506
|
+
echo "Creating vite.config.ts..."
|
|
7507
|
+
cat << 'END_OF_FILE_CONTENT' > "vite.config.ts"
|
|
7508
|
+
/**
|
|
7509
|
+
* Generated by @jsonpages/cli. Dev server API: /api/save-to-file, /api/upload-asset, /api/list-assets.
|
|
7510
|
+
*/
|
|
7511
|
+
import { defineConfig } from 'vite';
|
|
7512
|
+
import react from '@vitejs/plugin-react';
|
|
7513
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
7514
|
+
import path from 'path';
|
|
7515
|
+
import fs from 'fs';
|
|
7516
|
+
import { fileURLToPath } from 'url';
|
|
7517
|
+
|
|
7518
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7519
|
+
|
|
7520
|
+
const ASSETS_IMAGES_DIR = path.resolve(__dirname, 'public', 'assets', 'images');
|
|
7521
|
+
const DATA_CONFIG_DIR = path.resolve(__dirname, 'src', 'data', 'config');
|
|
7522
|
+
const DATA_PAGES_DIR = path.resolve(__dirname, 'src', 'data', 'pages');
|
|
7523
|
+
const IMAGE_EXT = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif']);
|
|
7524
|
+
const IMAGE_MIMES = new Set([
|
|
7525
|
+
'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml', 'image/avif',
|
|
7526
|
+
]);
|
|
7527
|
+
const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024; // 5MB
|
|
7528
|
+
|
|
7529
|
+
function safeFilename(original, mimeType) {
|
|
7530
|
+
const base = (original.replace(/\.[^.]+$/, '').replace(/[^a-zA-Z0-9-_]/g, '_').slice(0, 128)) || 'image';
|
|
7531
|
+
const ext = original.includes('.') ? path.extname(original).toLowerCase() : (mimeType?.startsWith('image/') ? `.${(mimeType.split('/')[1] || 'png').replace('jpeg', 'jpg')}` : '.png');
|
|
7532
|
+
return `${Date.now()}-${base}${IMAGE_EXT.has(ext) ? ext : '.png'}`;
|
|
7533
|
+
}
|
|
7534
|
+
|
|
7535
|
+
function listImagesInDir(dir, urlPrefix) {
|
|
7536
|
+
const list = [];
|
|
7537
|
+
if (!fs.existsSync(dir)) return list;
|
|
7538
|
+
for (const name of fs.readdirSync(dir)) {
|
|
7539
|
+
if (IMAGE_EXT.has(path.extname(name).toLowerCase())) list.push({ id: name, url: `${urlPrefix}/${name}`, alt: name, tags: [] });
|
|
7540
|
+
}
|
|
7541
|
+
return list;
|
|
7542
|
+
}
|
|
7543
|
+
|
|
7544
|
+
function sendJson(res, status, body) {
|
|
7545
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
7546
|
+
res.end(JSON.stringify(body));
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
function sendJsonFile(res, filePath) {
|
|
7550
|
+
try {
|
|
7551
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
7552
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
7553
|
+
res.end(content);
|
|
7554
|
+
} catch (e) {
|
|
7555
|
+
sendJson(res, 500, { error: e?.message || 'Read failed' });
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
function isTenantPageJsonRequest(req, pathname) {
|
|
7560
|
+
if (req.method !== 'GET' || !pathname.endsWith('.json')) return false;
|
|
7561
|
+
const viteOrStaticPrefixes = ['/api/', '/assets/', '/src/', '/node_modules/', '/public/', '/@'];
|
|
7562
|
+
return !viteOrStaticPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
7563
|
+
}
|
|
7564
|
+
export default defineConfig({
|
|
7565
|
+
plugins: [
|
|
7566
|
+
react(),
|
|
7567
|
+
tailwindcss(),
|
|
7568
|
+
{
|
|
7569
|
+
name: 'upload-asset-api',
|
|
7570
|
+
configureServer(server) {
|
|
7571
|
+
server.middlewares.use((req, res, next) => {
|
|
7572
|
+
const pathname = (req.url || '').split('?')[0];
|
|
7573
|
+
const isPageJsonRequest = isTenantPageJsonRequest(req, pathname);
|
|
7574
|
+
|
|
7575
|
+
if (isPageJsonRequest) {
|
|
7576
|
+
const normalizedPath = decodeURIComponent(pathname).replace(/\\/g, '/');
|
|
7577
|
+
const slug = normalizedPath.replace(/^\/+/, '').replace(/\.json$/i, '').replace(/^\/+|\/+$/g, '');
|
|
7578
|
+
const candidate = path.resolve(DATA_PAGES_DIR, `${slug}.json`);
|
|
7579
|
+
const isInsidePagesDir = candidate.startsWith(`${DATA_PAGES_DIR}${path.sep}`) || candidate === DATA_PAGES_DIR;
|
|
7580
|
+
if (!slug || !isInsidePagesDir || !fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) {
|
|
7581
|
+
sendJson(res, 404, { error: 'Page JSON not found' });
|
|
7582
|
+
return;
|
|
7583
|
+
}
|
|
7584
|
+
sendJsonFile(res, candidate);
|
|
7585
|
+
return;
|
|
7586
|
+
}
|
|
7587
|
+
if (req.method === 'GET' && req.url === '/api/list-assets') {
|
|
7588
|
+
try { sendJson(res, 200, listImagesInDir(ASSETS_IMAGES_DIR, '/assets/images')); } catch (e) { sendJson(res, 500, { error: e?.message || 'List failed' }); }
|
|
7589
|
+
return;
|
|
7590
|
+
}
|
|
7591
|
+
if (req.method === 'POST' && pathname === '/api/save-to-file') {
|
|
7592
|
+
const chunks = [];
|
|
7593
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
7594
|
+
req.on('end', () => {
|
|
7595
|
+
try {
|
|
7596
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
7597
|
+
if (!raw.trim()) { sendJson(res, 400, { error: 'Empty body' }); return; }
|
|
7598
|
+
const body = JSON.parse(raw);
|
|
7599
|
+
const { projectState, slug } = body;
|
|
7600
|
+
if (!projectState || typeof slug !== 'string') { sendJson(res, 400, { error: 'Missing projectState or slug' }); return; }
|
|
7601
|
+
if (!fs.existsSync(DATA_CONFIG_DIR)) fs.mkdirSync(DATA_CONFIG_DIR, { recursive: true });
|
|
7602
|
+
if (!fs.existsSync(DATA_PAGES_DIR)) fs.mkdirSync(DATA_PAGES_DIR, { recursive: true });
|
|
7603
|
+
if (projectState.site != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'site.json'), JSON.stringify(projectState.site, null, 2), 'utf8');
|
|
7604
|
+
if (projectState.theme != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'theme.json'), JSON.stringify(projectState.theme, null, 2), 'utf8');
|
|
7605
|
+
if (projectState.menu != null) fs.writeFileSync(path.join(DATA_CONFIG_DIR, 'menu.json'), JSON.stringify(projectState.menu, null, 2), 'utf8');
|
|
7606
|
+
if (projectState.page != null) {
|
|
7607
|
+
const safeSlug = (slug.replace(/[^a-zA-Z0-9-_]/g, '_') || 'page');
|
|
7608
|
+
fs.writeFileSync(path.join(DATA_PAGES_DIR, `${safeSlug}.json`), JSON.stringify(projectState.page, null, 2), 'utf8');
|
|
7609
|
+
}
|
|
7610
|
+
sendJson(res, 200, { ok: true });
|
|
7611
|
+
} catch (e) { sendJson(res, 500, { error: e?.message || 'Save to file failed' }); }
|
|
7612
|
+
});
|
|
7613
|
+
req.on('error', () => sendJson(res, 500, { error: 'Request error' }));
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
if (req.method !== 'POST' || req.url !== '/api/upload-asset') return next();
|
|
7617
|
+
const chunks = [];
|
|
7618
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
7619
|
+
req.on('end', () => {
|
|
7620
|
+
try {
|
|
7621
|
+
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
7622
|
+
const { filename, mimeType, data } = body;
|
|
7623
|
+
if (!filename || typeof data !== 'string') { sendJson(res, 400, { error: 'Missing filename or data' }); return; }
|
|
7624
|
+
const buf = Buffer.from(data, 'base64');
|
|
7625
|
+
if (buf.length > MAX_FILE_SIZE_BYTES) { sendJson(res, 413, { error: 'File too large. Max 5MB.' }); return; }
|
|
7626
|
+
if (mimeType && !IMAGE_MIMES.has(mimeType)) { sendJson(res, 400, { error: 'Invalid file type' }); return; }
|
|
7627
|
+
const name = safeFilename(filename, mimeType);
|
|
7628
|
+
if (!fs.existsSync(ASSETS_IMAGES_DIR)) fs.mkdirSync(ASSETS_IMAGES_DIR, { recursive: true });
|
|
7629
|
+
fs.writeFileSync(path.join(ASSETS_IMAGES_DIR, name), buf);
|
|
7630
|
+
sendJson(res, 200, { url: `/assets/images/${name}` });
|
|
7631
|
+
} catch (e) { sendJson(res, 500, { error: e?.message || 'Upload failed' }); }
|
|
7632
|
+
});
|
|
7633
|
+
req.on('error', () => sendJson(res, 500, { error: 'Request error' }));
|
|
7634
|
+
});
|
|
7635
|
+
},
|
|
7636
|
+
},
|
|
7637
|
+
],
|
|
7638
|
+
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
|
|
7639
|
+
});
|
|
7640
|
+
|
|
7641
|
+
|
|
7642
|
+
|
|
7643
|
+
|
|
7644
|
+
|
|
7505
7645
|
END_OF_FILE_CONTENT
|
package/package.json
CHANGED
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();
|