@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
package/cli/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execSync } from "node:child_process";
4
- import { createInterface } from "node:readline/promises";
5
- import { stdin as input, stdout as output } from "node:process";
6
4
  import { loadManifest, getPackageRoot, resolveRepositoryConfig } from "./config.js";
7
5
  import {
8
6
  promptTheme,
@@ -12,56 +10,97 @@ import {
12
10
  } from "./theme-manager.js";
13
11
  import { promptAndApplyBrand } from "./brand-manager.js";
14
12
  import { promptAndSetupDb } from "./db-setup.js";
13
+ import { printTemplateInfo } from "./info.js";
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,
18
23
  isDirectoryEmpty,
19
24
  listDirectoryEntries,
20
- removeDirectory,
21
25
  targetHasGitRepo,
22
26
  } from "./copy.js";
23
- import { fetchTemplateFromGitHub, resolveLocalTemplate } from "./fetch.js";
27
+ import { writeProjectStamp } from "./project-stamp.js";
28
+ import { printFetchComplete } from "./progress.js";
29
+ import { confirmYesNo } from "./prompt.js";
30
+ import { resolveTemplateSource } from "./template-resolver.js";
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";
24
39
 
25
40
  const HELP_TEXT = `
26
41
  tempjs — instantiate project templates from GitHub
27
42
 
28
43
  USAGE
29
44
  tempjs list
45
+ tempjs info <template-id>
30
46
  tempjs <template-id> [options]
31
- tempjs <template-id> config Initialize with interactive theme, typography, brand & database setup
32
- tempjs theme Change the project's theme in an initialized directory
33
- tempjs font Change the project's font styling in an initialized directory
34
- tempjs brand Configure the brand & contact info in an initialized directory
35
- tempjs init-db Configure .env and sync database schema in an initialized directory
47
+ tempjs <template-id> config Interactive theme, typography, brand & database setup
48
+ tempjs update [--check | --merge]
49
+ tempjs theme Change theme in an initialized project
50
+ tempjs font Change font styling in an initialized project
51
+ tempjs brand Configure brand & contact info
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>]
36
58
  tempjs --help
37
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
+
66
+ UPDATE
67
+ tempjs update --check Show diff vs latest template (read-only)
68
+ tempjs update --merge Apply non-conflicting template updates only
69
+ tempjs update --merge --yes Apply without confirmation prompt
70
+
38
71
  OPTIONS
39
- --config Prompt for full interactive setup (theme, font, brand, db) during initialization
40
- --force Overwrite existing files in the current directory
41
- --remote Fetch from GitHub even if a local template copy exists
42
- --init-git Run git init after copying the template
43
- --help Show this help message
72
+ --config Run full setup (theme, font, brand, database) after copying
73
+ --yes, -y Skip all prompts
74
+ --no-prompt Same as --yes
75
+ --force, -f Overwrite existing files without prompting
76
+ --remote Fetch from GitHub even if a local template copy exists
77
+ --init-git Run git init after copying the template
78
+
79
+ (See tempjs --help for theme, brand, and database flags.)
44
80
 
45
81
  EXAMPLES
46
82
  mkdir hotel-client && cd hotel-client
47
83
  tempjs hotel config
48
- tempjs brand
49
- tempjs init-db
84
+
85
+ tempjs update --check
86
+ tempjs update --merge --yes
87
+
88
+ tempjs doctor
89
+
90
+ tempjs add-module seo,gallery,reviews
91
+ tempjs add-module list
50
92
 
51
93
  ENVIRONMENT
52
- TEMPLATES_REPO_URL GitHub repo URL or owner/repo (overrides templates.json)
94
+ TEMPLATES_REPO_URL GitHub repo URL or owner/repo
53
95
  TEMPLATES_REPO_OWNER GitHub owner/username
54
96
  TEMPLATES_REPO_REPO GitHub repository name
55
97
  TEMPLATES_REPO_BRANCH Branch name (default: main)
56
98
  TEMPLATE_USE_REMOTE=1 Always fetch from GitHub
57
- GITHUB_TOKEN / GH_TOKEN Optional — required only for private repositories
58
-
59
- CONFIGURATION
60
- Repository and template mappings live in templates.json at the package root.
99
+ GITHUB_TOKEN / GH_TOKEN Optional — private repositories only
61
100
  `;
