@navneet_25/tempjs 1.1.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.
Files changed (100) hide show
  1. package/README.md +800 -35
  2. package/cli/brand-manager.js +277 -78
  3. package/cli/config.js +22 -1
  4. package/cli/copy.js +4 -18
  5. package/cli/db-setup.js +170 -49
  6. package/cli/doctor.js +393 -0
  7. package/cli/fetch-core-modules.js +130 -0
  8. package/cli/fetch.js +47 -29
  9. package/cli/file-tree.js +113 -0
  10. package/cli/fs-ignore.js +60 -0
  11. package/cli/index.js +274 -112
  12. package/cli/info.js +115 -0
  13. package/cli/init-options.js +209 -0
  14. package/cli/module-manager.js +168 -0
  15. package/cli/parse-args.js +177 -0
  16. package/cli/progress.js +33 -0
  17. package/cli/project-stamp.js +69 -0
  18. package/cli/prompt.js +19 -0
  19. package/cli/template-resolver.js +48 -0
  20. package/cli/theme-manager.js +28 -2
  21. package/cli/update.js +227 -0
  22. package/cli/version-manager.js +518 -0
  23. package/package.json +31 -4
  24. package/packages/core/modules/README.md +72 -0
  25. package/packages/core/modules/blog-compose/app/admin/components/blog-compose-form.ts +44 -0
  26. package/packages/core/modules/blog-compose/app/admin/hooks/useBlogPosts.ts +103 -0
  27. package/packages/core/modules/blog-compose/app/admin/panels/BlogComposePanel.tsx +388 -0
  28. package/packages/core/modules/blog-compose/app/api/blog-posts/[id]/route.ts +29 -0
  29. package/packages/core/modules/blog-compose/app/api/blog-posts/route.ts +21 -0
  30. package/packages/core/modules/blog-compose/app/blog/[slug]/page.tsx +79 -0
  31. package/packages/core/modules/blog-compose/app/blog/layout.tsx +22 -0
  32. package/packages/core/modules/blog-compose/app/blog/page.tsx +102 -0
  33. package/packages/core/modules/blog-compose/app/components/BlogSection.tsx +74 -0
  34. package/packages/core/modules/blog-compose/app/components/BlogShell.tsx +36 -0
  35. package/packages/core/modules/blog-compose/app/components/BlogSidebar.tsx +112 -0
  36. package/packages/core/modules/blog-compose/lib/blog/compose/annotate-text.tsx +103 -0
  37. package/packages/core/modules/blog-compose/lib/blog/compose/blocks/media-blocks.tsx +97 -0
  38. package/packages/core/modules/blog-compose/lib/blog/compose/blocks/text-blocks.tsx +115 -0
  39. package/packages/core/modules/blog-compose/lib/blog/compose/index.ts +21 -0
  40. package/packages/core/modules/blog-compose/lib/blog/compose/interpreter.tsx +55 -0
  41. package/packages/core/modules/blog-compose/lib/blog/compose/registry.ts +276 -0
  42. package/packages/core/modules/blog-compose/lib/blog/compose/types.ts +128 -0
  43. package/packages/core/modules/blog-compose/lib/blog/register-sitemap.ts +18 -0
  44. package/packages/core/modules/blog-compose/lib/controllers/BlogComposeController.ts +2 -0
  45. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.controller.ts +78 -0
  46. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.repository.ts +78 -0
  47. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.service.ts +72 -0
  48. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.types.ts +20 -0
  49. package/packages/core/modules/blog-compose/lib/features/blog-compose/index.ts +7 -0
  50. package/packages/core/modules/blog-compose/prisma/blog-compose.prisma +13 -0
  51. package/packages/core/modules/enquiry-modal/app/components/EnquiryModal.tsx +308 -0
  52. package/packages/core/modules/footer/app/components/Footer.tsx +127 -0
  53. package/packages/core/modules/gallery/app/admin/components/GalleryFormModal.tsx +132 -0
  54. package/packages/core/modules/gallery/app/admin/components/GalleryList.tsx +142 -0
  55. package/packages/core/modules/gallery/app/admin/hooks/useGallery.ts +72 -0
  56. package/packages/core/modules/gallery/app/api/gallery/[id]/route.ts +29 -0
  57. package/packages/core/modules/gallery/app/api/gallery/route.ts +22 -0
  58. package/packages/core/modules/gallery/app/components/GallerySection.tsx +92 -0
  59. package/packages/core/modules/gallery/app/gallery/layout.tsx +12 -0
  60. package/packages/core/modules/gallery/app/gallery/page.tsx +179 -0
  61. package/packages/core/modules/gallery/lib/controllers/GalleryController.ts +2 -0
  62. package/packages/core/modules/gallery/lib/features/gallery/gallery.controller.ts +78 -0
  63. package/packages/core/modules/gallery/lib/features/gallery/gallery.repository.ts +55 -0
  64. package/packages/core/modules/gallery/lib/features/gallery/gallery.service.ts +43 -0
  65. package/packages/core/modules/gallery/lib/features/gallery/gallery.types.ts +17 -0
  66. package/packages/core/modules/gallery/lib/features/gallery/index.ts +4 -0
  67. package/packages/core/modules/gallery/prisma/gallery.prisma +11 -0
  68. package/packages/core/modules/hero-simple/app/components/Hero.tsx +160 -0
  69. package/packages/core/modules/legal-pages/app/components/LegalDocumentPage.tsx +61 -0
  70. package/packages/core/modules/legal-pages/app/privacy-policy/page.tsx +19 -0
  71. package/packages/core/modules/legal-pages/app/terms-and-conditions/page.tsx +19 -0
  72. package/packages/core/modules/reviews/app/admin/components/ReviewFormModal.tsx +110 -0
  73. package/packages/core/modules/reviews/app/admin/components/ReviewsList.tsx +116 -0
  74. package/packages/core/modules/reviews/app/admin/hooks/useReviews.ts +70 -0
  75. package/packages/core/modules/reviews/app/api/reviews/[id]/route.ts +29 -0
  76. package/packages/core/modules/reviews/app/api/reviews/route.ts +17 -0
  77. package/packages/core/modules/reviews/app/components/ReviewsSection.tsx +100 -0
  78. package/packages/core/modules/reviews/lib/controllers/ReviewController.ts +2 -0
  79. package/packages/core/modules/reviews/lib/features/reviews/index.ts +3 -0
  80. package/packages/core/modules/reviews/lib/features/reviews/review.controller.ts +64 -0
  81. package/packages/core/modules/reviews/lib/features/reviews/review.repository.ts +50 -0
  82. package/packages/core/modules/reviews/lib/features/reviews/review.service.ts +38 -0
  83. package/packages/core/modules/reviews/prisma/reviews.prisma +9 -0
  84. package/packages/core/modules/seo/app/components/SiteJsonLd.tsx +16 -0
  85. package/packages/core/modules/seo/app/robots.ts +30 -0
  86. package/packages/core/modules/seo/app/sitemap.ts +9 -0
  87. package/packages/core/modules/seo/lib/seo/index.ts +5 -0
  88. package/packages/core/modules/seo/lib/seo/json-ld.ts +128 -0
  89. package/packages/core/modules/seo/lib/seo/metadata.ts +65 -0
  90. package/packages/core/modules/seo/lib/seo/sitemap.ts +82 -0
  91. package/packages/core/modules/seo/lib/seo/types.ts +30 -0
  92. package/packages/core/modules/seo/lib/seo/urls.ts +27 -0
  93. package/packages/core/modules/theme-modes/app/components/ThemeModeInit.tsx +24 -0
  94. package/packages/core/modules/theme-modes/app/components/ThemeModeToggle.tsx +58 -0
  95. package/packages/core/modules.json +140 -0
  96. package/packages/core/prisma/schema.prisma +33 -0
  97. package/scripts/module-installer-core.mjs +657 -0
  98. package/scripts/template-modules-admin.mjs +366 -0
  99. package/scripts/template-modules-home.mjs +120 -0
  100. package/templates.json +188 -3
