zero-query 0.9.7 → 0.9.9

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.
Files changed (72) hide show
  1. package/README.md +55 -4
  2. package/cli/commands/build.js +50 -3
  3. package/cli/commands/create.js +58 -11
  4. package/cli/help.js +4 -0
  5. package/cli/scaffold/default/app/app.js +211 -0
  6. package/cli/scaffold/default/app/components/about.js +201 -0
  7. package/cli/scaffold/default/app/components/api-demo.js +143 -0
  8. package/cli/scaffold/default/app/components/contact-card.js +231 -0
  9. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -0
  10. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -0
  11. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -0
  12. package/cli/scaffold/default/app/components/counter.js +127 -0
  13. package/cli/scaffold/default/app/components/home.js +249 -0
  14. package/cli/scaffold/{app → default/app}/components/not-found.js +2 -2
  15. package/cli/scaffold/default/app/components/playground/playground.css +116 -0
  16. package/cli/scaffold/default/app/components/playground/playground.html +162 -0
  17. package/cli/scaffold/default/app/components/playground/playground.js +117 -0
  18. package/cli/scaffold/default/app/components/todos.js +225 -0
  19. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -0
  20. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -0
  21. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -0
  22. package/cli/scaffold/default/app/routes.js +15 -0
  23. package/cli/scaffold/{app → default/app}/store.js +15 -10
  24. package/cli/scaffold/{global.css → default/global.css} +238 -252
  25. package/cli/scaffold/{index.html → default/index.html} +35 -0
  26. package/cli/scaffold/{app → minimal/app}/app.js +37 -39
  27. package/cli/scaffold/minimal/app/components/about.js +68 -0
  28. package/cli/scaffold/minimal/app/components/counter.js +122 -0
  29. package/cli/scaffold/minimal/app/components/home.js +68 -0
  30. package/cli/scaffold/minimal/app/components/not-found.js +16 -0
  31. package/cli/scaffold/minimal/app/routes.js +9 -0
  32. package/cli/scaffold/minimal/app/store.js +36 -0
  33. package/cli/scaffold/minimal/assets/.gitkeep +0 -0
  34. package/cli/scaffold/minimal/global.css +291 -0
  35. package/cli/scaffold/minimal/index.html +44 -0
  36. package/cli/scaffold/ssr/app/app.js +30 -0
  37. package/cli/scaffold/ssr/app/components/about.js +28 -0
  38. package/cli/scaffold/ssr/app/components/home.js +37 -0
  39. package/cli/scaffold/ssr/app/components/not-found.js +15 -0
  40. package/cli/scaffold/ssr/app/routes.js +6 -0
  41. package/cli/scaffold/ssr/global.css +113 -0
  42. package/cli/scaffold/ssr/index.html +31 -0
  43. package/cli/scaffold/ssr/package.json +8 -0
  44. package/cli/scaffold/ssr/server/index.js +118 -0
  45. package/dist/zquery.dist.zip +0 -0
  46. package/dist/zquery.js +2006 -1933
  47. package/dist/zquery.min.js +2 -2
  48. package/index.d.ts +20 -1
  49. package/index.js +8 -5
  50. package/package.json +8 -2
  51. package/src/component.js +6 -3
  52. package/src/diff.js +15 -2
  53. package/src/errors.js +59 -5
  54. package/src/package.json +1 -0
  55. package/src/ssr.js +116 -22
  56. package/tests/cli.test.js +304 -0
  57. package/tests/errors.test.js +423 -145
  58. package/tests/ssr.test.js +435 -3
  59. package/types/errors.d.ts +34 -2
  60. package/types/ssr.d.ts +21 -1
  61. package/cli/scaffold/app/components/about.js +0 -131
  62. package/cli/scaffold/app/components/api-demo.js +0 -103
  63. package/cli/scaffold/app/components/contacts/contacts.css +0 -246
  64. package/cli/scaffold/app/components/contacts/contacts.html +0 -140
  65. package/cli/scaffold/app/components/contacts/contacts.js +0 -153
  66. package/cli/scaffold/app/components/counter.js +0 -85
  67. package/cli/scaffold/app/components/home.js +0 -137
  68. package/cli/scaffold/app/components/todos.js +0 -131
  69. package/cli/scaffold/app/routes.js +0 -13
  70. /package/cli/scaffold/{LICENSE → default/LICENSE} +0 -0
  71. /package/cli/scaffold/{assets → default/assets}/.gitkeep +0 -0
  72. /package/cli/scaffold/{favicon.ico → default/favicon.ico} +0 -0
