@navneet_25/tempjs 2.0.0 → 3.0.0
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/README.md +66 -18
- package/cli/brand-manager.js +183 -96
- package/cli/config.js +9 -1
- package/cli/doctor.js +393 -0
- package/cli/fetch-core-modules.js +130 -0
- package/cli/index.js +141 -10
- package/cli/info.js +28 -1
- package/cli/init-options.js +209 -0
- package/cli/module-manager.js +168 -0
- package/cli/parse-args.js +21 -0
- package/cli/update.js +15 -0
- package/cli/version-manager.js +518 -0
- package/package.json +27 -4
- package/packages/core/modules/README.md +72 -0
- package/packages/core/modules/blog-compose/app/admin/components/blog-compose-form.ts +44 -0
- package/packages/core/modules/blog-compose/app/admin/hooks/useBlogPosts.ts +103 -0
- package/packages/core/modules/blog-compose/app/admin/panels/BlogComposePanel.tsx +388 -0
- package/packages/core/modules/blog-compose/app/api/blog-posts/[id]/route.ts +29 -0
- package/packages/core/modules/blog-compose/app/api/blog-posts/route.ts +21 -0
- package/packages/core/modules/blog-compose/app/blog/[slug]/page.tsx +79 -0
- package/packages/core/modules/blog-compose/app/blog/layout.tsx +22 -0
- package/packages/core/modules/blog-compose/app/blog/page.tsx +102 -0
- package/packages/core/modules/blog-compose/app/components/BlogSection.tsx +74 -0
- package/packages/core/modules/blog-compose/app/components/BlogShell.tsx +36 -0
- package/packages/core/modules/blog-compose/app/components/BlogSidebar.tsx +112 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/annotate-text.tsx +103 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/blocks/media-blocks.tsx +97 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/blocks/text-blocks.tsx +115 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/index.ts +21 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/interpreter.tsx +55 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/registry.ts +276 -0
- package/packages/core/modules/blog-compose/lib/blog/compose/types.ts +128 -0
- package/packages/core/modules/blog-compose/lib/blog/register-sitemap.ts +18 -0
- package/packages/core/modules/blog-compose/lib/controllers/BlogComposeController.ts +2 -0
- package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.controller.ts +78 -0
- package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.repository.ts +78 -0
- package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.service.ts +72 -0
- package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.types.ts +20 -0
- package/packages/core/modules/blog-compose/lib/features/blog-compose/index.ts +7 -0
- package/packages/core/modules/blog-compose/prisma/blog-compose.prisma +13 -0
- package/packages/core/modules/enquiry-modal/app/components/EnquiryModal.tsx +308 -0
- package/packages/core/modules/footer/app/components/Footer.tsx +127 -0
- package/packages/core/modules/gallery/app/admin/components/GalleryFormModal.tsx +132 -0
- package/packages/core/modules/gallery/app/admin/components/GalleryList.tsx +142 -0
- package/packages/core/modules/gallery/app/admin/hooks/useGallery.ts +72 -0
- package/packages/core/modules/gallery/app/api/gallery/[id]/route.ts +29 -0
- package/packages/core/modules/gallery/app/api/gallery/route.ts +22 -0
- package/packages/core/modules/gallery/app/components/GallerySection.tsx +92 -0
- package/packages/core/modules/gallery/app/gallery/layout.tsx +12 -0
- package/packages/core/modules/gallery/app/gallery/page.tsx +179 -0
- package/packages/core/modules/gallery/lib/controllers/GalleryController.ts +2 -0
- package/packages/core/modules/gallery/lib/features/gallery/gallery.controller.ts +78 -0
- package/packages/core/modules/gallery/lib/features/gallery/gallery.repository.ts +55 -0
- package/packages/core/modules/gallery/lib/features/gallery/gallery.service.ts +43 -0
- package/packages/core/modules/gallery/lib/features/gallery/gallery.types.ts +17 -0
- package/packages/core/modules/gallery/lib/features/gallery/index.ts +4 -0
- package/packages/core/modules/gallery/prisma/gallery.prisma +11 -0
- package/packages/core/modules/hero-simple/app/components/Hero.tsx +160 -0
- package/packages/core/modules/legal-pages/app/components/LegalDocumentPage.tsx +61 -0
- package/packages/core/modules/legal-pages/app/privacy-policy/page.tsx +19 -0
- package/packages/core/modules/legal-pages/app/terms-and-conditions/page.tsx +19 -0
- package/packages/core/modules/reviews/app/admin/components/ReviewFormModal.tsx +110 -0
- package/packages/core/modules/reviews/app/admin/components/ReviewsList.tsx +116 -0
- package/packages/core/modules/reviews/app/admin/hooks/useReviews.ts +70 -0
- package/packages/core/modules/reviews/app/api/reviews/[id]/route.ts +29 -0
- package/packages/core/modules/reviews/app/api/reviews/route.ts +17 -0
- package/packages/core/modules/reviews/app/components/ReviewsSection.tsx +100 -0
- package/packages/core/modules/reviews/lib/controllers/ReviewController.ts +2 -0
- package/packages/core/modules/reviews/lib/features/reviews/index.ts +3 -0
- package/packages/core/modules/reviews/lib/features/reviews/review.controller.ts +64 -0
- package/packages/core/modules/reviews/lib/features/reviews/review.repository.ts +50 -0
- package/packages/core/modules/reviews/lib/features/reviews/review.service.ts +38 -0
- package/packages/core/modules/reviews/prisma/reviews.prisma +9 -0
- package/packages/core/modules/seo/app/components/SiteJsonLd.tsx +16 -0
- package/packages/core/modules/seo/app/robots.ts +30 -0
- package/packages/core/modules/seo/app/sitemap.ts +9 -0
- package/packages/core/modules/seo/lib/seo/index.ts +5 -0
- package/packages/core/modules/seo/lib/seo/json-ld.ts +128 -0
- package/packages/core/modules/seo/lib/seo/metadata.ts +65 -0
- package/packages/core/modules/seo/lib/seo/sitemap.ts +82 -0
- package/packages/core/modules/seo/lib/seo/types.ts +30 -0
- package/packages/core/modules/seo/lib/seo/urls.ts +27 -0
- package/packages/core/modules/theme-modes/app/components/ThemeModeInit.tsx +24 -0
- package/packages/core/modules/theme-modes/app/components/ThemeModeToggle.tsx +58 -0
- package/packages/core/modules.json +140 -0
- package/packages/core/prisma/schema.prisma +33 -0
- package/scripts/module-installer-core.mjs +657 -0
- package/scripts/template-modules-admin.mjs +366 -0
- package/scripts/template-modules-home.mjs +120 -0
- package/templates.json +163 -8
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ git add .
|
|
|
17
17
|
git commit -m "Initial project"
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
**Documentation site:** [`docsite/`](docsite/) — JSON-driven docs for developers and maintainers. Run `pnpm docs:dev` from repo root.
|
|
21
|
+
|
|
20
22
|
## Available templates
|
|
21
23
|
|
|
22
24
|
| ID | Name | Description |
|
|
@@ -202,6 +204,15 @@ tempjs info hotel # shows full version in details
|
|
|
202
204
|
|
|
203
205
|
When maintainers bump `"version"` in `templates.json` and push, clients can pull fixes safely.
|
|
204
206
|
|
|
207
|
+
**Two version numbers (don’t confuse them):**
|
|
208
|
+
|
|
209
|
+
| Version | Where | Meaning |
|
|
210
|
+
|---------|--------|---------|
|
|
211
|
+
| **CLI** (`@navneet_25/tempjs`) | npm / `package.json` in templates repo | The `tempjs` tool itself |
|
|
212
|
+
| **Template** (`hotel`, `real-estate`) | `templates.json` → your `.tempjs.json` | The website template snapshot |
|
|
213
|
+
|
|
214
|
+
Maintainers: see **[VERSIONING.md](./VERSIONING.md)** for `tempjs version check` / `tempjs version inc`.
|
|
215
|
+
|
|
205
216
|
---
|
|
206
217
|
|
|
207
218
|
### Update an existing project (`tempjs update`)
|
|
@@ -462,9 +473,9 @@ npm install -g @navneet_25/tempjs
|
|
|
462
473
|
npm install -g @navneet_25/tempjs@latest
|
|
463
474
|
```
|
|
464
475
|
|
|
465
|
-
Current version: **1.
|
|
476
|
+
Current version: **2.1.0** (doctor, version tracking, copy-once core starter kit, optional sync propagate, update merge).
|
|
466
477
|
|
|
467
|
-
See also: [guide.md](./guide.md) for CLI architecture
|
|
478
|
+
See also: [guide.md](./guide.md) for CLI architecture, [VERSIONING.md](./VERSIONING.md) for release workflow.
|
|
468
479
|
|
|
469
480
|
Verify:
|
|
470
481
|
|
|
@@ -485,9 +496,42 @@ tempjs theme --theme theme3 --yes
|
|
|
485
496
|
tempjs brand --yes --name "Mi Plaza" --base-url "https://miplaza.com"
|
|
486
497
|
tempjs init-db --yes --db-host localhost --db-name my_db --skip-db-push
|
|
487
498
|
tempjs real-estate --force # overwrite existing files
|
|
499
|
+
tempjs doctor # readiness check (generated projects)
|
|
500
|
+
tempjs add-module list # optional core modules available
|
|
501
|
+
tempjs add-module seo,gallery,reviews # add modules to existing project
|
|
488
502
|
tempjs --help # show help
|
|
489
503
|
```
|
|
490
504
|
|
|
505
|
+
### Optional core modules (client projects)
|
|
506
|
+
|
|
507
|
+
After `tempjs hotel`, add cross-vertical features without re-copying the template:
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
tempjs add-module list
|
|
511
|
+
tempjs add-module seo,gallery,reviews
|
|
512
|
+
pnpm prisma db push && pnpm dev
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Modules: `enquiry-modal`, `footer`, `hero-simple`, `seo`, `gallery`, `reviews`, `legal-pages`. See `packages/core/modules.json` and docsite → Developers → Optional modules.
|
|
516
|
+
|
|
517
|
+
Shipped **hotel** and **real-estate** templates already include vertical features and adopted core modules where they overlapped bespoke code.
|
|
518
|
+
|
|
519
|
+
### Maintainer commands (monorepo root)
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
pnpm new-template bakery --name "Bakery" --modules enquiry-modal,footer,hero-simple,seo
|
|
523
|
+
pnpm template:add-module hotel gallery,reviews
|
|
524
|
+
pnpm template:assemble hotel # vertical modules → template root
|
|
525
|
+
pnpm template:extract-modules hotel # template root → modules/
|
|
526
|
+
pnpm sync-templates
|
|
527
|
+
pnpm dev:hotel
|
|
528
|
+
tempjs version check # unreleased CLI/template changes?
|
|
529
|
+
tempjs version inc patch cli
|
|
530
|
+
tempjs version inc minor hotel
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
See [VERSIONING.md](./VERSIONING.md).
|
|
534
|
+
|
|
491
535
|
### Options (summary)
|
|
492
536
|
|
|
493
537
|
| Option | Description |
|
|
@@ -613,36 +657,40 @@ The archive contains the whole templates repo on the wire, but only the requeste
|
|
|
613
657
|
|
|
614
658
|
```
|
|
615
659
|
templates/ # this repository root
|
|
616
|
-
├── ARCHITECTURE.md # core +
|
|
660
|
+
├── ARCHITECTURE.md # copy-once core + standalone templates
|
|
661
|
+
├── MAINTAINERS.md # maintainer handbook
|
|
662
|
+
├── ROADMAP.md # planned improvements
|
|
617
663
|
├── packages/
|
|
618
|
-
│ └── core/ #
|
|
664
|
+
│ └── core/ # starter kit (copied once via new-template)
|
|
619
665
|
├── templates/
|
|
620
|
-
│ ├──
|
|
621
|
-
│ │ ├── hotel-website-template/
|
|
622
|
-
│ │ └── real-estate-website-template/
|
|
623
|
-
│ ├── hotel-website-template/ # merged output (what tempjs copies)
|
|
666
|
+
│ ├── hotel-website-template/ # full hotel project (what tempjs copies)
|
|
624
667
|
│ └── real-estate-website-template/
|
|
625
668
|
├── scripts/
|
|
626
|
-
│
|
|
627
|
-
|
|
669
|
+
│ ├── new-template.mjs
|
|
670
|
+
│ └── sync-templates.mjs # optional: propagate core fixes
|
|
671
|
+
├── package.json # CLI package + maintainer scripts
|
|
628
672
|
├── templates.json
|
|
629
673
|
├── cli/
|
|
630
674
|
│ └── ...
|
|
631
675
|
```
|
|
632
676
|
|
|
633
|
-
## Shared core
|
|
677
|
+
## Shared core starter kit (copy-once)
|
|
634
678
|
|
|
635
|
-
|
|
679
|
+
`packages/core/` is **generic boilerplate** copied once when you run `pnpm new-template`. After that, each template under `templates/<name>/` is a **standalone** Next.js project — edit it directly. Use `pnpm sync-templates` only when you intentionally propagate a fix from core into existing templates.
|
|
636
680
|
|
|
637
|
-
**Full design doc:** [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
681
|
+
**Full design doc:** [ARCHITECTURE.md](./ARCHITECTURE.md) · **Maintainer guide:** [MAINTAINERS.md](./MAINTAINERS.md)
|
|
638
682
|
|
|
639
683
|
### Quick maintainer commands
|
|
640
684
|
|
|
641
685
|
```bash
|
|
642
|
-
#
|
|
643
|
-
pnpm
|
|
686
|
+
# Create a new template (copies core + scaffold once)
|
|
687
|
+
pnpm new-template bakery bakery-website-template --name "Bakery Website"
|
|
644
688
|
|
|
645
|
-
#
|
|
689
|
+
# Daily dev (no sync)
|
|
690
|
+
pnpm dev:hotel
|
|
691
|
+
|
|
692
|
+
# Optional: after fixing packages/core, propagate to existing templates
|
|
693
|
+
pnpm sync-templates
|
|
646
694
|
pnpm sync-templates:check
|
|
647
695
|
```
|
|
648
696
|
|
|
@@ -653,7 +701,7 @@ A normal Next.js project with organized `lib/`:
|
|
|
653
701
|
- `lib/database/` — Prisma client
|
|
654
702
|
- `lib/features/leads/` — shared lead module
|
|
655
703
|
- `lib/storage/` — FTP uploads
|
|
656
|
-
- `lib/features/<domain>/` — template-specific modules (rooms, projects, …)
|
|
704
|
+
- `lib/features/<domain>/` — template-specific modules (rooms, projects, …)
|
|
657
705
|
- `app/` — routes and UI
|
|
658
706
|
|
|
659
707
|
All source is on disk; no submodule, no `@tempjs/core` npm dependency in client projects.
|
|
@@ -662,7 +710,7 @@ All source is on disk; no submodule, no `@tempjs/core` npm dependency in client
|
|
|
662
710
|
|
|
663
711
|
| Approach | User gets full source? | Maintained in this repo |
|
|
664
712
|
|----------|------------------------|-------------------------|
|
|
665
|
-
| **
|
|
713
|
+
| **Copy-once core** (current) | Yes | `packages/core` starter + `templates/<name>/` |
|
|
666
714
|
| CLI merge at copy time | Yes | `packages/core` only in git |
|
|
667
715
|
| npm `@tempjs/core` package | Partial (core in node_modules) | Published package |
|
|
668
716
|
|
package/cli/brand-manager.js
CHANGED
|
@@ -2,6 +2,12 @@ import { createInterface } from "node:readline/promises";
|
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { findSiteTs } from "./theme-manager.js";
|
|
5
|
+
import {
|
|
6
|
+
INIT_FIELD_REGISTRY,
|
|
7
|
+
getTemplateBrandFields,
|
|
8
|
+
pickBrandOptionsForTemplate,
|
|
9
|
+
} from "./init-options.js";
|
|
10
|
+
import { loadManifest } from "./config.js";
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* @typedef {{
|
|
@@ -13,7 +19,14 @@ import { findSiteTs } from "./theme-manager.js";
|
|
|
13
19
|
* phoneDisplay?: string,
|
|
14
20
|
* countryCode?: string,
|
|
15
21
|
* email?: string,
|
|
16
|
-
* address?: string
|
|
22
|
+
* address?: string,
|
|
23
|
+
* tagline?: string,
|
|
24
|
+
* developerName?: string,
|
|
25
|
+
* channelPartner?: string,
|
|
26
|
+
* agentRera?: string,
|
|
27
|
+
* templeDistance?: string,
|
|
28
|
+
* company?: string,
|
|
29
|
+
* location?: string
|
|
17
30
|
* }} BrandOptions
|
|
18
31
|
*/
|
|
19
32
|
|
|
@@ -21,15 +34,26 @@ import { findSiteTs } from "./theme-manager.js";
|
|
|
21
34
|
* @param {string} siteContent
|
|
22
35
|
*/
|
|
23
36
|
function parseCurrentBrandValues(siteContent) {
|
|
37
|
+
const match = (pattern, fallback = "") => {
|
|
38
|
+
const result = siteContent.match(pattern);
|
|
39
|
+
return result?.[1] ?? fallback;
|
|
40
|
+
};
|
|
41
|
+
|
|
24
42
|
return {
|
|
25
|
-
brandName:
|
|
26
|
-
shortName:
|
|
27
|
-
baseUrl:
|
|
28
|
-
phone:
|
|
29
|
-
phoneDisplay:
|
|
30
|
-
countryCode:
|
|
31
|
-
email:
|
|
32
|
-
address:
|
|
43
|
+
brandName: match(/name:\s*"([^"]+)"/, "Demo Client Site"),
|
|
44
|
+
shortName: match(/shortName:\s*"([^"]+)"/, "Demo"),
|
|
45
|
+
baseUrl: match(/baseUrl:\s*"([^"]+)"/, "https://example.com"),
|
|
46
|
+
phone: match(/phone:\s*"([^"]+)"/, "9876543210"),
|
|
47
|
+
phoneDisplay: match(/phoneDisplay:\s*"([^"]+)"/, "+91 98765 43210"),
|
|
48
|
+
countryCode: match(/countryCode:\s*"([^"]+)"/, "91"),
|
|
49
|
+
email: match(/email:\s*"([^"]+)"/, "hello@example.com"),
|
|
50
|
+
address: match(/full:\s*"([^"]+)"/, "Lonavala, Maharashtra, India"),
|
|
51
|
+
tagline: match(/tagline:\s*"([^"]+)"/, ""),
|
|
52
|
+
developerName: match(/developerName:\s*"([^"]+)"/, ""),
|
|
53
|
+
channelPartner: match(/channelPartner:\s*"([^"]+)"/, ""),
|
|
54
|
+
agentRera: match(/agentRera:\s*"([^"]*)"/, ""),
|
|
55
|
+
templeDistance: match(/templeDistance:\s*"([^"]*)"/, ""),
|
|
56
|
+
locality: match(/locality:\s*"([^"]+)"/, "Lonavala"),
|
|
33
57
|
};
|
|
34
58
|
}
|
|
35
59
|
|
|
@@ -47,72 +71,103 @@ function deriveWwwHost(baseUrl) {
|
|
|
47
71
|
}
|
|
48
72
|
}
|
|
49
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} value
|
|
76
|
+
*/
|
|
77
|
+
function escapeForTsString(value) {
|
|
78
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
79
|
+
}
|
|
80
|
+
|
|
50
81
|
/**
|
|
51
82
|
* @param {string} siteContent
|
|
52
|
-
* @param {
|
|
53
|
-
*
|
|
54
|
-
* shortName: string,
|
|
55
|
-
* baseUrl: string,
|
|
56
|
-
* phone: string,
|
|
57
|
-
* phoneDisplay: string,
|
|
58
|
-
* countryCode: string,
|
|
59
|
-
* email: string,
|
|
60
|
-
* address: string
|
|
61
|
-
* }} values
|
|
83
|
+
* @param {ReturnType<typeof parseCurrentBrandValues>} current
|
|
84
|
+
* @param {BrandOptions} options
|
|
62
85
|
*/
|
|
63
|
-
function
|
|
64
|
-
const
|
|
86
|
+
function buildBrandValues(current, options) {
|
|
87
|
+
const brandName = options.name?.trim() || current.brandName;
|
|
88
|
+
const developerName =
|
|
89
|
+
options.developerName?.trim() || options.company?.trim() || current.developerName || brandName;
|
|
90
|
+
|
|
91
|
+
let address = options.address?.trim() || current.address;
|
|
92
|
+
let locality = current.locality;
|
|
93
|
+
|
|
94
|
+
if (options.location?.trim() && !options.address?.trim()) {
|
|
95
|
+
locality = options.location.trim();
|
|
96
|
+
const parts = address.split(",");
|
|
97
|
+
if (parts.length >= 2) {
|
|
98
|
+
address = `${locality}, ${parts.slice(1).join(",").trim()}`;
|
|
99
|
+
} else {
|
|
100
|
+
address = locality;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const addressParts = address.split(",");
|
|
105
|
+
if (addressParts.length >= 2) {
|
|
106
|
+
locality = addressParts[0].trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
65
110
|
brandName,
|
|
66
|
-
shortName,
|
|
67
|
-
baseUrl,
|
|
68
|
-
phone,
|
|
69
|
-
phoneDisplay,
|
|
70
|
-
countryCode,
|
|
71
|
-
email,
|
|
111
|
+
shortName: options.shortName?.trim() || current.shortName,
|
|
112
|
+
baseUrl: options.baseUrl?.trim() || current.baseUrl,
|
|
113
|
+
phone: options.phone?.trim() || current.phone,
|
|
114
|
+
phoneDisplay: options.phoneDisplay?.trim() || current.phoneDisplay,
|
|
115
|
+
countryCode: options.countryCode?.trim() || current.countryCode,
|
|
116
|
+
email: options.email?.trim() || current.email,
|
|
72
117
|
address,
|
|
73
|
-
|
|
118
|
+
locality,
|
|
119
|
+
region: addressParts.length >= 2 ? addressParts[1].trim() : "MH",
|
|
120
|
+
tagline: options.tagline?.trim() || current.tagline,
|
|
121
|
+
developerName,
|
|
122
|
+
channelPartner: options.channelPartner?.trim() || current.channelPartner || `${brandName} Partner`,
|
|
123
|
+
agentRera: options.agentRera?.trim() ?? current.agentRera,
|
|
124
|
+
templeDistance: options.templeDistance?.trim() ?? current.templeDistance,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
74
127
|
|
|
75
|
-
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} siteContent
|
|
130
|
+
* @param {ReturnType<typeof buildBrandValues>} values
|
|
131
|
+
*/
|
|
132
|
+
function applyBrandToSiteContent(siteContent, values) {
|
|
133
|
+
const wwwHost = deriveWwwHost(values.baseUrl);
|
|
76
134
|
|
|
77
135
|
const newBrandBlock = `brand: {
|
|
78
|
-
name: "${brandName}",
|
|
79
|
-
shortName: "${shortName}",
|
|
80
|
-
tagline: "
|
|
81
|
-
developerName: "${
|
|
82
|
-
channelPartner: "${
|
|
83
|
-
copyright: "${brandName}. All Rights Reserved.",
|
|
84
|
-
managedBy: "Managed by ${
|
|
136
|
+
name: "${escapeForTsString(values.brandName)}",
|
|
137
|
+
shortName: "${escapeForTsString(values.shortName)}",
|
|
138
|
+
tagline: "${escapeForTsString(values.tagline)}",
|
|
139
|
+
developerName: "${escapeForTsString(values.developerName)}",
|
|
140
|
+
channelPartner: "${escapeForTsString(values.channelPartner)}",
|
|
141
|
+
copyright: "${escapeForTsString(values.brandName)}. All Rights Reserved.",
|
|
142
|
+
managedBy: "Managed by ${escapeForTsString(values.developerName)}.",
|
|
85
143
|
}`;
|
|
86
144
|
|
|
87
145
|
const newDomainBlock = `domain: {
|
|
88
|
-
baseUrl: "${baseUrl}",
|
|
89
|
-
wwwHost: "${wwwHost}",
|
|
146
|
+
baseUrl: "${escapeForTsString(values.baseUrl)}",
|
|
147
|
+
wwwHost: "${escapeForTsString(wwwHost)}",
|
|
90
148
|
}`;
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (addressParts.length >= 2) {
|
|
96
|
-
locality = addressParts[0].trim();
|
|
97
|
-
region = addressParts[1].trim();
|
|
98
|
-
}
|
|
150
|
+
const templeLine = values.templeDistance
|
|
151
|
+
? `\n templeDistance: "${escapeForTsString(values.templeDistance)}",`
|
|
152
|
+
: "";
|
|
99
153
|
|
|
100
154
|
const newContactBlock = `contact: {
|
|
101
|
-
phone: "${phone}",
|
|
102
|
-
phoneDisplay: "${phoneDisplay}",
|
|
103
|
-
countryCode: "${countryCode}",
|
|
104
|
-
email: "${email}",
|
|
155
|
+
phone: "${escapeForTsString(values.phone)}",
|
|
156
|
+
phoneDisplay: "${escapeForTsString(values.phoneDisplay)}",
|
|
157
|
+
countryCode: "${escapeForTsString(values.countryCode)}",
|
|
158
|
+
email: "${escapeForTsString(values.email)}",
|
|
105
159
|
address: {
|
|
106
|
-
locality: "${locality}",
|
|
107
|
-
region: "${region}",
|
|
160
|
+
locality: "${escapeForTsString(values.locality)}",
|
|
161
|
+
region: "${escapeForTsString(values.region)}",
|
|
108
162
|
country: "IN",
|
|
109
|
-
full: "${address}",
|
|
110
|
-
}
|
|
163
|
+
full: "${escapeForTsString(values.address)}",
|
|
164
|
+
},${templeLine}
|
|
111
165
|
}`;
|
|
112
166
|
|
|
113
167
|
const brandRegex = /brand:\s*\{[\s\S]*?\n\s*\},/;
|
|
114
168
|
const domainRegex = /domain:\s*\{[\s\S]*?\n\s*\},/;
|
|
115
169
|
const contactRegex = /contact:\s*\{[\s\S]*?address:\s*\{[\s\S]*?\}[\s\S]*?\n\s*\},/;
|
|
170
|
+
const agentReraRegex = /agentRera:\s*"[^"]*"/;
|
|
116
171
|
|
|
117
172
|
if (brandRegex.test(siteContent)) {
|
|
118
173
|
siteContent = siteContent.replace(brandRegex, newBrandBlock + ",");
|
|
@@ -123,15 +178,65 @@ function applyBrandToSiteContent(siteContent, values) {
|
|
|
123
178
|
if (contactRegex.test(siteContent)) {
|
|
124
179
|
siteContent = siteContent.replace(contactRegex, newContactBlock + ",");
|
|
125
180
|
}
|
|
181
|
+
if (agentReraRegex.test(siteContent)) {
|
|
182
|
+
siteContent = siteContent.replace(
|
|
183
|
+
agentReraRegex,
|
|
184
|
+
`agentRera: "${escapeForTsString(values.agentRera)}"`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
126
187
|
|
|
127
188
|
return siteContent;
|
|
128
189
|
}
|
|
129
190
|
|
|
191
|
+
/**
|
|
192
|
+
* @param {ReturnType<typeof parseCurrentBrandValues>} current
|
|
193
|
+
* @param {string} fieldKey
|
|
194
|
+
*/
|
|
195
|
+
function currentValueForField(current, fieldKey) {
|
|
196
|
+
const def = INIT_FIELD_REGISTRY[fieldKey];
|
|
197
|
+
if (!def) return "";
|
|
198
|
+
|
|
199
|
+
switch (fieldKey) {
|
|
200
|
+
case "name":
|
|
201
|
+
return current.brandName;
|
|
202
|
+
case "shortName":
|
|
203
|
+
return current.shortName;
|
|
204
|
+
case "baseUrl":
|
|
205
|
+
return current.baseUrl;
|
|
206
|
+
case "phone":
|
|
207
|
+
return current.phone;
|
|
208
|
+
case "phoneDisplay":
|
|
209
|
+
return current.phoneDisplay;
|
|
210
|
+
case "countryCode":
|
|
211
|
+
return current.countryCode;
|
|
212
|
+
case "email":
|
|
213
|
+
return current.email;
|
|
214
|
+
case "address":
|
|
215
|
+
return current.address;
|
|
216
|
+
case "tagline":
|
|
217
|
+
return current.tagline;
|
|
218
|
+
case "developerName":
|
|
219
|
+
case "company":
|
|
220
|
+
return current.developerName;
|
|
221
|
+
case "channelPartner":
|
|
222
|
+
return current.channelPartner;
|
|
223
|
+
case "agentRera":
|
|
224
|
+
return current.agentRera;
|
|
225
|
+
case "templeDistance":
|
|
226
|
+
return current.templeDistance;
|
|
227
|
+
case "location":
|
|
228
|
+
return current.locality;
|
|
229
|
+
default:
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
130
234
|
/**
|
|
131
235
|
* @param {string} targetDir
|
|
132
236
|
* @param {BrandOptions} options
|
|
237
|
+
* @param {string} [templateId]
|
|
133
238
|
*/
|
|
134
|
-
export async function applyBrand(targetDir, options = {}) {
|
|
239
|
+
export async function applyBrand(targetDir, options = {}, templateId) {
|
|
135
240
|
const siteTsPath = await findSiteTs(targetDir);
|
|
136
241
|
if (!siteTsPath) {
|
|
137
242
|
throw new Error(
|
|
@@ -141,18 +246,7 @@ export async function applyBrand(targetDir, options = {}) {
|
|
|
141
246
|
|
|
142
247
|
let siteContent = await readFile(siteTsPath, "utf8");
|
|
143
248
|
const current = parseCurrentBrandValues(siteContent);
|
|
144
|
-
|
|
145
|
-
const values = {
|
|
146
|
-
brandName: options.name?.trim() || current.brandName,
|
|
147
|
-
shortName: options.shortName?.trim() || current.shortName,
|
|
148
|
-
baseUrl: options.baseUrl?.trim() || current.baseUrl,
|
|
149
|
-
phone: options.phone?.trim() || current.phone,
|
|
150
|
-
phoneDisplay: options.phoneDisplay?.trim() || current.phoneDisplay,
|
|
151
|
-
countryCode: options.countryCode?.trim() || current.countryCode,
|
|
152
|
-
email: options.email?.trim() || current.email,
|
|
153
|
-
address: options.address?.trim() || current.address,
|
|
154
|
-
};
|
|
155
|
-
|
|
249
|
+
const values = buildBrandValues(current, options);
|
|
156
250
|
siteContent = applyBrandToSiteContent(siteContent, values);
|
|
157
251
|
await writeFile(siteTsPath, siteContent, "utf8");
|
|
158
252
|
console.log("Successfully updated brand and contact configurations in constants/site.ts.");
|
|
@@ -161,10 +255,18 @@ export async function applyBrand(targetDir, options = {}) {
|
|
|
161
255
|
/**
|
|
162
256
|
* @param {string} targetDir
|
|
163
257
|
* @param {BrandOptions} options
|
|
258
|
+
* @param {string} [templateId]
|
|
164
259
|
*/
|
|
165
|
-
export async function promptAndApplyBrand(targetDir, options = {}) {
|
|
260
|
+
export async function promptAndApplyBrand(targetDir, options = {}, templateId) {
|
|
261
|
+
const manifest = loadManifest();
|
|
262
|
+
const entry = templateId ? manifest.templates[templateId] : undefined;
|
|
263
|
+
const fieldKeys = entry?.init?.brandFields ?? Object.keys(INIT_FIELD_REGISTRY);
|
|
264
|
+
|
|
166
265
|
if (options.yes) {
|
|
167
|
-
|
|
266
|
+
const picked = entry
|
|
267
|
+
? pickBrandOptionsForTemplate(templateId, entry, options)
|
|
268
|
+
: options;
|
|
269
|
+
await applyBrand(targetDir, picked, templateId);
|
|
168
270
|
return;
|
|
169
271
|
}
|
|
170
272
|
|
|
@@ -178,40 +280,25 @@ export async function promptAndApplyBrand(targetDir, options = {}) {
|
|
|
178
280
|
|
|
179
281
|
let siteContent = await readFile(siteTsPath, "utf8");
|
|
180
282
|
const current = parseCurrentBrandValues(siteContent);
|
|
283
|
+
const collected = { ...options };
|
|
181
284
|
|
|
182
285
|
const rl = createInterface({ input, output });
|
|
183
286
|
try {
|
|
184
287
|
console.log("\n--- Configure Brand & Contact Details ---");
|
|
185
288
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
(await rl.question(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
current.phoneDisplay;
|
|
197
|
-
const countryCode =
|
|
198
|
-
(await rl.question(`Country Code [${current.countryCode}]: `)).trim() || current.countryCode;
|
|
199
|
-
const email =
|
|
200
|
-
(await rl.question(`Contact Email [${current.email}]: `)).trim() || current.email;
|
|
201
|
-
const address =
|
|
202
|
-
(await rl.question(`Full Address [${current.address}]: `)).trim() || current.address;
|
|
203
|
-
|
|
204
|
-
siteContent = applyBrandToSiteContent(siteContent, {
|
|
205
|
-
brandName,
|
|
206
|
-
shortName,
|
|
207
|
-
baseUrl,
|
|
208
|
-
phone,
|
|
209
|
-
phoneDisplay,
|
|
210
|
-
countryCode,
|
|
211
|
-
email,
|
|
212
|
-
address,
|
|
213
|
-
});
|
|
289
|
+
for (const fieldKey of fieldKeys) {
|
|
290
|
+
const field = INIT_FIELD_REGISTRY[fieldKey];
|
|
291
|
+
if (!field) continue;
|
|
292
|
+
|
|
293
|
+
const currentVal = currentValueForField(current, fieldKey);
|
|
294
|
+
const answer = (await rl.question(`${field.prompt} [${currentVal}]: `)).trim();
|
|
295
|
+
if (answer) {
|
|
296
|
+
collected[field.cliKey] = answer;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
214
299
|
|
|
300
|
+
const values = buildBrandValues(current, collected);
|
|
301
|
+
siteContent = applyBrandToSiteContent(siteContent, values);
|
|
215
302
|
await writeFile(siteTsPath, siteContent, "utf8");
|
|
216
303
|
console.log("Successfully updated brand and contact configurations in constants/site.ts.");
|
|
217
304
|
} finally {
|
package/cli/config.js
CHANGED
|
@@ -17,7 +17,15 @@ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
17
17
|
* docker?: boolean,
|
|
18
18
|
* tags?: string[],
|
|
19
19
|
* features?: string[],
|
|
20
|
-
* docs?: string
|
|
20
|
+
* docs?: string,
|
|
21
|
+
* coreModules?: string[],
|
|
22
|
+
* templateModules?: string[],
|
|
23
|
+
* init?: {
|
|
24
|
+
* flagGroups?: string[],
|
|
25
|
+
* brandFields?: string[],
|
|
26
|
+
* skipBrand?: boolean,
|
|
27
|
+
* skipDatabase?: boolean
|
|
28
|
+
* }
|
|
21
29
|
* }} TemplateEntry */
|
|
22
30
|
/** @typedef {{ repository: RepositoryConfig, templates: Record<string, TemplateEntry> }} Manifest */
|
|
23
31
|
|