@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/update.js
CHANGED
|
@@ -62,6 +62,13 @@ export function printUpdateReport(diff, meta) {
|
|
|
62
62
|
console.log("");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
if (meta.projectVersion !== meta.latestVersion) {
|
|
66
|
+
console.log(
|
|
67
|
+
`Version ${meta.projectVersion} → ${meta.latestVersion}: see CHANGELOG.md for release notes.`
|
|
68
|
+
);
|
|
69
|
+
console.log("");
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
if (diff.removedFromTemplate.length > 0) {
|
|
66
73
|
console.log(`Removed from template (${diff.removedFromTemplate.length}) — not deleted locally:`);
|
|
67
74
|
diff.removedFromTemplate.slice(0, 15).forEach((p) => console.log(` - ${p}`));
|
|
@@ -147,7 +154,15 @@ export async function runUpdate(projectDir, flags, mode) {
|
|
|
147
154
|
|
|
148
155
|
if (mode.checkOnly) {
|
|
149
156
|
if (stamp.templateVersion !== latestVersion) {
|
|
157
|
+
const changelogPath = join(projectDir, "CHANGELOG.md");
|
|
150
158
|
console.log("Run `tempjs update --merge` to apply non-conflicting template updates.");
|
|
159
|
+
if (existsSync(changelogPath)) {
|
|
160
|
+
console.log(`See CHANGELOG.md for what changed in v${latestVersion}.`);
|
|
161
|
+
} else {
|
|
162
|
+
console.log(
|
|
163
|
+
`After updating, read CHANGELOG.md in the template for v${latestVersion} changes.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
151
166
|
}
|
|
152
167
|
return;
|
|
153
168
|
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { loadManifest, getPackageRoot } from "./config.js";
|
|
5
|
+
|
|
6
|
+
export const VERSION_STATE_FILE = ".tempjs-version.json";
|
|
7
|
+
|
|
8
|
+
const CLI_WATCH_PATHS = ["cli", "package.json", "templates.json"];
|
|
9
|
+
|
|
10
|
+
const CORE_PATH = "packages/core";
|
|
11
|
+
const SYNC_SCRIPT = "scripts/sync-templates.mjs";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{
|
|
15
|
+
* version: string,
|
|
16
|
+
* gitCommit: string,
|
|
17
|
+
* recordedAt: string
|
|
18
|
+
* }} VersionRecord
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{
|
|
23
|
+
* cli?: VersionRecord,
|
|
24
|
+
* core?: VersionRecord,
|
|
25
|
+
* templates?: Record<string, VersionRecord>
|
|
26
|
+
* }} VersionState
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} repoRoot
|
|
31
|
+
*/
|
|
32
|
+
export function isGitRepository(repoRoot) {
|
|
33
|
+
try {
|
|
34
|
+
execSync("git rev-parse --git-dir", { cwd: repoRoot, stdio: "pipe" });
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} repoRoot
|
|
43
|
+
*/
|
|
44
|
+
export function getHeadCommit(repoRoot) {
|
|
45
|
+
return execSync("git rev-parse HEAD", { cwd: repoRoot, encoding: "utf8" }).trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} repoRoot
|
|
50
|
+
* @returns {VersionState}
|
|
51
|
+
*/
|
|
52
|
+
export function readVersionState(repoRoot) {
|
|
53
|
+
const path = join(repoRoot, VERSION_STATE_FILE);
|
|
54
|
+
if (!existsSync(path)) return {};
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} repoRoot
|
|
64
|
+
* @param {VersionState} state
|
|
65
|
+
*/
|
|
66
|
+
export function writeVersionState(repoRoot, state) {
|
|
67
|
+
const path = join(repoRoot, VERSION_STATE_FILE);
|
|
68
|
+
writeFileSync(path, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} version
|
|
73
|
+
* @param {"patch"|"minor"|"major"} level
|
|
74
|
+
*/
|
|
75
|
+
export function bumpSemver(version, level) {
|
|
76
|
+
const parts = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
77
|
+
const major = parts[0] ?? 0;
|
|
78
|
+
const minor = parts[1] ?? 0;
|
|
79
|
+
const patch = parts[2] ?? 0;
|
|
80
|
+
|
|
81
|
+
if (level === "major") return `${major + 1}.0.0`;
|
|
82
|
+
if (level === "minor") return `${major}.${minor + 1}.0`;
|
|
83
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} repoRoot
|
|
88
|
+
* @param {string[]} paths
|
|
89
|
+
*/
|
|
90
|
+
function gitDiffFiles(repoRoot, sinceCommit, paths) {
|
|
91
|
+
if (!sinceCommit) return [];
|
|
92
|
+
const quoted = paths.map((p) => `"${p}"`).join(" ");
|
|
93
|
+
try {
|
|
94
|
+
const out = execSync(`git diff --name-only ${sinceCommit} HEAD -- ${quoted}`, {
|
|
95
|
+
cwd: repoRoot,
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
}).trim();
|
|
98
|
+
return out ? out.split("\n").filter(Boolean) : [];
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} repoRoot
|
|
106
|
+
* @param {string[]} paths
|
|
107
|
+
*/
|
|
108
|
+
function gitDirtyFiles(repoRoot, paths) {
|
|
109
|
+
const quoted = paths.map((p) => `"${p}"`).join(" ");
|
|
110
|
+
try {
|
|
111
|
+
const out = execSync(`git status --porcelain -- ${quoted}`, {
|
|
112
|
+
cwd: repoRoot,
|
|
113
|
+
encoding: "utf8",
|
|
114
|
+
}).trim();
|
|
115
|
+
if (!out) return [];
|
|
116
|
+
return out
|
|
117
|
+
.split("\n")
|
|
118
|
+
.map((line) => line.slice(3).trim())
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Paths that require sync before a template release (excludes merged output).
|
|
127
|
+
* @param {import('./config.js').TemplateEntry} entry
|
|
128
|
+
*/
|
|
129
|
+
export function getTemplateSourcePaths(entry) {
|
|
130
|
+
return [CORE_PATH, SYNC_SCRIPT];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} templateId
|
|
135
|
+
* @param {import('./config.js').TemplateEntry} entry
|
|
136
|
+
*/
|
|
137
|
+
export function getTemplateWatchPaths(templateId, entry) {
|
|
138
|
+
return [`templates/${entry.directory}`];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} repoRoot
|
|
143
|
+
* @returns {{ ok: boolean, output?: string }}
|
|
144
|
+
*/
|
|
145
|
+
export function runSyncTemplatesCheck(repoRoot) {
|
|
146
|
+
const scriptPath = join(repoRoot, SYNC_SCRIPT);
|
|
147
|
+
if (!existsSync(scriptPath)) {
|
|
148
|
+
return { ok: false, output: `Sync script not found: ${SYNC_SCRIPT}` };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const output = execSync(`node ${SYNC_SCRIPT} --check`, {
|
|
153
|
+
cwd: repoRoot,
|
|
154
|
+
encoding: "utf8",
|
|
155
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
156
|
+
});
|
|
157
|
+
return { ok: true, output };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const stdout = error.stdout?.toString?.() ?? "";
|
|
160
|
+
const stderr = error.stderr?.toString?.() ?? "";
|
|
161
|
+
return { ok: false, output: `${stdout}${stderr}`.trim() };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Verify templates match packages/core when core or sync script changed.
|
|
167
|
+
* @param {string} repoRoot
|
|
168
|
+
* @param {import('./config.js').TemplateEntry} entry
|
|
169
|
+
* @param {string | undefined} sinceCommit
|
|
170
|
+
* @returns {boolean} false if check failed (sets process.exitCode)
|
|
171
|
+
*/
|
|
172
|
+
export function ensureTemplateSyncIfNeeded(repoRoot, entry, sinceCommit) {
|
|
173
|
+
const sourcePaths = getTemplateSourcePaths(entry);
|
|
174
|
+
const { files } = collectPathChanges(repoRoot, sourcePaths, sinceCommit);
|
|
175
|
+
|
|
176
|
+
if (files.length === 0) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(
|
|
181
|
+
`Template source changed (${files.length} path(s)) — running sync-templates:check...`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const result = runSyncTemplatesCheck(repoRoot);
|
|
185
|
+
|
|
186
|
+
if (result.ok) {
|
|
187
|
+
console.log("✓ Templates are in sync with packages/core\n");
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.error("✗ Templates are out of sync with packages/core.");
|
|
192
|
+
if (result.output) {
|
|
193
|
+
console.error(result.output);
|
|
194
|
+
}
|
|
195
|
+
console.error("\nRun: pnpm sync-templates");
|
|
196
|
+
console.error("Then retry the version bump.");
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} repoRoot
|
|
203
|
+
* @param {string[]} paths
|
|
204
|
+
* @param {string | undefined} sinceCommit
|
|
205
|
+
*/
|
|
206
|
+
export function collectPathChanges(repoRoot, paths, sinceCommit) {
|
|
207
|
+
const committed = gitDiffFiles(repoRoot, sinceCommit, paths);
|
|
208
|
+
const dirty = gitDirtyFiles(repoRoot, paths);
|
|
209
|
+
const unique = [...new Set([...committed, ...dirty])];
|
|
210
|
+
return { files: unique, committed, dirty };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {string} repoRoot
|
|
215
|
+
*/
|
|
216
|
+
export function readCliPackageVersion(repoRoot) {
|
|
217
|
+
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
218
|
+
return pkg.version ?? "0.0.0";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} repoRoot
|
|
223
|
+
* @param {string} version
|
|
224
|
+
*/
|
|
225
|
+
export function writeCliPackageVersion(repoRoot, version) {
|
|
226
|
+
const pkgPath = join(repoRoot, "package.json");
|
|
227
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
228
|
+
pkg.version = version;
|
|
229
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} repoRoot
|
|
234
|
+
* @param {string} templateId
|
|
235
|
+
* @param {string} version
|
|
236
|
+
*/
|
|
237
|
+
export function writeTemplateManifestVersion(repoRoot, templateId, version) {
|
|
238
|
+
const manifestPath = join(repoRoot, "templates.json");
|
|
239
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
240
|
+
if (!manifest.templates[templateId]) {
|
|
241
|
+
throw new Error(`Unknown template: ${templateId}`);
|
|
242
|
+
}
|
|
243
|
+
manifest.templates[templateId].version = version;
|
|
244
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {string} repoRoot
|
|
249
|
+
* @param {string} directory
|
|
250
|
+
* @param {string} version
|
|
251
|
+
* @param {"patch"|"minor"|"major"} level
|
|
252
|
+
*/
|
|
253
|
+
export function appendChangelogStub(repoRoot, directory, version, level) {
|
|
254
|
+
const changelogPath = join(repoRoot, "templates", directory, "CHANGELOG.md");
|
|
255
|
+
if (!existsSync(changelogPath)) return;
|
|
256
|
+
|
|
257
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
258
|
+
const header = `## [${version}] — ${date}`;
|
|
259
|
+
if (readFileSync(changelogPath, "utf8").includes(header)) return;
|
|
260
|
+
|
|
261
|
+
const stub = `\n${header}\n\n### ${level === "patch" ? "Fixed" : "Changed"}\n- \n`;
|
|
262
|
+
writeFileSync(changelogPath, readFileSync(changelogPath, "utf8").trimEnd() + stub, "utf8");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @param {string} repoRoot
|
|
267
|
+
* @returns {VersionState}
|
|
268
|
+
*/
|
|
269
|
+
export function initializeVersionState(repoRoot) {
|
|
270
|
+
const manifest = loadManifest();
|
|
271
|
+
const head = getHeadCommit(repoRoot);
|
|
272
|
+
const now = new Date().toISOString();
|
|
273
|
+
|
|
274
|
+
/** @type {VersionState} */
|
|
275
|
+
const state = {
|
|
276
|
+
cli: {
|
|
277
|
+
version: readCliPackageVersion(repoRoot),
|
|
278
|
+
gitCommit: head,
|
|
279
|
+
recordedAt: now,
|
|
280
|
+
},
|
|
281
|
+
core: {
|
|
282
|
+
version: "shared",
|
|
283
|
+
gitCommit: head,
|
|
284
|
+
recordedAt: now,
|
|
285
|
+
},
|
|
286
|
+
templates: {},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
for (const [id, entry] of Object.entries(manifest.templates)) {
|
|
290
|
+
state.templates[id] = {
|
|
291
|
+
version: entry.version ?? "0.0.0",
|
|
292
|
+
gitCommit: head,
|
|
293
|
+
recordedAt: now,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
writeVersionState(repoRoot, state);
|
|
298
|
+
return state;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @param {string[]} files
|
|
303
|
+
* @param {number} max
|
|
304
|
+
*/
|
|
305
|
+
export function formatFileList(files, max = 8) {
|
|
306
|
+
if (files.length === 0) return "";
|
|
307
|
+
const preview = files.slice(0, max);
|
|
308
|
+
const lines = preview.map((f) => ` ${f}`);
|
|
309
|
+
if (files.length > max) {
|
|
310
|
+
lines.push(` ... and ${files.length - max} more`);
|
|
311
|
+
}
|
|
312
|
+
return lines.join("\n");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @typedef {{
|
|
317
|
+
* scope: string,
|
|
318
|
+
* version: string,
|
|
319
|
+
* baselineCommit?: string,
|
|
320
|
+
* upToDate: boolean,
|
|
321
|
+
* files: string[],
|
|
322
|
+
* dirty: boolean
|
|
323
|
+
* }} VersionCheckResult
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @param {string} repoRoot
|
|
328
|
+
* @param {{ scope?: string }} options
|
|
329
|
+
*/
|
|
330
|
+
export function runVersionCheck(repoRoot, options = {}) {
|
|
331
|
+
if (!isGitRepository(repoRoot)) {
|
|
332
|
+
console.error("Version tracking requires a git repository.");
|
|
333
|
+
console.error("Run from the templates monorepo root.");
|
|
334
|
+
process.exitCode = 1;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let state = readVersionState(repoRoot);
|
|
339
|
+
if (!state.cli?.gitCommit) {
|
|
340
|
+
console.log("Initializing version tracking (.tempjs-version.json)...");
|
|
341
|
+
state = initializeVersionState(repoRoot);
|
|
342
|
+
console.log("Baseline recorded at current HEAD. All versions marked up to date.\n");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const manifest = loadManifest();
|
|
346
|
+
const scope = options.scope?.toLowerCase();
|
|
347
|
+
const checkCli = !scope || scope === "cli";
|
|
348
|
+
const checkTemplates = !scope || scope === "all" || (scope && scope !== "cli" && manifest.templates[scope]);
|
|
349
|
+
|
|
350
|
+
/** @type {VersionCheckResult[]} */
|
|
351
|
+
const results = [];
|
|
352
|
+
|
|
353
|
+
if (checkCli) {
|
|
354
|
+
const paths = CLI_WATCH_PATHS;
|
|
355
|
+
const record = state.cli;
|
|
356
|
+
const currentVersion = readCliPackageVersion(repoRoot);
|
|
357
|
+
const { files, dirty } = collectPathChanges(repoRoot, paths, record?.gitCommit);
|
|
358
|
+
const versionMismatch = record?.version !== currentVersion;
|
|
359
|
+
results.push({
|
|
360
|
+
scope: "cli",
|
|
361
|
+
version: currentVersion,
|
|
362
|
+
baselineCommit: record?.gitCommit,
|
|
363
|
+
upToDate: files.length === 0 && !versionMismatch,
|
|
364
|
+
files,
|
|
365
|
+
dirty: dirty.length > 0,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const templateIds =
|
|
370
|
+
scope && scope !== "cli" && scope !== "all"
|
|
371
|
+
? [scope]
|
|
372
|
+
: checkTemplates
|
|
373
|
+
? Object.keys(manifest.templates)
|
|
374
|
+
: [];
|
|
375
|
+
|
|
376
|
+
for (const templateId of templateIds) {
|
|
377
|
+
const entry = manifest.templates[templateId];
|
|
378
|
+
if (!entry) continue;
|
|
379
|
+
const paths = getTemplateWatchPaths(templateId, entry);
|
|
380
|
+
const record = state.templates?.[templateId];
|
|
381
|
+
const currentVersion = entry.version ?? "0.0.0";
|
|
382
|
+
const { files, dirty } = collectPathChanges(repoRoot, paths, record?.gitCommit);
|
|
383
|
+
const versionMismatch = record?.version !== currentVersion;
|
|
384
|
+
results.push({
|
|
385
|
+
scope: `template:${templateId}`,
|
|
386
|
+
version: currentVersion,
|
|
387
|
+
baselineCommit: record?.gitCommit,
|
|
388
|
+
upToDate: files.length === 0 && !versionMismatch,
|
|
389
|
+
files,
|
|
390
|
+
dirty: dirty.length > 0,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log("tempjs version check\n");
|
|
395
|
+
|
|
396
|
+
let anyStale = false;
|
|
397
|
+
|
|
398
|
+
for (const result of results) {
|
|
399
|
+
const label =
|
|
400
|
+
result.scope === "cli"
|
|
401
|
+
? `CLI (@navneet_25/tempjs)`
|
|
402
|
+
: `Template ${result.scope.replace("template:", "")} (${manifest.templates[result.scope.replace("template:", "")]?.name ?? ""})`;
|
|
403
|
+
|
|
404
|
+
if (result.upToDate) {
|
|
405
|
+
console.log(`✓ ${label} v${result.version}`);
|
|
406
|
+
console.log(` Up to date since ${result.baselineCommit?.slice(0, 7) ?? "baseline"}`);
|
|
407
|
+
} else {
|
|
408
|
+
anyStale = true;
|
|
409
|
+
console.log(`✗ ${label} v${result.version}`);
|
|
410
|
+
if (result.files.length > 0) {
|
|
411
|
+
console.log(` ${result.files.length} path(s) changed since last version bump:`);
|
|
412
|
+
console.log(formatFileList(result.files));
|
|
413
|
+
}
|
|
414
|
+
if (result.scope === "cli") {
|
|
415
|
+
console.log(` → tempjs version inc patch cli`);
|
|
416
|
+
} else {
|
|
417
|
+
const id = result.scope.replace("template:", "");
|
|
418
|
+
console.log(` → tempjs version inc patch ${id}`);
|
|
419
|
+
}
|
|
420
|
+
console.log("");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!anyStale) {
|
|
425
|
+
console.log("\nEverything is up to date — no unreleased changes detected.");
|
|
426
|
+
} else {
|
|
427
|
+
console.log("\nDocument changes in CHANGELOG.md, then bump with tempjs version inc.");
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @param {string} repoRoot
|
|
434
|
+
* @param {"patch"|"minor"|"major"} level
|
|
435
|
+
* @param {string} target cli | template id | all
|
|
436
|
+
*/
|
|
437
|
+
export function runVersionInc(repoRoot, level, target) {
|
|
438
|
+
if (!["patch", "minor", "major"].includes(level)) {
|
|
439
|
+
console.error(`Invalid level: ${level}. Use patch, minor, or major.`);
|
|
440
|
+
process.exitCode = 1;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!isGitRepository(repoRoot)) {
|
|
445
|
+
console.error("Version bumps require a git repository.");
|
|
446
|
+
process.exitCode = 1;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let state = readVersionState(repoRoot);
|
|
451
|
+
if (!state.cli?.gitCommit) {
|
|
452
|
+
state = initializeVersionState(repoRoot);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const manifest = loadManifest();
|
|
456
|
+
const head = getHeadCommit(repoRoot);
|
|
457
|
+
const now = new Date().toISOString();
|
|
458
|
+
const normalized = target?.toLowerCase() ?? "cli";
|
|
459
|
+
|
|
460
|
+
if (normalized === "all") {
|
|
461
|
+
runVersionInc(repoRoot, level, "cli");
|
|
462
|
+
for (const id of Object.keys(manifest.templates)) {
|
|
463
|
+
runVersionInc(repoRoot, level, id);
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (normalized === "cli") {
|
|
469
|
+
const current = readCliPackageVersion(repoRoot);
|
|
470
|
+
const next = bumpSemver(current, level);
|
|
471
|
+
writeCliPackageVersion(repoRoot, next);
|
|
472
|
+
state.cli = { version: next, gitCommit: head, recordedAt: now };
|
|
473
|
+
writeVersionState(repoRoot, state);
|
|
474
|
+
console.log(`CLI: ${current} → ${next} (${level})`);
|
|
475
|
+
console.log(`Recorded baseline commit ${head.slice(0, 7)}`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const entry = manifest.templates[normalized];
|
|
480
|
+
if (!entry) {
|
|
481
|
+
console.error(`Unknown template: ${normalized}`);
|
|
482
|
+
console.error("Run `tempjs list` for template ids.");
|
|
483
|
+
process.exitCode = 1;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const baselineCommit = state.templates?.[normalized]?.gitCommit;
|
|
488
|
+
|
|
489
|
+
const current = entry.version ?? "0.0.0";
|
|
490
|
+
const next = bumpSemver(current, level);
|
|
491
|
+
writeTemplateManifestVersion(repoRoot, normalized, next);
|
|
492
|
+
appendChangelogStub(repoRoot, entry.directory, next, level);
|
|
493
|
+
|
|
494
|
+
if (!state.templates) state.templates = {};
|
|
495
|
+
state.templates[normalized] = { version: next, gitCommit: head, recordedAt: now };
|
|
496
|
+
state.core = { version: "shared", gitCommit: head, recordedAt: now };
|
|
497
|
+
writeVersionState(repoRoot, state);
|
|
498
|
+
|
|
499
|
+
console.log(`Template ${normalized}: ${current} → ${next} (${level})`);
|
|
500
|
+
console.log(`Updated templates.json and CHANGELOG.md stub`);
|
|
501
|
+
console.log(`Recorded baseline commit ${head.slice(0, 7)}`);
|
|
502
|
+
console.log("\nNext: edit CHANGELOG, commit, publish/push.");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Ensure we're in the monorepo (has templates.json + cli).
|
|
507
|
+
* @param {string} cwd
|
|
508
|
+
*/
|
|
509
|
+
export function resolveMonorepoRoot(cwd) {
|
|
510
|
+
const packageRoot = getPackageRoot();
|
|
511
|
+
if (existsSync(join(packageRoot, "templates.json")) && existsSync(join(packageRoot, "cli"))) {
|
|
512
|
+
return packageRoot;
|
|
513
|
+
}
|
|
514
|
+
if (existsSync(join(cwd, "templates.json")) && existsSync(join(cwd, "cli"))) {
|
|
515
|
+
return cwd;
|
|
516
|
+
}
|
|
517
|
+
return null;
|
|
518
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navneet_25/tempjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "CLI to instantiate website project templates from a single GitHub repository",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"cli",
|
|
11
|
-
"templates.json"
|
|
11
|
+
"templates.json",
|
|
12
|
+
"packages/core/modules.json",
|
|
13
|
+
"packages/core/modules/**",
|
|
14
|
+
"packages/core/prisma/schema.prisma",
|
|
15
|
+
"scripts/module-installer-core.mjs",
|
|
16
|
+
"scripts/template-modules-home.mjs",
|
|
17
|
+
"scripts/template-modules-admin.mjs"
|
|
12
18
|
],
|
|
13
19
|
"engines": {
|
|
14
20
|
"node": ">=18"
|
|
@@ -23,6 +29,23 @@
|
|
|
23
29
|
"license": "MIT",
|
|
24
30
|
"scripts": {
|
|
25
31
|
"sync-templates": "node scripts/sync-templates.mjs",
|
|
26
|
-
"sync-templates:check": "node scripts/sync-templates.mjs --check"
|
|
32
|
+
"sync-templates:check": "node scripts/sync-templates.mjs --check",
|
|
33
|
+
"propagate-core": "node scripts/sync-templates.mjs",
|
|
34
|
+
"new-template": "node scripts/new-template.mjs",
|
|
35
|
+
"template:add-module": "node scripts/template-add-module.mjs",
|
|
36
|
+
"template:assemble": "node scripts/template-assemble.mjs",
|
|
37
|
+
"template:extract-modules": "node scripts/template-extract-modules.mjs",
|
|
38
|
+
"template:validate": "node scripts/template-validate.mjs",
|
|
39
|
+
"template:diff-core": "node scripts/template-diff-core.mjs",
|
|
40
|
+
"setup-hooks": "git config core.hooksPath .githooks && chmod +x .githooks/pre-commit",
|
|
41
|
+
"dev:hotel": "cd templates/hotel-website-template && pnpm dev",
|
|
42
|
+
"dev:real-estate": "cd templates/real-estate-website-template && pnpm dev",
|
|
43
|
+
"docs:dev": "cd docsite && pnpm dev",
|
|
44
|
+
"docs:build": "cd docsite && pnpm build",
|
|
45
|
+
"docsite:dev": "cd docsite && pnpm dev",
|
|
46
|
+
"docsite:build": "cd docsite && pnpm build",
|
|
47
|
+
"version:check": "node cli/index.js version check",
|
|
48
|
+
"version:inc": "node cli/index.js version inc",
|
|
49
|
+
"dev:dev-agency": "cd templates/dev-agency-website-template && pnpm dev"
|
|
27
50
|
}
|
|
28
|
-
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Optional core modules
|
|
2
|
+
|
|
3
|
+
Copy on demand via `pnpm new-template --modules`, `pnpm template:add-module` (monorepo), or `tempjs add-module` (client projects).
|
|
4
|
+
|
|
5
|
+
## Available modules
|
|
6
|
+
|
|
7
|
+
| Id | Feature flag | Prisma | Admin UI today |
|
|
8
|
+
|----|--------------|--------|----------------|
|
|
9
|
+
| `enquiry-modal` | `enquiryModal` | — | — |
|
|
10
|
+
| `footer` | `footer` | — | — |
|
|
11
|
+
| `hero-simple` | `heroSimple` | — | — |
|
|
12
|
+
| `seo` | `seo` | — | sitemap, robots, JSON-LD |
|
|
13
|
+
| `gallery` | `gallery` | `GalleryImage` | GalleryList tab on unified dashboard |
|
|
14
|
+
| `reviews` | `reviews` | `Review` | ReviewsList tab on unified dashboard |
|
|
15
|
+
| `legal-pages` | `legalPages` | — | — |
|
|
16
|
+
| `blog-compose` | `blogCompose` | `BlogPost` | BlogComposePanel tab — compose editor + preview |
|
|
17
|
+
| Optional `+sidebar` on install | `blogSidebar` | — | BlogShell + BlogSidebar on /blog routes |
|
|
18
|
+
|
|
19
|
+
Registry: `packages/core/modules.json`
|
|
20
|
+
|
|
21
|
+
**Admin tab registry:** gallery/reviews/blog-compose update `app/admin/registry.ts` on install; unified `app/admin/page.tsx` uses `lib/admin/AdminShell` + `getAdminTabs()`.
|
|
22
|
+
|
|
23
|
+
## Monorepo
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm new-template bakery bakery-website-template --name "Bakery" \
|
|
27
|
+
--modules seo,hero-simple,footer,enquiry-modal,gallery,reviews,legal-pages
|
|
28
|
+
|
|
29
|
+
pnpm template:add-module bakery gallery,reviews
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Client projects
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
tempjs add-module list
|
|
36
|
+
tempjs add-module seo,gallery,reviews
|
|
37
|
+
pnpm prisma db push
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Published CLI ships `packages/core/modules/` and fetches from GitHub when not in the monorepo. Records installed ids in `.tempjs-modules.json`.
|
|
41
|
+
|
|
42
|
+
## SEO module
|
|
43
|
+
|
|
44
|
+
- `app/sitemap.ts` — `buildAppSitemap()` + `registerDynamicSitemapProvider()` for listing slugs
|
|
45
|
+
- `app/robots.ts` — host, sitemap URL, configurable disallow rules
|
|
46
|
+
- `lib/seo/json-ld.ts` — Organization/WebSite graph
|
|
47
|
+
- `lib/seo/metadata.ts` — canonical, Open Graph, Twitter cards
|
|
48
|
+
- `SiteJsonLd` wired into `app/layout.tsx` on install
|
|
49
|
+
|
|
50
|
+
Template vertical modules (hotel `lib/hotel/register-sitemap.ts`, real-estate `lib/real-estate/register-sitemap.ts`) register dynamic routes.
|
|
51
|
+
|
|
52
|
+
## Gallery / reviews / blog-compose
|
|
53
|
+
|
|
54
|
+
- Appends Prisma models to `prisma/domain.prisma` and regenerates `schema.prisma`
|
|
55
|
+
- Public API + admin CRUD components
|
|
56
|
+
- Scaffold templates: wires `app/page.tsx` and updates `app/admin/registry.ts` (removes legacy `/admin/content` if present)
|
|
57
|
+
- Shipped hotel/real-estate: custom `registry.ts` with vertical tabs; optional module tabs when modules are enabled
|
|
58
|
+
|
|
59
|
+
## Compose blog (`blog-compose`)
|
|
60
|
+
|
|
61
|
+
- Compose Blog Engine under `lib/blog/compose/` — declarative JSON blocks, interpreter, registry
|
|
62
|
+
- `BlogPost` model with `contentJson` Compose document
|
|
63
|
+
- `/blog`, `/blog/[slug]`, `BlogSection`, admin `BlogComposePanel` with live preview
|
|
64
|
+
- `lib/blog/register-sitemap.ts` — dynamic post slugs when `seo` module is installed (`add-module` patches `app/sitemap.ts`)
|
|
65
|
+
|
|
66
|
+
## Legal pages
|
|
67
|
+
|
|
68
|
+
- `/privacy-policy` and `/terms-and-conditions` from `SITE.privacyPage` / `SITE.termsPage`
|
|
69
|
+
|
|
70
|
+
## Template vertical modules (not in this folder)
|
|
71
|
+
|
|
72
|
+
Hotel and real-estate vertical slices live under `templates/<dir>/modules/` with `template-modules.json`. See `pnpm template:assemble` / `pnpm template:extract-modules`.
|