package/tests/cli.test.js CHANGED
@@ -454,3 +454,307 @@ describe('CLI — flag/option extra', () => {
454
454
  expect(flag('watch', 'w')).toBe(true);
455
455
  });
456
456
  });
457
+
458
+
459
+ // ===========================================================================
460
+ // createProject — scaffold command
461
+ // ===========================================================================
462
+
463
+ describe('CLI — createProject', () => {
464
+ const fs = require('fs');
465
+ const path = require('path');
466
+ const os = require('os');
467
+
468
+ let createProject;
469
+
470
+ beforeEach(async () => {
471
+ vi.resetModules();
472
+ const mod = await import('../cli/commands/create.js');
473
+ createProject = mod.default || mod;
474
+ });
475
+
476
+ function tmpDir() {
477
+ const dir = path.join(os.tmpdir(), 'zq-create-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8));
478
+ fs.mkdirSync(dir, { recursive: true });
479
+ return dir;
480
+ }
481
+
482
+ function cleanup(dir) {
483
+ if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
484
+ }
485
+
486
+ // -- scaffold variant directories exist --
487
+
488
+ it('default scaffold directory exists', () => {
489
+ const dir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default');
490
+ expect(fs.existsSync(dir)).toBe(true);
491
+ });
492
+
493
+ it('minimal scaffold directory exists', () => {
494
+ const dir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal');
495
+ expect(fs.existsSync(dir)).toBe(true);
496
+ });
497
+
498
+ // -- default scaffold contains expected files --
499
+
500
+ it('default scaffold has index.html', () => {
501
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default', 'index.html');
502
+ expect(fs.existsSync(f)).toBe(true);
503
+ });
504
+
505
+ it('default scaffold has app/app.js', () => {
506
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default', 'app', 'app.js');
507
+ expect(fs.existsSync(f)).toBe(true);
508
+ });
509
+
510
+ it('default scaffold has app/routes.js', () => {
511
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default', 'app', 'routes.js');
512
+ expect(fs.existsSync(f)).toBe(true);
513
+ });
514
+
515
+ it('default scaffold has app/store.js', () => {
516
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default', 'app', 'store.js');
517
+ expect(fs.existsSync(f)).toBe(true);
518
+ });
519
+
520
+ // -- minimal scaffold contains expected files --
521
+
522
+ it('minimal scaffold has index.html', () => {
523
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal', 'index.html');
524
+ expect(fs.existsSync(f)).toBe(true);
525
+ });
526
+
527
+ it('minimal scaffold has app/app.js', () => {
528
+ const f = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal', 'app', 'app.js');
529
+ expect(fs.existsSync(f)).toBe(true);
530
+ });
531
+
532
+ it('minimal scaffold has 4 components', () => {
533
+ const dir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal', 'app', 'components');
534
+ const files = fs.readdirSync(dir).sort();
535
+ expect(files).toEqual(['about.js', 'counter.js', 'home.js', 'not-found.js']);
536
+ });
537
+
538
+ // -- minimal scaffold is a subset of default --
539
+
540
+ it('minimal scaffold has fewer files than default', () => {
541
+ function countFiles(dir) {
542
+ let count = 0;
543
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
544
+ if (entry.isDirectory()) count += countFiles(path.join(dir, entry.name));
545
+ else count++;
546
+ }
547
+ return count;
548
+ }
549
+ const defDir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default');
550
+ const minDir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal');
551
+ expect(countFiles(minDir)).toBeLessThan(countFiles(defDir));
552
+ });
553
+
554
+ // -- {{NAME}} replacement --
555
+
556
+ it('scaffold templates contain {{NAME}} placeholder', () => {
557
+ const html = fs.readFileSync(
558
+ path.resolve(__dirname, '..', 'cli', 'scaffold', 'default', 'index.html'), 'utf-8'
559
+ );
560
+ expect(html).toContain('{{NAME}}');
561
+ });
562
+
563
+ it('minimal scaffold templates contain {{NAME}} placeholder', () => {
564
+ const html = fs.readFileSync(
565
+ path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal', 'index.html'), 'utf-8'
566
+ );
567
+ expect(html).toContain('{{NAME}}');
568
+ });
569
+
570
+ // -- walkDir functionality (tested via module internals) --
571
+
572
+ it('scaffolds default project into a target directory', () => {
573
+ const target = tmpDir();
574
+ const projDir = path.join(target, 'test-app');
575
+
576
+ // Simulate: process.argv for default (no --minimal)
577
+ process.argv = ['node', 'zquery', 'create', 'test-app'];
578
+ vi.resetModules();
579
+
580
+ // We can't easily call createProject because it uses process.exit.
581
+ // Instead, test the walkDir + copy logic directly.
582
+ const scaffoldDir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'default');
583
+
584
+ function walkDir(dir, prefix = '') {
585
+ const entries = [];
586
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
587
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
588
+ if (entry.isDirectory()) entries.push(...walkDir(path.join(dir, entry.name), rel));
589
+ else entries.push(rel);
590
+ }
591
+ return entries;
592
+ }
593
+
594
+ const files = walkDir(scaffoldDir);
595
+ expect(files.length).toBeGreaterThan(5);
596
+ expect(files).toContain('index.html');
597
+ expect(files).toContain('global.css');
598
+ expect(files).toContain('app/app.js');
599
+ expect(files).toContain('app/routes.js');
600
+ expect(files).toContain('app/store.js');
601
+
602
+ cleanup(target);
603
+ });
604
+
605
+ it('walkDir lists minimal scaffold files correctly', () => {
606
+ const scaffoldDir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal');
607
+
608
+ function walkDir(dir, prefix = '') {
609
+ const entries = [];
610
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
611
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
612
+ if (entry.isDirectory()) entries.push(...walkDir(path.join(dir, entry.name), rel));
613
+ else entries.push(rel);
614
+ }
615
+ return entries;
616
+ }
617
+
618
+ const files = walkDir(scaffoldDir);
619
+ expect(files).toContain('index.html');
620
+ expect(files).toContain('global.css');
621
+ expect(files).toContain('app/app.js');
622
+ expect(files).toContain('app/routes.js');
623
+ expect(files).toContain('app/store.js');
624
+ expect(files).toContain('app/components/home.js');
625
+ expect(files).toContain('app/components/counter.js');
626
+ expect(files).toContain('app/components/about.js');
627
+ expect(files).toContain('app/components/not-found.js');
628
+ expect(files).toContain('assets/.gitkeep');
629
+ });
630
+
631
+ // -- {{NAME}} replacement in copied files --
632
+
633
+ it('replaces {{NAME}} in copied scaffold files', () => {
634
+ const target = tmpDir();
635
+ const scaffoldDir = path.resolve(__dirname, '..', 'cli', 'scaffold', 'minimal');
636
+ const projectName = 'my-cool-app';
637
+
638
+ function walkDir(dir, prefix = '') {
639
+ const entries = [];
640
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
641
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
642
+ if (entry.isDirectory()) entries.push(...walkDir(path.join(dir, entry.name), rel));
643
+ else entries.push(rel);
644
+ }
645
+ return entries;
646
+ }
647
+
648
+ const files = walkDir(scaffoldDir);
649
+
650
+ for (const rel of files) {
651
+ let content = fs.readFileSync(path.join(scaffoldDir, rel), 'utf-8');
652
+ content = content.replace(/\{\{NAME\}\}/g, projectName);
653
+ const dest = path.join(target, rel);
654
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
655
+ fs.writeFileSync(dest, content, 'utf-8');
656
+ }
657
+
658
+ const html = fs.readFileSync(path.join(target, 'index.html'), 'utf-8');
659
+ expect(html).toContain(projectName);
660
+ expect(html).not.toContain('{{NAME}}');
661
+
662
+ const appJs = fs.readFileSync(path.join(target, 'app', 'app.js'), 'utf-8');
663
+ expect(appJs).toContain(projectName);
664
+ expect(appJs).not.toContain('{{NAME}}');
665
+
666
+ cleanup(target);
667
+ });
668
+
669
+ // -- conflict detection --
670
+
671
+ it('conflicts array detects existing files', () => {
672
+ const target = tmpDir();
673
+ fs.writeFileSync(path.join(target, 'index.html'), 'existing');
674
+ fs.mkdirSync(path.join(target, 'app'), { recursive: true });
675
+
676
+ const conflicts = ['index.html', 'global.css', 'app', 'assets'].filter(f =>
677
+ fs.existsSync(path.join(target, f))
678
+ );
679
+
680
+ expect(conflicts).toContain('index.html');
681
+ expect(conflicts).toContain('app');
682
+ expect(conflicts).not.toContain('global.css');
683
+ expect(conflicts).not.toContain('assets');
684
+
685
+ cleanup(target);
686
+ });
687
+
688
+ it('no conflicts in empty directory', () => {
689
+ const target = tmpDir();
690
+
691
+ const conflicts = ['index.html', 'global.css', 'app', 'assets'].filter(f =>
692
+ fs.existsSync(path.join(target, f))
693
+ );
694
+
695
+ expect(conflicts).toHaveLength(0);
696
+
697
+ cleanup(target);
698
+ });
699
+
700
+ // -- flag parsing for --minimal --
701
+
702
+ it('--minimal flag resolves to minimal variant', async () => {
703
+ process.argv = ['node', 'zquery', 'create', '--minimal', 'my-app'];
704
+ vi.resetModules();
705
+ const { flag } = await import('../cli/args.js');
706
+ expect(flag('minimal', 'm')).toBe(true);
707
+ });
708
+
709
+ it('-m short flag resolves to minimal variant', async () => {
710
+ process.argv = ['node', 'zquery', 'create', '-m', 'my-app'];
711
+ vi.resetModules();
712
+ const { flag } = await import('../cli/args.js');
713
+ expect(flag('minimal', 'm')).toBe(true);
714
+ });
715
+
716
+ it('no flag defaults to default variant', async () => {
717
+ process.argv = ['node', 'zquery', 'create', 'my-app'];
718
+ vi.resetModules();
719
+ const { flag } = await import('../cli/args.js');
720
+ expect(flag('minimal', 'm')).toBe(false);
721
+ });
722
+
723
+ // -- dirArg parsing skips flags --
724
+
725
+ it('dirArg parsing skips flag args', () => {
726
+ const args = ['create', '--minimal', 'my-app'];
727
+ const dirArg = args.slice(1).find(a => !a.startsWith('-'));
728
+ expect(dirArg).toBe('my-app');
729
+ });
730
+
731
+ it('dirArg parsing returns first positional', () => {
732
+ const args = ['create', 'my-app'];
733
+ const dirArg = args.slice(1).find(a => !a.startsWith('-'));
734
+ expect(dirArg).toBe('my-app');
735
+ });
736
+
737
+ it('dirArg is undefined when no positional given', () => {
738
+ const args = ['create', '--minimal'];
739
+ const dirArg = args.slice(1).find(a => !a.startsWith('-'));
740
+ expect(dirArg).toBeUndefined();
741
+ });
742
+
743
+ it('dirArg with flag after dir name', () => {
744
+ const args = ['create', 'my-app', '--minimal'];
745
+ const dirArg = args.slice(1).find(a => !a.startsWith('-'));
746
+ expect(dirArg).toBe('my-app');
747
+ });
748
+
749
+ // -- help text mentions --minimal --
750
+
751
+ it('help text includes --minimal flag', async () => {
752
+ vi.resetModules();
753
+ const spy = vi.spyOn(console, 'log').mockImplementation(() => {});
754
+ const showHelp = (await import('../cli/help.js')).default;
755
+ showHelp();
756
+ const text = spy.mock.calls.map(c => c.join(' ')).join('\n');
757
+ expect(text).toContain('--minimal');
758
+ spy.mockRestore();
759
+ });
760
+ });