62
101
 
63
102
  /**
64
- * @param {Record<string, { name: string, description: string }>} templates
103
+ * @param {Record<string, import('./config.js').TemplateEntry>} templates
65
104
  */
66
105
  function printTemplateList(templates) {
67
106
  console.log("Available templates:\n");
@@ -70,41 +109,20 @@ function printTemplateList(templates) {
70
109
 
71
110
  for (const id of ids) {
72
111
  const entry = templates[id];
73
- console.log(`${id.padEnd(idWidth + 2)}${entry.name}`);
112
+ const version = entry.version ? ` v${entry.version}` : "";
113
+ console.log(`${id.padEnd(idWidth + 2)}${entry.name}${version}`);
74
114
  if (entry.description) {
75
115
  console.log(`${"".padEnd(idWidth + 2)}${entry.description}`);
76
116
  }
77
- }
78
- console.log("");
79
- }
80
-
81
- /**
82
- * @param {string[]} argv
83
- */
84
- function parseArgs(argv) {
85
- const flags = {
86
- force: false,
87
- remote: false,
88
- initGit: false,
89
- help: false,
90
- config: false,
91
- };
92
- const positionals = [];
93
-
94
- for (const arg of argv) {
95
- if (arg === "--force") flags.force = true;
96
- else if (arg === "--remote") flags.remote = true;
97
- else if (arg === "--init-git") flags.initGit = true;
98
- else if (arg === "--help" || arg === "-h") flags.help = true;
99
- else if (arg === "--config") flags.config = true;
100
- else if (arg.startsWith("-")) {
101
- throw new Error(`Unknown option: ${arg}`);
102
- } else {
103
- positionals.push(arg);
117
+ if (entry.tags?.length) {
118
+ console.log(`${"".padEnd(idWidth + 2)}[${entry.tags.join(", ")}]`);
119
+ }
120
+ if (entry.stack?.length) {
121
+ console.log(`${"".padEnd(idWidth + 2)}${entry.stack.slice(0, 4).join(" · ")}`);
104
122
  }
105
123
  }
106
-
107
- return { flags, positionals };
124
+ console.log("");
125
+ console.log("Run `tempjs info <template-id>` for full details.\n");
108
126
  }
109
127
 
110
128
  /**
@@ -123,24 +141,10 @@ function printConflictWarning(conflicts) {
123
141
  console.log("");
124
142
  }
125
143
 
126
- /**
127
- * @param {boolean} force
128
- */
129
- async function confirmOverwrite(force) {
130
- if (force) return true;
131
- const rl = createInterface({ input, output });
132
- try {
133
- const answer = await rl.question("Continue? [y/N] ");
134
- return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
135
- } finally {
136
- rl.close();
137
- }
138
- }
139
-
140
144
  /**
141
145
  * @param {string} targetDir
142
146
  * @param {string} templateId
143
- * @param {{ force: boolean, remote: boolean, initGit: boolean }} flags
147
+ * @param {import('./parse-args.js').CliFlags} flags
144
148
  * @param {boolean} runWithConfig
145
149
  */
146
150
  async function runTemplate(targetDir, templateId, flags, runWithConfig = false) {
@@ -156,30 +160,22 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
156
160
 
157
161
  const repo = resolveRepositoryConfig(manifest.repository);
158
162
  const packageRoot = getPackageRoot();
163
+ const cliOptions = toCliOptions(flags);
159
164
  const useRemote =
160
165
  flags.remote ||
161
166
  process.env.TEMPLATE_USE_REMOTE === "1" ||
162
167
  process.env.TEMPLATE_USE_REMOTE === "true";
163
168
 
164
- let sourceDir = null;
165
- let cleanupDir = null;
169
+ let resolved = null;
166
170
 
167
171
  try {
168
- if (!useRemote) {
169
- sourceDir = await resolveLocalTemplate(
170
- packageRoot,
171
- repo.templatesPath,
172
- entry.directory
173
- );
174
- }
175
-
176
- if (!sourceDir) {
177
- console.log(`Fetching template "${templateId}" from ${repo.owner}/${repo.repo}...`);
178
- sourceDir = await fetchTemplateFromGitHub(repo, entry.directory);
179
- cleanupDir = sourceDir.replace(/[/\\]template$/, "");
180
- } else {
181
- console.log(`Using local template "${templateId}"...`);
182
- }
172
+ resolved = await resolveTemplateSource({
173
+ repo,
174
+ packageRoot,
175
+ templateDirectory: entry.directory,
176
+ useRemote,
177
+ });
178
+ printFetchComplete({ templateId, stats: resolved.stats });
183
179
 
184
180
  if (targetHasGitRepo(targetDir)) {
185
181
  console.warn(
@@ -189,19 +185,20 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
189
185
 
190
186
  const entries = listDirectoryEntries(targetDir);
191
187
  const hasContent = entries.length > 0;
188
+ const autoConfirm = flags.force || flags.yes;
192
189
 
193
190
  if (hasContent && !flags.force) {
194
- const conflicts = findConflictingPaths(sourceDir, targetDir);
191
+ const conflicts = findConflictingPaths(resolved.templateRoot, targetDir);
195
192
  if (conflicts.length > 0) {
196
193
  printConflictWarning(conflicts);
197
- const confirmed = await confirmOverwrite(false);
194
+ const confirmed = await confirmYesNo("Continue? [y/N] ", autoConfirm);
198
195
  if (!confirmed) {
199
196
  console.log("Aborted.");
200
197
  return;
201
198
  }
202
199
  } else if (!isDirectoryEmpty(targetDir)) {
203
200
  console.log("Current directory is not empty, but no files would be overwritten.");
204
- const confirmed = await confirmOverwrite(false);
201
+ const confirmed = await confirmYesNo("Continue? [y/N] ", autoConfirm);
205
202
  if (!confirmed) {
206
203
  console.log("Aborted.");
207
204
  return;
@@ -210,32 +207,50 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
210
207
  }
211
208
 
212
209
  const allowOverwrite = flags.force || hasContent;
213
- copyTemplate(sourceDir, targetDir, { force: allowOverwrite });
210
+ copyTemplate(resolved.templateRoot, targetDir, { force: allowOverwrite });
211
+
212
+ await writeProjectStamp(targetDir, {
213
+ templateId,
214
+ templateVersion: entry.version ?? "0.0.0",
215
+ templateDirectory: entry.directory,
216
+ repository: `${repo.owner}/${repo.repo}`,
217
+ branch: repo.branch,
218
+ sourceDir: resolved.templateRoot,
219
+ isUpdate: false,
220
+ });
214
221
 
215
222
  if (flags.initGit && !targetHasGitRepo(targetDir)) {
216
223
  execSync("git init", { cwd: targetDir, stdio: "inherit" });
217
224
  }
218
225
 
219
226
  if (runWithConfig) {
227
+ const initConfig = resolveTemplateInitConfig(entry);
220
228
  console.log("\nConfiguring project theme and typography...");
221
- const selectedTheme = await promptTheme("theme1");
222
- const selectedFont = await promptFont("default");
229
+ const selectedTheme = await promptTheme("theme1", cliOptions);
230
+ const selectedFont = await promptFont("default", cliOptions);
223
231
  await applyThemeAndFont(targetDir, selectedTheme, selectedFont);
224
232
 
225
- // Interactive Brand & Contact Details configuration
226
- await promptAndApplyBrand(targetDir);
233
+ if (!initConfig.skipBrand) {
234
+ const brandOpts = pickBrandOptionsForTemplate(templateId, entry, cliOptions);
235
+ await promptAndApplyBrand(targetDir, brandOpts, templateId);
236
+ }
227
237
 
228
- // Environment & Database auto-setup
229
- await promptAndSetupDb(targetDir);
238
+ if (!initConfig.skipDatabase) {
239
+ await promptAndSetupDb(targetDir, cliOptions);
240
+ }
230
241
  }
231
242
 
232
243
  console.log(`\nTemplate "${entry.name}" created successfully in ${targetDir}`);
233
- console.log("\nNext steps:");
234
- console.log(" pnpm install # or npm install");
235
- console.log(" pnpm dev # start development server");
236
- if (!flags.initGit && !targetHasGitRepo(targetDir)) {
237
- console.log(" git init # optional: initialize a new repository");
238
- }
244
+ console.log(`Stamped .tempjs.json (template v${entry.version ?? "0.0.0"})`);
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");
239
254
  } catch (error) {
240
255
  if (error instanceof Error && error.message === "TARGET_NOT_EMPTY") {
241
256
  printConflictWarning(error.conflicts ?? []);
@@ -247,8 +262,8 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
247
262
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
248
263
  process.exitCode = 1;
249
264
  } finally {
250
- if (cleanupDir) {
251
- removeDirectory(cleanupDir);
265
+ if (resolved) {
266
+ resolved.release();
252
267
  }
253
268
  }
254
269
  }
@@ -258,33 +273,180 @@ async function runTemplate(targetDir, templateId, flags, runWithConfig = false)
258
273
  */
259
274
  async function main(argv) {
260
275
  const { flags, positionals } = parseArgs(argv);
261
- let command = positionals[0];
276
+ const command = positionals[0];
262
277
 
263
278
  if (flags.help || command === "help" || (!command && argv.length === 0)) {
264
279
  console.log(HELP_TEXT.trim());
265
280
  return;
266
281
  }
267
282
 
268
- if (command === "theme" || command === "font" || command === "brand" || command === "init-db") {
283
+ const manifest = loadManifest();
284
+ const repo = resolveRepositoryConfig(manifest.repository);
285
+ const cliOptions = toCliOptions(flags);
286
+
287
+ if (command === "list") {
288
+ printTemplateList(manifest.templates);
289
+ return;
290
+ }
291
+
292
+ if (command === "info") {
293
+ const templateId = positionals[1];
294
+ if (!templateId) {
295
+ console.error("Usage: tempjs info <template-id>");
296
+ console.error("Run `tempjs list` to see available templates.");
297
+ process.exitCode = 1;
298
+ return;
299
+ }
300
+ const entry = manifest.templates[templateId];
301
+ if (!entry) {
302
+ console.error(`Unknown template: ${templateId}`);
303
+ console.error("Run `tempjs list` to see available templates.");
304
+ process.exitCode = 1;
305
+ return;
306
+ }
307
+ printTemplateInfo(templateId, entry, repo);
308
+ return;
309
+ }
310
+
311
+ if (command === "add-module") {
269
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
+
339
+ if (command === "update") {
340
+ const hasCheck = flags.updateCheck || argv.includes("--check");
341
+ const hasMerge = flags.updateMerge || argv.includes("--merge");
342
+
343
+ if (!hasCheck && !hasMerge) {
344
+ console.error("Usage: tempjs update --check | tempjs update --merge [--yes]");
345
+ process.exitCode = 1;
346
+ return;
347
+ }
348
+
349
+ const targetDir = process.cwd();
350
+ await runUpdate(targetDir, flags, { checkOnly: hasCheck && !hasMerge });
351
+ return;
352
+ }
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
+
410
+ if (
411
+ command === "theme" ||
412
+ command === "font" ||
413
+ command === "brand" ||
414
+ command === "init-db" ||
415
+ command === "doctor"
416
+ ) {
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
+
270
425
  const currentConfig = getSavedConfig(targetDir);
271
426
 
272
427
  if (command === "theme") {
273
- const selectedTheme = await promptTheme(currentConfig.theme || "theme1");
274
- await applyThemeAndFont(targetDir, selectedTheme, currentConfig.font || "default");
428
+ const selectedTheme = await promptTheme(currentConfig.theme || "theme1", cliOptions);
429
+ const selectedFont = await promptFont(currentConfig.font || "default", {
430
+ ...cliOptions,
431
+ yes: cliOptions.yes || Boolean(cliOptions.font),
432
+ });
433
+ await applyThemeAndFont(targetDir, selectedTheme, selectedFont);
275
434
  } else if (command === "font") {
276
- const selectedFont = await promptFont(currentConfig.font || "default");
277
- await applyThemeAndFont(targetDir, currentConfig.theme || "theme1", selectedFont);
435
+ const selectedFont = await promptFont(currentConfig.font || "default", cliOptions);
436
+ await applyThemeAndFont(
437
+ targetDir,
438
+ currentConfig.theme || "theme1",
439
+ selectedFont
440
+ );
278
441
  } else if (command === "brand") {
279
- await promptAndApplyBrand(targetDir);
442
+ const stamp = readProjectStamp(targetDir);
443
+ await promptAndApplyBrand(targetDir, cliOptions, stamp?.template);
280
444
  } else if (command === "init-db") {
281
- await promptAndSetupDb(targetDir);
445
+ await promptAndSetupDb(targetDir, cliOptions);
282
446
  }
283
447
  return;
284
448
  }
285
449
 
286
- const manifest = loadManifest();
287
-
288
450
  let runWithConfig = flags.config;
289
451
  if (positionals.length >= 2 && positionals[1] === "config") {
290
452
  runWithConfig = true;
package/cli/info.js ADDED
@@ -0,0 +1,115 @@
1
+ import { loadManifest, resolveRepositoryConfig } from "./config.js";
2
+ import { getTemplateBrandFields } from "./init-options.js";
3
+
4
+ /**
5
+ * @param {string} templateId
6
+ * @param {import('./config.js').TemplateEntry} entry
7
+ * @param {import('./config.js').RepositoryConfig} repo
8
+ */
9
+ export function printTemplateInfo(templateId, entry, repo) {
10
+ const lines = [];
11
+
12
+ lines.push(`${entry.name} (${templateId})`);
13
+ lines.push("=".repeat(Math.min(60, entry.name.length + templateId.length + 4)));
14
+ lines.push("");
15
+
16
+ if (entry.description) {
17
+ lines.push(entry.description);
18
+ lines.push("");
19
+ }
20
+
21
+ if (entry.version) {
22
+ lines.push(`Version: ${entry.version}`);
23
+ }
24
+
25
+ if (entry.stack?.length) {
26
+ lines.push(`Stack: ${entry.stack.join(", ")}`);
27
+ }
28
+
29
+ if (entry.node) {
30
+ lines.push(`Node.js: ${entry.node}`);
31
+ }
32
+
33
+ if (entry.packageManager) {
34
+ lines.push(`Package manager: ${entry.packageManager}`);
35
+ }
36
+
37
+ if (entry.setupTime) {
38
+ lines.push(`Typical setup: ${entry.setupTime}`);
39
+ }
40
+
41
+ if (entry.docker !== undefined) {
42
+ lines.push(`Docker support: ${entry.docker ? "Yes (docker-compose.yml)" : "No"}`);
43
+ }
44
+
45
+ if (entry.tags?.length) {
46
+ lines.push(`Tags: ${entry.tags.join(", ")}`);
47
+ }
48
+
49
+ lines.push(`Source directory: ${repo.templatesPath}/${entry.directory}`);
50
+ lines.push(`Repository: github.com/${repo.owner}/${repo.repo} (${repo.branch})`);
51
+
52
+ if (entry.features?.length) {
53
+ lines.push("");
54
+ lines.push("Features:");
55
+ for (const feature of entry.features) {
56
+ lines.push(` • ${feature}`);
57
+ }
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
+
73
+ lines.push("");
74
+ lines.push("Quick start:");
75
+ lines.push(` mkdir my-project && cd my-project`);
76
+ lines.push(` tempjs ${templateId} config`);
77
+ lines.push(` pnpm install && pnpm dev`);
78
+ lines.push("");
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(" ");
85
+ lines.push(
86
+ ` tempjs ${templateId} --config --yes --theme theme1 ${flagPreview} --db-host localhost`
87
+ );
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
+
97
+ if (entry.docs) {
98
+ lines.push("");
99
+ lines.push(`Docs in generated project: ${entry.docs}`);
100
+ }
101
+
102
+ console.log(lines.join("\n"));
103
+ }
104
+
105
+ /**
106
+ * @param {Record<string, import('./config.js').TemplateEntry>} templates
107
+ * @param {import('./config.js').RepositoryConfig} repo
108
+ */
109
+ export function printAllTemplatesInfo(templates, repo) {
110
+ const ids = Object.keys(templates).sort();
111
+ for (let i = 0; i < ids.length; i++) {
112
+ if (i > 0) console.log("\n");
113
+ printTemplateInfo(ids[i], templates[ids[i]], repo);
114
+ }
115
+ }