@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.
Files changed (90) hide show
  1. package/README.md +66 -18
  2. package/cli/brand-manager.js +183 -96
  3. package/cli/config.js +9 -1
  4. package/cli/doctor.js +393 -0
  5. package/cli/fetch-core-modules.js +130 -0
  6. package/cli/index.js +141 -10
  7. package/cli/info.js +28 -1
  8. package/cli/init-options.js +209 -0
  9. package/cli/module-manager.js +168 -0
  10. package/cli/parse-args.js +21 -0
  11. package/cli/update.js +15 -0
  12. package/cli/version-manager.js +518 -0
  13. package/package.json +27 -4
  14. package/packages/core/modules/README.md +72 -0
  15. package/packages/core/modules/blog-compose/app/admin/components/blog-compose-form.ts +44 -0
  16. package/packages/core/modules/blog-compose/app/admin/hooks/useBlogPosts.ts +103 -0
  17. package/packages/core/modules/blog-compose/app/admin/panels/BlogComposePanel.tsx +388 -0
  18. package/packages/core/modules/blog-compose/app/api/blog-posts/[id]/route.ts +29 -0
  19. package/packages/core/modules/blog-compose/app/api/blog-posts/route.ts +21 -0
  20. package/packages/core/modules/blog-compose/app/blog/[slug]/page.tsx +79 -0
  21. package/packages/core/modules/blog-compose/app/blog/layout.tsx +22 -0
  22. package/packages/core/modules/blog-compose/app/blog/page.tsx +102 -0
  23. package/packages/core/modules/blog-compose/app/components/BlogSection.tsx +74 -0
  24. package/packages/core/modules/blog-compose/app/components/BlogShell.tsx +36 -0
  25. package/packages/core/modules/blog-compose/app/components/BlogSidebar.tsx +112 -0
  26. package/packages/core/modules/blog-compose/lib/blog/compose/annotate-text.tsx +103 -0
  27. package/packages/core/modules/blog-compose/lib/blog/compose/blocks/media-blocks.tsx +97 -0
  28. package/packages/core/modules/blog-compose/lib/blog/compose/blocks/text-blocks.tsx +115 -0
  29. package/packages/core/modules/blog-compose/lib/blog/compose/index.ts +21 -0
  30. package/packages/core/modules/blog-compose/lib/blog/compose/interpreter.tsx +55 -0
  31. package/packages/core/modules/blog-compose/lib/blog/compose/registry.ts +276 -0
  32. package/packages/core/modules/blog-compose/lib/blog/compose/types.ts +128 -0
  33. package/packages/core/modules/blog-compose/lib/blog/register-sitemap.ts +18 -0
  34. package/packages/core/modules/blog-compose/lib/controllers/BlogComposeController.ts +2 -0
  35. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.controller.ts +78 -0
  36. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.repository.ts +78 -0
  37. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.service.ts +72 -0
  38. package/packages/core/modules/blog-compose/lib/features/blog-compose/blog-compose.types.ts +20 -0
  39. package/packages/core/modules/blog-compose/lib/features/blog-compose/index.ts +7 -0
  40. package/packages/core/modules/blog-compose/prisma/blog-compose.prisma +13 -0
  41. package/packages/core/modules/enquiry-modal/app/components/EnquiryModal.tsx +308 -0
  42. package/packages/core/modules/footer/app/components/Footer.tsx +127 -0
  43. package/packages/core/modules/gallery/app/admin/components/GalleryFormModal.tsx +132 -0
  44. package/packages/core/modules/gallery/app/admin/components/GalleryList.tsx +142 -0
  45. package/packages/core/modules/gallery/app/admin/hooks/useGallery.ts +72 -0
  46. package/packages/core/modules/gallery/app/api/gallery/[id]/route.ts +29 -0
  47. package/packages/core/modules/gallery/app/api/gallery/route.ts +22 -0
  48. package/packages/core/modules/gallery/app/components/GallerySection.tsx +92 -0
  49. package/packages/core/modules/gallery/app/gallery/layout.tsx +12 -0
  50. package/packages/core/modules/gallery/app/gallery/page.tsx +179 -0
  51. package/packages/core/modules/gallery/lib/controllers/GalleryController.ts +2 -0
  52. package/packages/core/modules/gallery/lib/features/gallery/gallery.controller.ts +78 -0
  53. package/packages/core/modules/gallery/lib/features/gallery/gallery.repository.ts +55 -0
  54. package/packages/core/modules/gallery/lib/features/gallery/gallery.service.ts +43 -0
  55. package/packages/core/modules/gallery/lib/features/gallery/gallery.types.ts +17 -0
  56. package/packages/core/modules/gallery/lib/features/gallery/index.ts +4 -0
  57. package/packages/core/modules/gallery/prisma/gallery.prisma +11 -0
  58. package/packages/core/modules/hero-simple/app/components/Hero.tsx +160 -0
  59. package/packages/core/modules/legal-pages/app/components/LegalDocumentPage.tsx +61 -0
  60. package/packages/core/modules/legal-pages/app/privacy-policy/page.tsx +19 -0
  61. package/packages/core/modules/legal-pages/app/terms-and-conditions/page.tsx +19 -0
  62. package/packages/core/modules/reviews/app/admin/components/ReviewFormModal.tsx +110 -0
  63. package/packages/core/modules/reviews/app/admin/components/ReviewsList.tsx +116 -0
  64. package/packages/core/modules/reviews/app/admin/hooks/useReviews.ts +70 -0
  65. package/packages/core/modules/reviews/app/api/reviews/[id]/route.ts +29 -0
  66. package/packages/core/modules/reviews/app/api/reviews/route.ts +17 -0
  67. package/packages/core/modules/reviews/app/components/ReviewsSection.tsx +100 -0
  68. package/packages/core/modules/reviews/lib/controllers/ReviewController.ts +2 -0
  69. package/packages/core/modules/reviews/lib/features/reviews/index.ts +3 -0
  70. package/packages/core/modules/reviews/lib/features/reviews/review.controller.ts +64 -0
  71. package/packages/core/modules/reviews/lib/features/reviews/review.repository.ts +50 -0
  72. package/packages/core/modules/reviews/lib/features/reviews/review.service.ts +38 -0
  73. package/packages/core/modules/reviews/prisma/reviews.prisma +9 -0
  74. package/packages/core/modules/seo/app/components/SiteJsonLd.tsx +16 -0
  75. package/packages/core/modules/seo/app/robots.ts +30 -0
  76. package/packages/core/modules/seo/app/sitemap.ts +9 -0
  77. package/packages/core/modules/seo/lib/seo/index.ts +5 -0
  78. package/packages/core/modules/seo/lib/seo/json-ld.ts +128 -0
  79. package/packages/core/modules/seo/lib/seo/metadata.ts +65 -0
  80. package/packages/core/modules/seo/lib/seo/sitemap.ts +82 -0
  81. package/packages/core/modules/seo/lib/seo/types.ts +30 -0
  82. package/packages/core/modules/seo/lib/seo/urls.ts +27 -0
  83. package/packages/core/modules/theme-modes/app/components/ThemeModeInit.tsx +24 -0
  84. package/packages/core/modules/theme-modes/app/components/ThemeModeToggle.tsx +58 -0
  85. package/packages/core/modules.json +140 -0
  86. package/packages/core/prisma/schema.prisma +33 -0
  87. package/scripts/module-installer-core.mjs +657 -0
  88. package/scripts/template-modules-admin.mjs +366 -0
  89. package/scripts/template-modules-home.mjs +120 -0
  90. package/templates.json +163 -8