@@ -2,104 +2,303 @@ 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
- export async function promptAndApplyBrand(targetDir) {
7
- const siteTsPath = await findSiteTs(targetDir);
8
- if (!siteTsPath) {
9
- console.error("Error: Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory.");
10
- return;
11
- }
12
+ /**
13
+ * @typedef {{
14
+ * yes?: boolean,
15
+ * name?: string,
16
+ * shortName?: string,
17
+ * baseUrl?: string,
18
+ * phone?: string,
19
+ * phoneDisplay?: string,
20
+ * countryCode?: string,
21
+ * email?: 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
30
+ * }} BrandOptions
31
+ */
12
32
 
13
- // Load current values if possible to provide defaults
14
- let siteContent = await readFile(siteTsPath, "utf8");
33
+ /**
34
+ * @param {string} siteContent
35
+ */
36
+ function parseCurrentBrandValues(siteContent) {
37
+ const match = (pattern, fallback = "") => {
38
+ const result = siteContent.match(pattern);
39
+ return result?.[1] ?? fallback;
40
+ };
15
41
 
16
- // Parse existing values using simple regex
17
- const currentBrandName = (siteContent.match(/name:\s*"([^"]+)"/) || [])[1] || "Chanakya Resort";
18
- const currentShortName = (siteContent.match(/shortName:\s*"([^"]+)"/) || [])[1] || "Chanakya";
19
- const currentBaseUrl = (siteContent.match(/baseUrl:\s*"([^"]+)"/) || [])[1] || "https://chanakyaresort.com";
20
- const currentPhone = (siteContent.match(/phone:\s*"([^"]+)"/) || [])[1] || "9876543210";
21
- const currentPhoneDisplay = (siteContent.match(/phoneDisplay:\s*"([^"]+)"/) || [])[1] || "+91 98765 43210";
22
- const currentCountryCode = (siteContent.match(/countryCode:\s*"([^"]+)"/) || [])[1] || "91";
23
- const currentEmail = (siteContent.match(/email:\s*"([^"]+)"/) || [])[1] || "reservations@chanakyaresort.com";
24
- const currentAddress = (siteContent.match(/full:\s*"([^"]+)"/) || [])[1] || "Lonavala, Maharashtra, India";
42
+ return {
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"),
57
+ };
58
+ }
25
59
 
26
- const rl = createInterface({ input, output });
60
+ /**
61
+ * @param {string} baseUrl
62
+ */
63
+ function deriveWwwHost(baseUrl) {
27
64
  try {
28
- console.log("\n--- Configure Brand & Contact Details ---");
65
+ const url = new URL(baseUrl);
66
+ return url.hostname.startsWith("www.") ? url.hostname : `www.${url.hostname}`;
67
+ } catch {
68
+ let wwwHost = baseUrl.replace(/^https?:\/\//, "");
69
+ if (!wwwHost.startsWith("www.")) wwwHost = `www.${wwwHost}`;
70
+ return wwwHost;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param {string} value
76
+ */
77
+ function escapeForTsString(value) {
78
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
79
+ }
29
80
 
30
- const brandName = (await rl.question(`Brand Name [${currentBrandName}]: `)).trim() || currentBrandName;
31
- const shortName = (await rl.question(`Short/Display Name [${currentShortName}]: `)).trim() || currentShortName;
32
- const baseUrl = (await rl.question(`Base URL [${currentBaseUrl}]: `)).trim() || currentBaseUrl;
33
-
34
- // Derive wwwHost from baseUrl
35
- let wwwHost = "www.example.com";
36
- try {
37
- const url = new URL(baseUrl);
38
- wwwHost = url.hostname.startsWith("www.") ? url.hostname : `www.${url.hostname}`;
39
- } catch {
40
- wwwHost = baseUrl.replace(/^https?:\/\//, "");
41
- if (!wwwHost.startsWith("www.")) wwwHost = `www.${wwwHost}`;
81
+ /**
82
+ * @param {string} siteContent
83
+ * @param {ReturnType<typeof parseCurrentBrandValues>} current
84
+ * @param {BrandOptions} options
85
+ */
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;
42
101
  }
102
+ }
43
103
 
44
- const phone = (await rl.question(`Contact Phone Number [${currentPhone}]: `)).trim() || currentPhone;
45
- const phoneDisplay = (await rl.question(`Display Phone Number [${currentPhoneDisplay}]: `)).trim() || currentPhoneDisplay;
46
- const countryCode = (await rl.question(`Country Code [${currentCountryCode}]: `)).trim() || currentCountryCode;
47
- const email = (await rl.question(`Contact Email [${currentEmail}]: `)).trim() || currentEmail;
48
- const address = (await rl.question(`Full Address [${currentAddress}]: `)).trim() || currentAddress;
49
-
50
- // Build replacement blocks
51
- const newBrandBlock = `brand: {
52
- name: "${brandName}",
53
- shortName: "${shortName}",
54
- tagline: "Where Nature Meets Refined Comfort",
55
- developerName: "${brandName}",
56
- channelPartner: "${brandName} Partner",
57
- copyright: "${brandName}. All Rights Reserved.",
58
- managedBy: "Managed by ${brandName}.",
104
+ const addressParts = address.split(",");
105
+ if (addressParts.length >= 2) {
106
+ locality = addressParts[0].trim();
107
+ }
108
+
109
+ return {
110
+ brandName,
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,
117
+ address,
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
+ }
127
+
128
+ /**
129
+ * @param {string} siteContent
130
+ * @param {ReturnType<typeof buildBrandValues>} values
131
+ */
132
+ function applyBrandToSiteContent(siteContent, values) {
133
+ const wwwHost = deriveWwwHost(values.baseUrl);
134
+
135
+ const newBrandBlock = `brand: {
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)}.",
59
143
  }`;
60
144
 
61
- const newDomainBlock = `domain: {
62
- baseUrl: "${baseUrl}",
63
- wwwHost: "${wwwHost}",
145
+ const newDomainBlock = `domain: {
146
+ baseUrl: "${escapeForTsString(values.baseUrl)}",
147
+ wwwHost: "${escapeForTsString(wwwHost)}",
64
148
  }`;
65
149
 
66
- // Extract address locality/region/country or keep defaults
67
- let locality = "Lonavala";
68
- let region = "MH";
69
- const addressParts = address.split(",");
70
- if (addressParts.length >= 2) {
71
- locality = addressParts[0].trim();
72
- region = addressParts[1].trim();
73
- }
150
+ const templeLine = values.templeDistance
151
+ ? `\n templeDistance: "${escapeForTsString(values.templeDistance)}",`
152
+ : "";
74
153
 
75
- const newContactBlock = `contact: {
76
- phone: "${phone}",
77
- phoneDisplay: "${phoneDisplay}",
78
- countryCode: "${countryCode}",
79
- email: "${email}",
154
+ const newContactBlock = `contact: {
155
+ phone: "${escapeForTsString(values.phone)}",
156
+ phoneDisplay: "${escapeForTsString(values.phoneDisplay)}",
157
+ countryCode: "${escapeForTsString(values.countryCode)}",
158
+ email: "${escapeForTsString(values.email)}",
80
159
  address: {
81
- locality: "${locality}",
82
- region: "${region}",
160
+ locality: "${escapeForTsString(values.locality)}",
161
+ region: "${escapeForTsString(values.region)}",
83
162
  country: "IN",
84
- full: "${address}",
85
- },
163
+ full: "${escapeForTsString(values.address)}",
164
+ },${templeLine}
86
165
  }`;
87
166
 
88
- // Apply replacements using exact patterns matching our templates
89
- const brandRegex = /brand:\s*\{[\s\S]*?\n\s*\},/;
90
- const domainRegex = /domain:\s*\{[\s\S]*?\n\s*\},/;
91
- const contactRegex = /contact:\s*\{[\s\S]*?address:\s*\{[\s\S]*?\}[\s\S]*?\n\s*\},/;
167
+ const brandRegex = /brand:\s*\{[\s\S]*?\n\s*\},/;
168
+ const domainRegex = /domain:\s*\{[\s\S]*?\n\s*\},/;
169
+ const contactRegex = /contact:\s*\{[\s\S]*?address:\s*\{[\s\S]*?\}[\s\S]*?\n\s*\},/;
170
+ const agentReraRegex = /agentRera:\s*"[^"]*"/;
92
171
 
93
- if (brandRegex.test(siteContent)) {
94
- siteContent = siteContent.replace(brandRegex, newBrandBlock + ",");
95
- }
96
- if (domainRegex.test(siteContent)) {
97
- siteContent = siteContent.replace(domainRegex, newDomainBlock + ",");
98
- }
99
- if (contactRegex.test(siteContent)) {
100
- siteContent = siteContent.replace(contactRegex, newContactBlock + ",");
172
+ if (brandRegex.test(siteContent)) {
173
+ siteContent = siteContent.replace(brandRegex, newBrandBlock + ",");
174
+ }
175
+ if (domainRegex.test(siteContent)) {
176
+ siteContent = siteContent.replace(domainRegex, newDomainBlock + ",");
177
+ }
178
+ if (contactRegex.test(siteContent)) {
179
+ siteContent = siteContent.replace(contactRegex, newContactBlock + ",");
180
+ }
181
+ if (agentReraRegex.test(siteContent)) {
182
+ siteContent = siteContent.replace(
183
+ agentReraRegex,
184
+ `agentRera: "${escapeForTsString(values.agentRera)}"`
185
+ );
186
+ }
187
+
188
+ return siteContent;
189
+ }
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
+
234
+ /**
235
+ * @param {string} targetDir
236
+ * @param {BrandOptions} options
237
+ * @param {string} [templateId]
238
+ */
239
+ export async function applyBrand(targetDir, options = {}, templateId) {
240
+ const siteTsPath = await findSiteTs(targetDir);
241
+ if (!siteTsPath) {
242
+ throw new Error(
243
+ "Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory."
244
+ );
245
+ }
246
+
247
+ let siteContent = await readFile(siteTsPath, "utf8");
248
+ const current = parseCurrentBrandValues(siteContent);
249
+ const values = buildBrandValues(current, options);
250
+ siteContent = applyBrandToSiteContent(siteContent, values);
251
+ await writeFile(siteTsPath, siteContent, "utf8");
252
+ console.log("Successfully updated brand and contact configurations in constants/site.ts.");
253
+ }
254
+
255
+ /**
256
+ * @param {string} targetDir
257
+ * @param {BrandOptions} options
258
+ * @param {string} [templateId]
259
+ */
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
+
265
+ if (options.yes) {
266
+ const picked = entry
267
+ ? pickBrandOptionsForTemplate(templateId, entry, options)
268
+ : options;
269
+ await applyBrand(targetDir, picked, templateId);
270
+ return;
271
+ }
272
+
273
+ const siteTsPath = await findSiteTs(targetDir);
274
+ if (!siteTsPath) {
275
+ console.error(
276
+ "Error: Could not locate constants/site.ts. Make sure you are inside an initialized tempjs template directory."
277
+ );
278
+ return;
279
+ }
280
+
281
+ let siteContent = await readFile(siteTsPath, "utf8");
282
+ const current = parseCurrentBrandValues(siteContent);
283
+ const collected = { ...options };
284
+
285
+ const rl = createInterface({ input, output });
286
+ try {
287
+ console.log("\n--- Configure Brand & Contact Details ---");
288
+
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
+ }
101
298
  }
102
299
 
300
+ const values = buildBrandValues(current, collected);
301
+ siteContent = applyBrandToSiteContent(siteContent, values);
103
302
  await writeFile(siteTsPath, siteContent, "utf8");
104
303
  console.log("Successfully updated brand and contact configurations in constants/site.ts.");
105
304
  } finally {
package/cli/config.js CHANGED
@@ -5,7 +5,28 @@ import { fileURLToPath } from "node:url";
5
5
  const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
6
6
 
7
7
  /** @typedef {{ owner: string, repo: string, branch: string, templatesPath: string }} RepositoryConfig */
8
- /** @typedef {{ directory: string, name: string, description: string }} TemplateEntry */
8
+ /** @typedef {{
9
+ * directory: string,
10
+ * name: string,
11
+ * description: string,
12
+ * version?: string,
13
+ * stack?: string[],
14
+ * packageManager?: string,
15
+ * node?: string,
16
+ * setupTime?: string,
17
+ * docker?: boolean,
18
+ * tags?: string[],
19
+ * features?: 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
+ * }
29
+ * }} TemplateEntry */
9
30
  /** @typedef {{ repository: RepositoryConfig, templates: Record<string, TemplateEntry> }} Manifest */
10
31
 
11
32
  /**
package/cli/copy.js CHANGED
@@ -9,23 +9,10 @@ import {
9
9
  statSync,
10
10
  } from "node:fs";
11
11
  import { join } from "node:path";
12
+ import { NEVER_COPY_NAMES, shouldSkipFileName } from "./fs-ignore.js";
12
13
 
13
- const NEVER_COPY = new Set([".git", ".gitignore.bak"]);
14
14
  const NEVER_OVERWRITE = new Set([".git"]);
15
15
 
16
- /**
17
- * Files that must never be transferred into a generated project.
18
- * @param {string} name
19
- * @returns {boolean}
20
- */
21
- function shouldSkipFile(name) {
22
- if (NEVER_COPY.has(name)) return true;
23
- if (name === ".env" || name.startsWith(".env.")) {
24
- return name !== ".env.example";
25
- }
26
- return false;
27
- }
28
-
29
16
  /**
30
17
  * @param {string} dir
31
18
  * @returns {string[]}
@@ -75,7 +62,7 @@ function collectConflicts(sourceDir, targetDir, relative, conflicts) {
75
62
 
76
63
  const sourceNames = readdirSync(currentSource);
77
64
  for (const name of sourceNames) {
78
- if (shouldSkipFile(name)) continue;
65
+ if (shouldSkipFileName(name)) continue;
79
66
 
80
67
  const relPath = relative ? join(relative, name) : name;
81
68
  const sourcePath = join(sourceDir, relPath);
@@ -126,7 +113,7 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
126
113
  const names = readdirSync(sourcePath);
127
114
 
128
115
  for (const name of names) {
129
- if (shouldSkipFile(name)) continue;
116
+ if (shouldSkipFileName(name) || NEVER_COPY_NAMES.has(name)) continue;
130
117
 
131
118
  const relPath = relative ? join(relative, name) : name;
132
119
  const from = join(sourceRoot, relPath);
@@ -138,7 +125,6 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
138
125
 
139
126
  const stat = lstatSync(from);
140
127
  if (stat.isSymbolicLink()) {
141
- const linkTarget = readlinkSync(from);
142
128
  mkdirSync(join(to, ".."), { recursive: true });
143
129
  cpSync(from, to, { recursive: true, force: true });
144
130
  continue;
@@ -158,7 +144,7 @@ function copyRecursive(sourceRoot, targetRoot, relative) {
158
144
  * @param {string} dir
159
145
  */
160
146
  export function removeDirectory(dir) {
161
- if (existsSync(dir)) {
147
+ if (dir && existsSync(dir)) {
162
148
  rmSync(dir, { recursive: true, force: true });
163
149
  }
164
150
  }