meno-core 1.0.52 → 1.0.53
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/build-astro.ts +183 -13
- package/build-next.ts +1361 -0
- package/build-static.ts +7 -5
- package/dist/bin/cli.js +2 -2
- package/dist/build-static.js +6 -6
- package/dist/chunks/{chunk-HNLUO36W.js → chunk-GZHGVVW3.js} +2 -2
- package/dist/chunks/chunk-GZHGVVW3.js.map +7 -0
- package/dist/chunks/{chunk-LPVETICS.js → chunk-H3GJ4H2U.js} +185 -1
- package/dist/chunks/chunk-H3GJ4H2U.js.map +7 -0
- package/dist/chunks/{chunk-CXCBV2M7.js → chunk-IGYR22T6.js} +76 -270
- package/dist/chunks/chunk-IGYR22T6.js.map +7 -0
- package/dist/chunks/{chunk-LHLHPYSP.js → chunk-JGP5A3Y5.js} +12 -11
- package/dist/chunks/chunk-JGP5A3Y5.js.map +7 -0
- package/dist/chunks/{chunk-7NIC4I3V.js → chunk-JGWFTO6P.js} +167 -21
- package/dist/chunks/chunk-JGWFTO6P.js.map +7 -0
- package/dist/chunks/{chunk-EDQSMAMP.js → chunk-O3NAGJP4.js} +85 -4
- package/dist/chunks/chunk-O3NAGJP4.js.map +7 -0
- package/dist/chunks/{chunk-H4JSCDNW.js → chunk-QB2LNO4W.js} +24 -1
- package/dist/chunks/chunk-QB2LNO4W.js.map +7 -0
- package/dist/chunks/{chunk-A725KYFK.js → chunk-R6XHAFBF.js} +561 -112
- package/dist/chunks/chunk-R6XHAFBF.js.map +7 -0
- package/dist/chunks/{chunk-J23ZX5AP.js → chunk-X754AHS5.js} +277 -1
- package/dist/chunks/chunk-X754AHS5.js.map +7 -0
- package/dist/chunks/{chunk-2QK6U5UK.js → chunk-YBLHKYFF.js} +12 -2
- package/dist/chunks/chunk-YBLHKYFF.js.map +7 -0
- package/dist/chunks/{constants-GWBAD66U.js → constants-STK2YBIW.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +354 -59
- package/dist/lib/client/index.js.map +4 -4
- package/dist/lib/server/index.js +1458 -190
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +202 -34
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/test-utils/index.js +1 -1
- package/entries/client-router.tsx +5 -165
- package/lib/client/ErrorBoundary.test.tsx +27 -25
- package/lib/client/ErrorBoundary.tsx +34 -19
- package/lib/client/core/ComponentBuilder.ts +19 -2
- package/lib/client/core/builders/embedBuilder.ts +8 -4
- package/lib/client/core/builders/listBuilder.ts +23 -4
- package/lib/client/fontFamiliesService.test.ts +76 -0
- package/lib/client/fontFamiliesService.ts +69 -0
- package/lib/client/hmrCssReload.ts +160 -0
- package/lib/client/hooks/useColorVariables.ts +2 -0
- package/lib/client/index.ts +4 -0
- package/lib/client/meno-filter/ui.ts +2 -0
- package/lib/client/routing/RouteLoader.test.ts +2 -2
- package/lib/client/routing/RouteLoader.ts +8 -2
- package/lib/client/routing/Router.tsx +81 -15
- package/lib/client/scripts/ScriptExecutor.test.ts +143 -0
- package/lib/client/scripts/ScriptExecutor.ts +56 -2
- package/lib/client/styles/StyleInjector.ts +20 -5
- package/lib/client/styles/UtilityClassCollector.ts +7 -1
- package/lib/client/styles/cspNonce.test.ts +67 -0
- package/lib/client/styles/cspNonce.ts +63 -0
- package/lib/client/templateEngine.test.ts +80 -0
- package/lib/client/templateEngine.ts +5 -0
- package/lib/server/astro/cmsPageEmitter.ts +35 -5
- package/lib/server/astro/componentEmitter.ts +61 -5
- package/lib/server/astro/nodeToAstro.ts +149 -11
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +264 -0
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +184 -0
- package/lib/server/createServer.ts +11 -0
- package/lib/server/draftPageStore.ts +49 -0
- package/lib/server/fileWatcher.ts +62 -2
- package/lib/server/index.ts +13 -1
- package/lib/server/providers/fileSystemPageProvider.ts +8 -0
- package/lib/server/routes/api/components.ts +9 -4
- package/lib/server/routes/api/core-routes.ts +2 -2
- package/lib/server/routes/api/pages.ts +14 -22
- package/lib/server/routes/api/shared.ts +56 -0
- package/lib/server/routes/index.ts +90 -0
- package/lib/server/routes/pages.ts +13 -6
- package/lib/server/services/componentService.test.ts +199 -2
- package/lib/server/services/componentService.ts +354 -49
- package/lib/server/services/fileWatcherService.ts +4 -24
- package/lib/server/services/pageService.test.ts +23 -0
- package/lib/server/services/pageService.ts +124 -6
- package/lib/server/ssr/attributeBuilder.ts +8 -2
- package/lib/server/ssr/buildErrorOverlay.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +21 -2
- package/lib/server/ssr/errorOverlay.ts +38 -11
- package/lib/server/ssr/htmlGenerator.test.ts +53 -13
- package/lib/server/ssr/htmlGenerator.ts +71 -27
- package/lib/server/ssr/liveReloadIntegration.test.ts +123 -2
- package/lib/server/ssr/ssrRenderer.test.ts +67 -0
- package/lib/server/ssr/ssrRenderer.ts +94 -9
- package/lib/server/websocketManager.ts +0 -1
- package/lib/shared/componentRefs.ts +45 -0
- package/lib/shared/constants.ts +8 -0
- package/lib/shared/cssGeneration.ts +2 -0
- package/lib/shared/cssProperties.ts +184 -0
- package/lib/shared/expressionEvaluator.ts +54 -0
- package/lib/shared/fontCss.ts +101 -0
- package/lib/shared/fontLoader.ts +8 -86
- package/lib/shared/friendlyError.test.ts +87 -0
- package/lib/shared/friendlyError.ts +121 -0
- package/lib/shared/hrefRefs.test.ts +130 -0
- package/lib/shared/hrefRefs.ts +100 -0
- package/lib/shared/index.ts +52 -0
- package/lib/shared/inlineSvgStyleRules.test.ts +108 -0
- package/lib/shared/inlineSvgStyleRules.ts +134 -0
- package/lib/shared/interfaces/contentProvider.ts +13 -0
- package/lib/shared/itemTemplateUtils.test.ts +14 -0
- package/lib/shared/itemTemplateUtils.ts +4 -1
- package/lib/shared/registry/NodeTypeDefinition.ts +1 -1
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +1 -1
- package/lib/shared/slugTranslator.test.ts +24 -0
- package/lib/shared/slugTranslator.ts +24 -0
- package/lib/shared/styleNodeUtils.ts +4 -1
- package/lib/shared/tree/PathBuilder.test.ts +128 -1
- package/lib/shared/tree/PathBuilder.ts +83 -31
- package/lib/shared/types/comment.ts +99 -0
- package/lib/shared/types/index.ts +12 -0
- package/lib/shared/types/rendering.ts +8 -0
- package/lib/shared/utilityClassConfig.ts +4 -2
- package/lib/shared/utilityClassMapper.test.ts +24 -0
- package/lib/shared/validation/commentValidators.ts +69 -0
- package/lib/shared/validation/index.ts +1 -0
- package/lib/shared/viewportUnits.integration.test.ts +42 -0
- package/lib/shared/viewportUnits.test.ts +103 -0
- package/lib/shared/viewportUnits.ts +63 -0
- package/lib/test-utils/dom-setup.ts +6 -0
- package/package.json +1 -1
- package/dist/chunks/chunk-2QK6U5UK.js.map +0 -7
- package/dist/chunks/chunk-7NIC4I3V.js.map +0 -7
- package/dist/chunks/chunk-A725KYFK.js.map +0 -7
- package/dist/chunks/chunk-CXCBV2M7.js.map +0 -7
- package/dist/chunks/chunk-EDQSMAMP.js.map +0 -7
- package/dist/chunks/chunk-H4JSCDNW.js.map +0 -7
- package/dist/chunks/chunk-HNLUO36W.js.map +0 -7
- package/dist/chunks/chunk-J23ZX5AP.js.map +0 -7
- package/dist/chunks/chunk-LHLHPYSP.js.map +0 -7
- package/dist/chunks/chunk-LPVETICS.js.map +0 -7
- /package/dist/chunks/{constants-GWBAD66U.js.map → constants-STK2YBIW.js.map} +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { test, expect, describe, beforeEach, mock } from 'bun:test';
|
|
2
|
-
import {
|
|
1
|
+
import { test, expect, describe, beforeEach, afterAll, mock } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { ComponentService, rewriteComponentRefs, type ComponentServiceFs, type ComponentLoader, type ComponentWriter } from './componentService';
|
|
6
|
+
import { setProjectRoot, getProjectRoot, projectPaths } from '../projectContext';
|
|
3
7
|
import { createMockComponentDefinition } from '../../test-utils';
|
|
4
8
|
import {
|
|
5
9
|
createTypedMockComponentServiceFs,
|
|
@@ -249,6 +253,199 @@ describe('ComponentService', () => {
|
|
|
249
253
|
});
|
|
250
254
|
});
|
|
251
255
|
|
|
256
|
+
describe('rewriteComponentRefs', () => {
|
|
257
|
+
test('rewrites component references in a tree', () => {
|
|
258
|
+
const tree = {
|
|
259
|
+
type: 'node',
|
|
260
|
+
tag: 'div',
|
|
261
|
+
children: [
|
|
262
|
+
{ type: 'component', component: 'Old' },
|
|
263
|
+
{ type: 'node', tag: 'section', children: [
|
|
264
|
+
{ type: 'component', component: 'Old', props: { text: 'hi' } },
|
|
265
|
+
{ type: 'component', component: 'Keep' },
|
|
266
|
+
] },
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const changed = rewriteComponentRefs(tree, 'Old', 'New');
|
|
271
|
+
|
|
272
|
+
expect(changed).toBe(true);
|
|
273
|
+
expect((tree.children[0] as any).component).toBe('New');
|
|
274
|
+
expect(((tree.children[1] as any).children[0]).component).toBe('New');
|
|
275
|
+
expect(((tree.children[1] as any).children[1]).component).toBe('Keep');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('returns false when no references match', () => {
|
|
279
|
+
const tree = {
|
|
280
|
+
type: 'node',
|
|
281
|
+
tag: 'div',
|
|
282
|
+
children: [{ type: 'component', component: 'Keep' }],
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
expect(rewriteComponentRefs(tree, 'Old', 'New')).toBe(false);
|
|
286
|
+
expect((tree.children[0] as any).component).toBe('Keep');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('does not match component names that appear as substrings in other fields', () => {
|
|
290
|
+
// Critical: a name like "Button" must not be substituted inside HTML
|
|
291
|
+
// tags, CSS classes, or string copy that happen to contain the word.
|
|
292
|
+
const tree = {
|
|
293
|
+
type: 'node',
|
|
294
|
+
tag: 'button',
|
|
295
|
+
attributes: { class: 'Button primary' },
|
|
296
|
+
children: [
|
|
297
|
+
{ type: 'embed', html: '<button>Button</button>' },
|
|
298
|
+
'Click the Button below',
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const changed = rewriteComponentRefs(tree, 'Button', 'Cta');
|
|
303
|
+
|
|
304
|
+
expect(changed).toBe(false);
|
|
305
|
+
expect(tree.tag).toBe('button');
|
|
306
|
+
expect(tree.attributes.class).toBe('Button primary');
|
|
307
|
+
expect((tree.children[0] as any).html).toBe('<button>Button</button>');
|
|
308
|
+
expect(tree.children[1]).toBe('Click the Button below');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('handles missing children safely', () => {
|
|
312
|
+
expect(rewriteComponentRefs({ type: 'component', component: 'Old' }, 'Old', 'New')).toBe(true);
|
|
313
|
+
expect(rewriteComponentRefs(null, 'Old', 'New')).toBe(false);
|
|
314
|
+
expect(rewriteComponentRefs(undefined, 'Old', 'New')).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('walks an object children field (e.g. single nested node)', () => {
|
|
318
|
+
const tree = {
|
|
319
|
+
type: 'node',
|
|
320
|
+
tag: 'div',
|
|
321
|
+
children: { type: 'component', component: 'Old' },
|
|
322
|
+
};
|
|
323
|
+
expect(rewriteComponentRefs(tree, 'Old', 'New')).toBe(true);
|
|
324
|
+
expect((tree.children as any).component).toBe('New');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('renameComponent', () => {
|
|
329
|
+
test('rejects when oldName is empty', async () => {
|
|
330
|
+
await expect(componentService.renameComponent('', 'New')).rejects.toThrow(/oldName/);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('rejects when newName is empty', async () => {
|
|
334
|
+
await expect(componentService.renameComponent('Button', '')).rejects.toThrow(/newName/);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('rejects when names are identical', async () => {
|
|
338
|
+
await componentService.loadAllComponents();
|
|
339
|
+
await expect(componentService.renameComponent('Button', 'Button')).rejects.toThrow(/differ/);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('rejects invalid identifiers', async () => {
|
|
343
|
+
await componentService.loadAllComponents();
|
|
344
|
+
await expect(componentService.renameComponent('Button', '1Bad')).rejects.toThrow(/identifier/);
|
|
345
|
+
await expect(componentService.renameComponent('Button', 'bad-name')).rejects.toThrow(/identifier/);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('rejects when the source component does not exist', async () => {
|
|
349
|
+
await componentService.loadAllComponents();
|
|
350
|
+
await expect(componentService.renameComponent('Ghost', 'NewGhost')).rejects.toThrow(/not found/);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('rejects when the target name is already taken', async () => {
|
|
354
|
+
await componentService.loadAllComponents();
|
|
355
|
+
// Both TestComponent and Button are seeded in beforeEach.
|
|
356
|
+
await expect(componentService.renameComponent('Button', 'TestComponent')).rejects.toThrow(/already exists/);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('JSON mode (no writer) — default path regression guard', () => {
|
|
361
|
+
// Locks in that a writer-less service still writes <root>/components/<name>.json
|
|
362
|
+
// with the exact 2-space JSON serialization (byte-identical to legacy behavior).
|
|
363
|
+
const prevRoot = getProjectRoot();
|
|
364
|
+
const root = mkdtempSync(join(tmpdir(), 'meno-json-comp-'));
|
|
365
|
+
afterAll(() => {
|
|
366
|
+
rmSync(root, { recursive: true, force: true });
|
|
367
|
+
setProjectRoot(prevRoot);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('saveComponent writes components/<name>.json under projectRoot', async () => {
|
|
371
|
+
setProjectRoot(root);
|
|
372
|
+
// Real JSON projects always have a components/ dir; the JSON save path only
|
|
373
|
+
// auto-creates category subdirs, never the root (unchanged legacy behavior).
|
|
374
|
+
mkdirSync(join(root, 'components'), { recursive: true });
|
|
375
|
+
const svc = new ComponentService(); // no fs, no loader, no writer → real fs
|
|
376
|
+
const def = createMockComponentDefinition('JsonCard');
|
|
377
|
+
await svc.saveComponent('JsonCard', def);
|
|
378
|
+
|
|
379
|
+
const file = join(projectPaths.components(), 'JsonCard.json');
|
|
380
|
+
expect(file).toBe(join(root, 'components', 'JsonCard.json'));
|
|
381
|
+
expect(existsSync(file)).toBe(true);
|
|
382
|
+
|
|
383
|
+
// Byte-identical serialization: javascript stripped, 2-space indent.
|
|
384
|
+
const onDisk = readFileSync(file, 'utf8');
|
|
385
|
+
const expected = JSON.parse(JSON.stringify(def));
|
|
386
|
+
delete (expected as any).component?.javascript;
|
|
387
|
+
expect(onDisk).toBe(JSON.stringify(expected, null, 2));
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('writer mode — write ops route through the injected writer', () => {
|
|
392
|
+
// A spy writer that records calls and never touches projectPaths.components().
|
|
393
|
+
function makeSpyWriter() {
|
|
394
|
+
const calls: Array<{ op: string; dir: string; name: string; extra?: unknown }> = [];
|
|
395
|
+
const dir = '/virtual/src/components';
|
|
396
|
+
const writer: ComponentWriter = {
|
|
397
|
+
componentsDir: () => dir,
|
|
398
|
+
writeComponent: async (d, n) => { calls.push({ op: 'writeComponent', dir: d, name: n }); },
|
|
399
|
+
writeJavaScript: async (d, n, js) => { calls.push({ op: 'writeJavaScript', dir: d, name: n, extra: js }); },
|
|
400
|
+
writeCSS: async (d, n, css) => { calls.push({ op: 'writeCSS', dir: d, name: n, extra: css }); },
|
|
401
|
+
removeComponent: async (d, n) => { calls.push({ op: 'removeComponent', dir: d, name: n }); },
|
|
402
|
+
componentExists: async () => false,
|
|
403
|
+
moveComponent: async (s, t, n, dn) => { calls.push({ op: 'moveComponent', dir: `${s}->${t}`, name: n, extra: dn }); },
|
|
404
|
+
};
|
|
405
|
+
return { writer, calls, dir };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
test('saveComponent calls writer.writeComponent (not fs/json), under the writer dir', async () => {
|
|
409
|
+
const { writer, calls, dir } = makeSpyWriter();
|
|
410
|
+
const svc = new ComponentService({ writer });
|
|
411
|
+
await svc.saveComponent('Widget', createMockComponentDefinition('Widget'));
|
|
412
|
+
expect(calls).toHaveLength(1);
|
|
413
|
+
expect(calls[0].op).toBe('writeComponent');
|
|
414
|
+
expect(calls[0].dir).toBe(dir);
|
|
415
|
+
expect(calls[0].name).toBe('Widget');
|
|
416
|
+
expect(svc.hasComponent('Widget')).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('saveComponentJavaScript / saveComponentCSS route through the writer and update the cache', async () => {
|
|
420
|
+
const { writer, calls } = makeSpyWriter();
|
|
421
|
+
const svc = new ComponentService({ writer });
|
|
422
|
+
await svc.saveComponent('Widget', createMockComponentDefinition('Widget'));
|
|
423
|
+
await svc.saveComponentCSS('Widget', '.w{}');
|
|
424
|
+
await svc.saveComponentJavaScript('Widget', 'x();');
|
|
425
|
+
const ops = calls.map(c => c.op);
|
|
426
|
+
expect(ops).toEqual(['writeComponent', 'writeCSS', 'writeJavaScript']);
|
|
427
|
+
const cached = svc.getComponent('Widget');
|
|
428
|
+
expect(cached?.component?.css).toBe('.w{}');
|
|
429
|
+
expect(cached?.component?.javascript).toBe('x();');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('renameComponent uses writer.moveComponent with the new name', async () => {
|
|
433
|
+
const { writer, calls } = makeSpyWriter();
|
|
434
|
+
const svc = new ComponentService({
|
|
435
|
+
writer,
|
|
436
|
+
loader: createTypedMockComponentLoader(new Map([['Widget', createMockComponentDefinition('Widget')]])),
|
|
437
|
+
});
|
|
438
|
+
await svc.loadAllComponents();
|
|
439
|
+
await svc.renameComponent('Widget', 'Gadget');
|
|
440
|
+
const move = calls.find(c => c.op === 'moveComponent');
|
|
441
|
+
expect(move).toBeDefined();
|
|
442
|
+
expect(move!.name).toBe('Widget');
|
|
443
|
+
expect(move!.extra).toBe('Gadget'); // dstName
|
|
444
|
+
expect(svc.hasComponent('Gadget')).toBe(true);
|
|
445
|
+
expect(svc.hasComponent('Widget')).toBe(false);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
252
449
|
describe('constructor options', () => {
|
|
253
450
|
test('should work with no options', () => {
|
|
254
451
|
const service = new ComponentService();
|