@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/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
|
-
|
|
205
|
-
|
|
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
|
|
212
|
-
console.log("
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
|
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,
|