package/cli/index.js CHANGED
@@ -12,6 +12,11 @@ import { promptAndApplyBrand } from "./brand-manager.js";
12
12
  import { promptAndSetupDb } from "./db-setup.js";
13
13
  import { printTemplateInfo } from "./info.js";
14
14
  import { parseArgs, toCliOptions } from "./parse-args.js";
15
+ import {
16
+ pickBrandOptionsForTemplate,
17
+ resolveTemplateInitConfig,
18
+ } from "./init-options.js";
19
+ import { readProjectStamp } from "./project-stamp.js";
15
20
  import {
16
21
  copyTemplate,
17
22
  findConflictingPaths,
@@ -24,6 +29,13 @@ import { printFetchComplete } from "./progress.js";
24
29
  import { confirmYesNo } from "./prompt.js";
25
30
  import { resolveTemplateSource } from "./template-resolver.js";
26
31
  import { runUpdate } from "./update.js";
32
+ import { runDoctor } from "./doctor.js";
33
+ import {
34
+ resolveMonorepoRoot,
35
+ runVersionCheck,
36
+ runVersionInc,
37
+ } from "./version-manager.js";
38
+ import { runAddModule, printAvailableModules } from "./module-manager.js";
27
39
 
28
40
  const HELP_TEXT = `
29
41
  tempjs — instantiate project templates from GitHub
@@ -38,8 +50,19 @@ USAGE
38
50
  tempjs font Change font styling in an initialized project
39
51
  tempjs brand Configure brand & contact info
40
52
  tempjs init-db Configure .env and sync database schema
53
+ tempjs doctor Check if this project can run (env, DB, deps)
54
+ tempjs add-module <ids> Add optional core modules (seo, gallery, reviews, …)
55
+ tempjs add-module list List available core modules
56
+ tempjs version check [cli|all|<template-id>]
57
+ tempjs version inc <patch|minor|major> [cli|all|<template-id>]
41
58
  tempjs --help
42
59
 
60
+ VERSION (maintainers — run from monorepo root)
61
+ tempjs version check Compare git changes since last version bump
62
+ tempjs version check hotel Check one template only
63
+ tempjs version inc patch cli Bump @navneet_25/tempjs in package.json
64
+ tempjs version inc minor hotel Bump template in templates.json + CHANGELOG stub
65
+
43
66
  UPDATE
44
67
  tempjs update --check Show diff vs latest template (read-only)
45
68
  tempjs update --merge Apply non-conflicting template updates only
@@ -62,6 +85,11 @@ EXAMPLES
62
85
  tempjs update --check
63
86
  tempjs update --merge --yes
64
87
 
88
+ tempjs doctor
89
+
90
+ tempjs add-module seo,gallery,reviews
91
+ tempjs add-module list
92
+
65
93
  ENVIRONMENT
66
94
  TEMPLATES_REPO_URL GitHub repo URL or owner/repo
67
95
  TEMPLATES_REPO_OWNER GitHub owner/username
@@ -196,23 +224,33 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
196
224
  }
197
225
 
198
226
  if (runWithConfig) {
227
+ const initConfig = resolveTemplateInitConfig(entry);
199
228
  console.log("\nConfiguring project theme and typography...");
200
229
  const selectedTheme = await promptTheme("theme1", cliOptions);
201
230
  const selectedFont = await promptFont("default", cliOptions);
202
231
  await applyThemeAndFont(targetDir, selectedTheme, selectedFont);
203
232
 
204
- await promptAndApplyBrand(targetDir, cliOptions);
205
- await promptAndSetupDb(targetDir, cliOptions);
233
+ if (!initConfig.skipBrand) {
234
+ const brandOpts = pickBrandOptionsForTemplate(templateId, entry, cliOptions);
235
+ await promptAndApplyBrand(targetDir, brandOpts, templateId);
236
+ }
237
+
238
+ if (!initConfig.skipDatabase) {
239
+ await promptAndSetupDb(targetDir, cliOptions);
240
+ }
206
241
  }
207
242
 
208
243
  console.log(`\nTemplate "${entry.name}" created successfully in ${targetDir}`);
209
244
  console.log(`Stamped .tempjs.json (template v${entry.version ?? "0.0.0"})`);
210
- console.log("\nNext steps:");
211
- console.log(" pnpm install # or npm install");
212
- console.log(" pnpm dev # start development server");
213
- if (!flags.initGit && !targetHasGitRepo(targetDir)) {
214
- console.log(" git init # optional: initialize a new repository");
215
- }
245
+ console.log("\nNext steps — see GETTING_STARTED.md:");
246
+ console.log(" 1. pnpm install");
247
+ console.log(" 2. cp .env.example .env");
248
+ console.log(" 3. docker compose up -d # or: tempjs init-db");
249
+ console.log(" 4. pnpm prisma db push && pnpm prisma db seed");
250
+ console.log(" 5. pnpm dev");
251
+ console.log(" 6. Open /admin — login with ADMIN_USER / ADMIN_PASSWORD");
252
+ console.log("\n curl http://localhost:3000/api/health # verify DB + env");
253
+ console.log(" tempjs doctor # full readiness check");
216
254
  } catch (error) {
217
255
  if (error instanceof Error && error.message === "TARGET_NOT_EMPTY") {
218
256
  printConflictWarning(error.conflicts ?? []);
@@ -270,6 +308,34 @@ async function main(argv) {
270
308
  return;
271
309
  }
272
310
 
311
+ if (command === "add-module") {
312
+ const targetDir = process.cwd();
313
+ const sub = positionals[1];
314
+
315
+ if (sub === "list" || flags.list) {
316
+ console.log("Available core modules:\n");
317
+ await printAvailableModules(repo);
318
+ return;
319
+ }
320
+
321
+ const moduleArg = sub ?? positionals.slice(1).join(",");
322
+ const moduleIds = moduleArg
323
+ ? moduleArg.split(",").map((s) => s.trim()).filter(Boolean)
324
+ : [];
325
+
326
+ if (moduleIds.length === 0) {
327
+ console.error("Usage: tempjs add-module <module-id>[,<module-id>...]");
328
+ console.error(" tempjs add-module list");
329
+ process.exitCode = 1;
330
+ return;
331
+ }
332
+
333
+ await runAddModule(targetDir, moduleIds, repo, {
334
+ useRemote: flags.remote,
335
+ });
336
+ return;
337
+ }
338
+
273
339
  if (command === "update") {
274
340
  const hasCheck = flags.updateCheck || argv.includes("--check");
275
341
  const hasMerge = flags.updateMerge || argv.includes("--merge");
@@ -285,13 +351,77 @@ async function main(argv) {
285
351
  return;
286
352
  }
287
353
 
354
+ if (command === "version") {
355
+ const repoRoot = resolveMonorepoRoot(process.cwd());
356
+ if (!repoRoot) {
357
+ console.error("tempjs version commands must run from the templates monorepo root.");
358
+ console.error("(Directory containing templates.json and cli/)");
359
+ process.exitCode = 1;
360
+ return;
361
+ }
362
+
363
+ const sub = positionals[1];
364
+
365
+ if (sub === "check") {
366
+ runVersionCheck(repoRoot, { scope: positionals[2] });
367
+ return;
368
+ }
369
+
370
+ if (sub === "inc") {
371
+ const level = positionals[2];
372
+ const target = positionals[3] ?? "cli";
373
+
374
+ if (!level || !["patch", "minor", "major"].includes(level)) {
375
+ console.error("Usage: tempjs version inc <patch|minor|major> [cli|all|<template-id>]");
376
+ process.exitCode = 1;
377
+ return;
378
+ }
379
+
380
+ runVersionInc(repoRoot, level, target);
381
+ return;
382
+ }
383
+
384
+ console.log(`
385
+ tempjs version — release tracking for CLI and templates (maintainers)
386
+
387
+ USAGE
388
+ tempjs version check [cli|all|<template-id>]
389
+ tempjs version inc <patch|minor|major> [cli|all|<template-id>]
390
+
391
+ HOW IT WORKS
392
+ .tempjs-version.json stores the git commit recorded at each version bump.
393
+ "check" diffs tracked paths since that commit (+ uncommitted changes).
394
+
395
+ CLI tracks: cli/, package.json, templates.json
396
+ Templates track: packages/core/, template directory, sync script
397
+
398
+ EXAMPLES
399
+ tempjs version check
400
+ tempjs version check hotel
401
+ tempjs version inc patch cli
402
+ tempjs version inc minor hotel
403
+ tempjs version inc patch all
404
+
405
+ See VERSIONING.md in the repo for the full maintainer guide.
406
+ `.trim());
407
+ return;
408
+ }
409
+
288
410
  if (
289
411
  command === "theme" ||
290
412
  command === "font" ||
291
413
  command === "brand" ||
292
- command === "init-db"
414
+ command === "init-db" ||
415
+ command === "doctor"
293
416
  ) {
294
417
  const targetDir = process.cwd();
418
+
419
+ if (command === "doctor") {
420
+ const code = await runDoctor(targetDir);
421
+ if (code !== 0) process.exitCode = 1;
422
+ return;
423
+ }
424
+
295
425
  const currentConfig = getSavedConfig(targetDir);
296
426
 
297
427
  if (command === "theme") {
@@ -309,7 +439,8 @@ async function main(argv) {
309
439
  selectedFont
310
440
  );
311
441
  } else if (command === "brand") {
312
- await promptAndApplyBrand(targetDir, cliOptions);
442
+ const stamp = readProjectStamp(targetDir);
443
+ await promptAndApplyBrand(targetDir, cliOptions, stamp?.template);
313
444
  } else if (command === "init-db") {
314
445
  await promptAndSetupDb(targetDir, cliOptions);
315
446
  }
package/cli/info.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { loadManifest, resolveRepositoryConfig } from "./config.js";
2
+ import { getTemplateBrandFields } from "./init-options.js";
2
3
 
3
4
  /**
4
5
  * @param {string} templateId
@@ -56,6 +57,19 @@ export function printTemplateInfo(templateId, entry, repo) {
56
57
  }
57
58
  }
58
59
 
60
+ if (entry.coreModules?.length) {
61
+ lines.push("");
62
+ lines.push(`Core modules: ${entry.coreModules.join(", ")}`);
63
+ }
64
+
65
+ if (entry.optionalModuleExamples?.length) {
66
+ lines.push(`Optional examples: ${entry.optionalModuleExamples.join(", ")}`);
67
+ }
68
+
69
+ if (entry.templateModules?.length) {
70
+ lines.push(`Template modules: ${entry.templateModules.join(", ")}`);
71
+ }
72
+
59
73
  lines.push("");
60
74
  lines.push("Quick start:");
61
75
  lines.push(` mkdir my-project && cd my-project`);
@@ -63,10 +77,23 @@ export function printTemplateInfo(templateId, entry, repo) {
63
77
  lines.push(` pnpm install && pnpm dev`);
64
78
  lines.push("");
65
79
  lines.push("Non-interactive:");
80
+ const brandFields = getTemplateBrandFields(entry);
81
+ const flagPreview = brandFields
82
+ .slice(0, 4)
83
+ .map((f) => f.flag)
84
+ .join(" ");
66
85
  lines.push(
67
- ` tempjs ${templateId} --config --yes --theme theme1 --name "My Brand" --db-host localhost`
86
+ ` tempjs ${templateId} --config --yes --theme theme1 ${flagPreview} --db-host localhost`
68
87
  );
69
88
 
89
+ if (entry.init?.brandFields?.length) {
90
+ lines.push("");
91
+ lines.push("Init options for this template:");
92
+ for (const field of brandFields) {
93
+ lines.push(` ${field.flag.padEnd(22)} ${field.description}`);
94
+ }
95
+ }
96
+
70
97
  if (entry.docs) {
71
98
  lines.push("");
72
99
  lines.push(`Docs in generated project: ${entry.docs}`);
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Per-template init field registry and flag resolution.
3
+ * Maintainers: add fields here once, then reference keys in templates.json `init.brandFields`.
4
+ */
5
+
6
+ /** @typedef {{
7
+ * flag: string,
8
+ * cliKey: string,
9
+ * prompt: string,
10
+ * description: string,
11
+ * siteKey?: string,
12
+ * example?: string
13
+ * }} InitFieldDef */
14
+
15
+ /** @typedef {{
16
+ * flagGroups?: string[],
17
+ * brandFields?: string[],
18
+ * skipBrand?: boolean,
19
+ * skipDatabase?: boolean
20
+ * }} TemplateInitConfig */
21
+
22
+ export const DEFAULT_FLAG_GROUPS = ["generate", "theme", "brand", "database"];
23
+
24
+ export const DEFAULT_BRAND_FIELDS = [
25
+ "name",
26
+ "shortName",
27
+ "baseUrl",
28
+ "phone",
29
+ "phoneDisplay",
30
+ "countryCode",
31
+ "email",
32
+ "address",
33
+ ];
34
+
35
+ /**
36
+ * All supported brand/init fields. Keys match templates.json `init.brandFields` entries.
37
+ * @type {Record<string, InitFieldDef>}
38
+ */
39
+ export const INIT_FIELD_REGISTRY = {
40
+ name: {
41
+ flag: "--name",
42
+ cliKey: "name",
43
+ prompt: "Brand Name",
44
+ description: "Primary business name shown across the site",
45
+ siteKey: "brandName",
46
+ example: "--name \"Lakeside Haven Resort\"",
47
+ },
48
+ shortName: {
49
+ flag: "--short-name",
50
+ cliKey: "shortName",
51
+ prompt: "Short/Display Name",
52
+ description: "Compact label for headers and compact UI",
53
+ example: "--short-name \"Lakeside Haven\"",
54
+ },
55
+ baseUrl: {
56
+ flag: "--base-url",
57
+ cliKey: "baseUrl",
58
+ prompt: "Base URL",
59
+ description: "Production site URL (SEO, sitemap, canonical links)",
60
+ example: "--base-url https://lakesidehaven.com",
61
+ },
62
+ phone: {
63
+ flag: "--phone",
64
+ cliKey: "phone",
65
+ prompt: "Contact Phone Number",
66
+ description: "Raw phone digits without spaces",
67
+ example: "--phone 9876543210",
68
+ },
69
+ phoneDisplay: {
70
+ flag: "--phone-display",
71
+ cliKey: "phoneDisplay",
72
+ prompt: "Display Phone Number",
73
+ description: "Formatted phone for display in UI",
74
+ example: "--phone-display \"+91 98765 43210\"",
75
+ },
76
+ countryCode: {
77
+ flag: "--country-code",
78
+ cliKey: "countryCode",
79
+ prompt: "Country Code",
80
+ description: "Phone country code (no plus sign)",
81
+ example: "--country-code 91",
82
+ },
83
+ email: {
84
+ flag: "--email",
85
+ cliKey: "email",
86
+ prompt: "Contact Email",
87
+ description: "Public contact email",
88
+ example: "--email reservations@example.com",
89
+ },
90
+ address: {
91
+ flag: "--address",
92
+ cliKey: "address",
93
+ prompt: "Full Address",
94
+ description: "Full address string (locality and region are derived for structured fields)",
95
+ example: "--address \"Lonavala, Maharashtra, India\"",
96
+ },
97
+ tagline: {
98
+ flag: "--tagline",
99
+ cliKey: "tagline",
100
+ prompt: "Tagline",
101
+ description: "Short brand tagline under the logo",
102
+ example: "--tagline \"Where Nature Meets Refined Comfort\"",
103
+ },
104
+ developerName: {
105
+ flag: "--developer-name",
106
+ cliKey: "developerName",
107
+ prompt: "Developer / Company Name",
108
+ description: "Legal or developer entity name (real estate) or resort operator name",
109
+ example: "--developer-name \"Greenfield Properties\"",
110
+ },
111
+ channelPartner: {
112
+ flag: "--channel-partner",
113
+ cliKey: "channelPartner",
114
+ prompt: "Channel Partner Name",
115
+ description: "Sales or hospitality partner label used in consent text",
116
+ example: "--channel-partner \"Greenfield Realty Partners\"",
117
+ },
118
+ agentRera: {
119
+ flag: "--agent-rera",
120
+ cliKey: "agentRera",
121
+ prompt: "Agent RERA Number",
122
+ description: "RERA registration for real-estate agent/consent (legal.agentRera)",
123
+ example: "--agent-rera A52100012345",
124
+ },
125
+ templeDistance: {
126
+ flag: "--temple-distance",
127
+ cliKey: "templeDistance",
128
+ prompt: "Nearby Landmark Distance",
129
+ description: "Hotel-specific nearby landmark line (contact.templeDistance)",
130
+ example: "--temple-distance \"5 min walk to Lonavala Lake viewpoint\"",
131
+ },
132
+ company: {
133
+ flag: "--company",
134
+ cliKey: "company",
135
+ prompt: "Company Name",
136
+ description: "Alias for developer/company name when templates use company branding",
137
+ siteKey: "developerName",
138
+ example: "--company \"Mi Plaza Hospitality\"",
139
+ },
140
+ location: {
141
+ flag: "--location",
142
+ cliKey: "location",
143
+ prompt: "Primary Location",
144
+ description: "City or locality label (updates address locality when full address is not set)",
145
+ example: "--location \"Lonavala\"",
146
+ },
147
+ };
148
+
149
+ /**
150
+ * @param {import('./config.js').TemplateEntry} entry
151
+ * @returns {TemplateInitConfig}
152
+ */
153
+ export function resolveTemplateInitConfig(entry) {
154
+ const init = entry.init ?? {};
155
+ return {
156
+ flagGroups: init.flagGroups ?? DEFAULT_FLAG_GROUPS,
157
+ brandFields: init.brandFields ?? DEFAULT_BRAND_FIELDS,
158
+ skipBrand: init.skipBrand ?? false,
159
+ skipDatabase: init.skipDatabase ?? false,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * @param {import('./config.js').TemplateEntry} entry
165
+ * @returns {InitFieldDef[]}
166
+ */
167
+ export function getTemplateBrandFields(entry) {
168
+ const { brandFields } = resolveTemplateInitConfig(entry);
169
+ return brandFields
170
+ .map((key) => INIT_FIELD_REGISTRY[key])
171
+ .filter(Boolean);
172
+ }
173
+
174
+ /**
175
+ * @param {string} templateId
176
+ * @param {import('./config.js').TemplateEntry} entry
177
+ * @param {Record<string, string | undefined>} options
178
+ */
179
+ export function pickBrandOptionsForTemplate(templateId, entry, options) {
180
+ const { brandFields, skipBrand } = resolveTemplateInitConfig(entry);
181
+ if (skipBrand) return {};
182
+
183
+ const picked = { yes: options.yes };
184
+
185
+ for (const fieldKey of brandFields) {
186
+ const def = INIT_FIELD_REGISTRY[fieldKey];
187
+ if (!def) continue;
188
+
189
+ const value = options[def.cliKey];
190
+ if (value !== undefined && value !== "") {
191
+ picked[def.cliKey] = value;
192
+ }
193
+ }
194
+
195
+ // company flag maps to developerName in brand-manager
196
+ if (options.company && !picked.developerName) {
197
+ picked.developerName = options.company;
198
+ }
199
+
200
+ return picked;
201
+ }
202
+
203
+ /**
204
+ * @param {import('./config.js').TemplateEntry} entry
205
+ * @returns {string[]}
206
+ */
207
+ export function getTemplateFlagGroups(entry) {
208
+ return resolveTemplateInitConfig(entry).flagGroups;
209
+ }
@@ -0,0 +1,168 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { rm } from "node:fs/promises";
5
+
6
+ import { getPackageRoot } from "./config.js";
7
+ import { fetchCoreModulesFromGitHub, resolveLocalCoreDir } from "./fetch-core-modules.js";
8
+ import { readProjectStamp } from "./project-stamp.js";
9
+
10
+ const scriptsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
11
+
12
+ /**
13
+ * @param {string} projectDir
14
+ */
15
+ function assertInitializedProject(projectDir) {
16
+ const stamp = readProjectStamp(projectDir);
17
+ if (!stamp) {
18
+ throw new Error(
19
+ "This directory does not look like a tempjs project (missing .tempjs.json).\n" +
20
+ "Run tempjs <template-id> from an empty directory first."
21
+ );
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @param {string} coreDir
27
+ * @param {string} projectDir
28
+ */
29
+ function writeMergedPrismaSchema(coreDir, projectDir) {
30
+ const coreSchemaPath = join(coreDir, "prisma", "schema.prisma");
31
+ if (!existsSync(coreSchemaPath)) {
32
+ return;
33
+ }
34
+
35
+ let merged = readFileSync(coreSchemaPath, "utf8").trim();
36
+ const domainPath = join(projectDir, "prisma", "domain.prisma");
37
+
38
+ if (existsSync(domainPath)) {
39
+ const domainPart = readFileSync(domainPath, "utf8").trim();
40
+ if (domainPart) {
41
+ merged = `${merged}\n\n${domainPart}`;
42
+ }
43
+ }
44
+
45
+ const destSchema = join(projectDir, "prisma", "schema.prisma");
46
+ mkdirSync(dirname(destSchema), { recursive: true });
47
+ writeFileSync(destSchema, `${merged}\n`, "utf8");
48
+ console.log(" ✓ merged prisma/schema.prisma");
49
+ }
50
+
51
+ /**
52
+ * @param {import('./config.js').RepositoryConfig} repo
53
+ * @param {{ useRemote?: boolean }} [options]
54
+ * @returns {Promise<{ coreDir: string, cleanupDir: string }>}
55
+ */
56
+ async function resolveCoreDir(repo, options = {}) {
57
+ const packageRoot = getPackageRoot();
58
+ const localCore = resolveLocalCoreDir(packageRoot);
59
+
60
+ if (localCore && !options.useRemote) {
61
+ return { coreDir: localCore, cleanupDir: "" };
62
+ }
63
+
64
+ return fetchCoreModulesFromGitHub(repo);
65
+ }
66
+
67
+ /**
68
+ * Add optional core modules to an initialized client project.
69
+ * @param {string} projectDir
70
+ * @param {string[]} moduleIds
71
+ * @param {import('./config.js').RepositoryConfig} repo
72
+ * @param {{ useRemote?: boolean }} [options]
73
+ */
74
+ export async function runAddModule(projectDir, moduleIds, repo, options = {}) {
75
+ assertInitializedProject(projectDir);
76
+
77
+ if (moduleIds.length === 0) {
78
+ throw new Error("No module ids provided. Example: tempjs add-module seo,gallery");
79
+ }
80
+
81
+ const stamp = readProjectStamp(projectDir);
82
+ const displayName = stamp?.template
83
+ ? stamp.template.replace(/-website-template$/, "").replace(/-/g, " ")
84
+ : "Your Site";
85
+
86
+ const { coreDir, cleanupDir } = await resolveCoreDir(repo, {
87
+ useRemote: options.useRemote,
88
+ });
89
+
90
+ const installerPath = join(scriptsDir, "module-installer-core.mjs");
91
+ const { copyModulesIntoProject, listModuleIds, resolveModuleIds, parseModuleSpecs, moduleInstallOptionsFromSpecs } = await import(
92
+ pathToFileURL(installerPath).href
93
+ );
94
+
95
+ const specs = parseModuleSpecs(moduleIds.join(","));
96
+ const resolvedIds = specs.map((s) => s.id);
97
+ const available = listModuleIds(coreDir);
98
+ const resolved = resolveModuleIds(coreDir, resolvedIds);
99
+ const installOptions = moduleInstallOptionsFromSpecs(specs);
100
+
101
+ console.log(`Adding core modules to ${relative(process.cwd(), projectDir) || "."}:`);
102
+ console.log(` modules: ${resolved.join(", ")}`);
103
+
104
+ try {
105
+ const installed = copyModulesIntoProject(projectDir, resolved, {
106
+ coreDir,
107
+ displayName,
108
+ skipHomePage: true,
109
+ skipSeoMetadataReplace: true,
110
+ installOptions,
111
+ writePrismaSchema: () => writeMergedPrismaSchema(coreDir, projectDir),
112
+ });
113
+
114
+ const cmsModules = installed.filter((id) => id === "gallery" || id === "reviews");
115
+ if (cmsModules.length > 0) {
116
+ console.log(
117
+ ` Admin tabs: gallery/reviews wired on /admin via app/admin/registry.ts`
118
+ );
119
+ }
120
+
121
+ console.log(`\n✓ Installed: ${installed.join(", ")}`);
122
+ console.log("\nNext steps:");
123
+ console.log(" pnpm prisma db push");
124
+ console.log(" pnpm dev");
125
+ if (cmsModules.length > 0) {
126
+ console.log(" Open /admin — Gallery and Reviews tabs appear when SITE.features flags are true");
127
+ }
128
+ console.log(`\nAvailable modules: ${available.join(", ")}`);
129
+ } finally {
130
+ if (cleanupDir) {
131
+ await rm(cleanupDir, { recursive: true, force: true });
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * @param {import('./config.js').RepositoryConfig} repo
138
+ */
139
+ export async function printAvailableModules(repo) {
140
+ const packageRoot = getPackageRoot();
141
+ const localCore = resolveLocalCoreDir(packageRoot);
142
+
143
+ let coreDir = localCore;
144
+ let cleanupDir = "";
145
+
146
+ if (!coreDir) {
147
+ const fetched = await fetchCoreModulesFromGitHub(repo);
148
+ coreDir = fetched.coreDir;
149
+ cleanupDir = fetched.cleanupDir;
150
+ }
151
+
152
+ try {
153
+ const installerPath = join(scriptsDir, "module-installer-core.mjs");
154
+ const { listModuleIds, loadModulesRegistry } = await import(pathToFileURL(installerPath).href);
155
+ const registry = loadModulesRegistry(coreDir);
156
+ for (const id of listModuleIds(coreDir)) {
157
+ const mod = registry.modules[id];
158
+ console.log(` ${id.padEnd(16)} ${mod.label}`);
159
+ if (mod.description) {
160
+ console.log(` ${mod.description}`);
161
+ }
162
+ }
163
+ } finally {
164
+ if (cleanupDir) {
165
+ await rm(cleanupDir, { recursive: true, force: true });
166
+ }
167
+ }
168
+ }
package/cli/parse-args.js CHANGED
@@ -20,6 +20,13 @@ export function createDefaultFlags() {
20
20
  countryCode: undefined,
21
21
  email: undefined,
22
22
  address: undefined,
23
+ tagline: undefined,
24
+ developerName: undefined,
25
+ channelPartner: undefined,
26
+ agentRera: undefined,
27
+ templeDistance: undefined,
28
+ company: undefined,
29
+ location: undefined,
23
30
  dbHost: undefined,
24
31
  dbPort: undefined,
25
32
  dbUser: undefined,
@@ -59,6 +66,13 @@ const FLAG_MAP = {
59
66
  "--country-code": { key: "countryCode", type: "string" },
60
67
  "--email": { key: "email", type: "string" },
61
68
  "--address": { key: "address", type: "string" },
69
+ "--tagline": { key: "tagline", type: "string" },
70
+ "--developer-name": { key: "developerName", type: "string" },
71
+ "--channel-partner": { key: "channelPartner", type: "string" },
72
+ "--agent-rera": { key: "agentRera", type: "string" },
73
+ "--temple-distance": { key: "templeDistance", type: "string" },
74
+ "--company": { key: "company", type: "string" },
75
+ "--location": { key: "location", type: "string" },
62
76
  "--db-host": { key: "dbHost", type: "string" },
63
77
  "--db-port": { key: "dbPort", type: "string" },
64
78
  "--db-user": { key: "dbUser", type: "string" },
@@ -143,6 +157,13 @@ export function toCliOptions(flags) {
143
157
  countryCode: flags.countryCode,
144
158
  email: flags.email,
145
159
  address: flags.address,
160
+ tagline: flags.tagline,
161
+ developerName: flags.developerName,
162
+ channelPartner: flags.channelPartner,
163
+ agentRera: flags.agentRera,
164
+ templeDistance: flags.templeDistance,
165
+ company: flags.company,
166
+ location: flags.location,
146
167
  dbHost: flags.dbHost,
147
168
  dbPort: flags.dbPort,
148
169
  dbUser: flags.dbUser,