@aime-aatrick/create-fullstack-template 1.0.5 → 1.0.8

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.
@@ -2,8 +2,9 @@
2
2
 
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { existsSync } from "node:fs";
5
- import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
6
- import { join, resolve } from "node:path";
5
+ import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile, copyFile } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
+ import { dirname, join, resolve } from "node:path";
7
8
  import { createInterface } from "node:readline/promises";
8
9
  import { spawn } from "node:child_process";
9
10
  import degit from "degit";
@@ -58,10 +59,167 @@ const askPackageManager = async (rl) => {
58
59
  return "pnpm";
59
60
  };
60
61
 
62
+ const readIfExists = async (path) => {
63
+ if (!existsSync(path)) return null;
64
+ return readFile(path, "utf8");
65
+ };
66
+
67
+ const shouldIgnoreRelativePath = (relativePath) => {
68
+ const normalized = relativePath.replaceAll("\\", "/");
69
+ if (
70
+ normalized === ".env" ||
71
+ normalized === ".env.local" ||
72
+ normalized === ".env.example" ||
73
+ normalized === "backend/.env" ||
74
+ normalized === "backend/.env.example" ||
75
+ normalized === "frontend/.env.local" ||
76
+ normalized === "frontend/.env.example"
77
+ ) {
78
+ return true;
79
+ }
80
+
81
+ const ignoredPathParts = [".git", "node_modules", ".next", "dist", "coverage", ".turbo"];
82
+ return ignoredPathParts.some(
83
+ (part) => normalized === part || normalized.startsWith(`${part}/`) || normalized.includes(`/${part}/`),
84
+ );
85
+ };
86
+
87
+ const syncTemplateTree = async (sourceDir, destinationDir, options) => {
88
+ const { force } = options;
89
+ const stats = {
90
+ copied: 0,
91
+ updated: 0,
92
+ skipped: 0,
93
+ };
94
+
95
+ const walk = async (sourcePath, destinationPath, relativePath = "") => {
96
+ if (shouldIgnoreRelativePath(relativePath)) return;
97
+
98
+ const sourceStat = await stat(sourcePath);
99
+ if (sourceStat.isDirectory()) {
100
+ if (!existsSync(destinationPath)) {
101
+ await mkdir(destinationPath, { recursive: true });
102
+ }
103
+ const entries = await readdir(sourcePath, { withFileTypes: true });
104
+ for (const entry of entries) {
105
+ const childSource = join(sourcePath, entry.name);
106
+ const childDestination = join(destinationPath, entry.name);
107
+ const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
108
+ await walk(childSource, childDestination, childRelative);
109
+ }
110
+ return;
111
+ }
112
+
113
+ if (!existsSync(destinationPath)) {
114
+ await mkdir(dirname(destinationPath), { recursive: true });
115
+ await copyFile(sourcePath, destinationPath);
116
+ stats.copied += 1;
117
+ return;
118
+ }
119
+
120
+ const sourceContent = await readFile(sourcePath);
121
+ const destinationContent = await readFile(destinationPath);
122
+ if (sourceContent.equals(destinationContent)) return;
123
+
124
+ if (!force) {
125
+ stats.skipped += 1;
126
+ return;
127
+ }
128
+
129
+ await writeFile(destinationPath, sourceContent);
130
+ stats.updated += 1;
131
+ };
132
+
133
+ await walk(sourceDir, destinationDir);
134
+ return stats;
135
+ };
136
+
137
+ const runUpdate = async (args) => {
138
+ const force = args.includes("--force");
139
+ const targetArg = args.find((value) => !value.startsWith("--")) || ".";
140
+ const targetDir = resolve(process.cwd(), targetArg);
141
+
142
+ if (!existsSync(targetDir)) {
143
+ throw new Error(`Target folder does not exist: ${targetDir}`);
144
+ }
145
+
146
+ const backendExists = existsSync(join(targetDir, "backend"));
147
+ const frontendExists = existsSync(join(targetDir, "frontend"));
148
+ if (!backendExists && !frontendExists) {
149
+ throw new Error(
150
+ 'Target does not look like a generated project. Expected "backend" and/or "frontend" folders.',
151
+ );
152
+ }
153
+
154
+ const tempRoot = await mkdtemp(join(tmpdir(), "fullstack-template-update-"));
155
+ const templateDir = join(tempRoot, "template");
156
+
157
+ try {
158
+ console.log(`\nFetching latest template from ${REPO} ...`);
159
+ const emitter = degit(REPO, { cache: false, force: true, verbose: true });
160
+ await emitter.clone(templateDir);
161
+
162
+ await rm(join(templateDir, "create-fullstack-template"), { recursive: true, force: true });
163
+ await rm(join(templateDir, ".github"), { recursive: true, force: true });
164
+
165
+ const total = { copied: 0, updated: 0, skipped: 0 };
166
+
167
+ if (backendExists && existsSync(join(templateDir, "backend"))) {
168
+ console.log("Updating backend files...");
169
+ const backendStats = await syncTemplateTree(
170
+ join(templateDir, "backend"),
171
+ join(targetDir, "backend"),
172
+ { force },
173
+ );
174
+ total.copied += backendStats.copied;
175
+ total.updated += backendStats.updated;
176
+ total.skipped += backendStats.skipped;
177
+ }
178
+
179
+ if (frontendExists && existsSync(join(templateDir, "frontend"))) {
180
+ console.log("Updating frontend files...");
181
+ const frontendStats = await syncTemplateTree(
182
+ join(templateDir, "frontend"),
183
+ join(targetDir, "frontend"),
184
+ { force },
185
+ );
186
+ total.copied += frontendStats.copied;
187
+ total.updated += frontendStats.updated;
188
+ total.skipped += frontendStats.skipped;
189
+ }
190
+
191
+ const templateReadme = await readIfExists(join(templateDir, "README.md"));
192
+ if (templateReadme && (force || !existsSync(join(targetDir, "README.md")))) {
193
+ const readmeExists = existsSync(join(targetDir, "README.md"));
194
+ await writeFile(join(targetDir, "README.md"), templateReadme, "utf8");
195
+ if (readmeExists) {
196
+ total.updated += 1;
197
+ } else {
198
+ total.copied += 1;
199
+ }
200
+ } else if (templateReadme) {
201
+ total.skipped += 1;
202
+ }
203
+
204
+ console.log("\nUpdate complete.");
205
+ console.log(`- Copied new files: ${total.copied}`);
206
+ console.log(`- Updated files: ${total.updated}`);
207
+ console.log(`- Skipped conflicts (use --force to overwrite): ${total.skipped}`);
208
+ } finally {
209
+ await rm(tempRoot, { recursive: true, force: true });
210
+ }
211
+ };
212
+
61
213
  const main = async () => {
62
214
  const rl = createInterface({ input: process.stdin, output: process.stdout });
63
215
 
64
216
  try {
217
+ const command = process.argv[2];
218
+ if (command === "update") {
219
+ await runUpdate(process.argv.slice(3));
220
+ return;
221
+ }
222
+
65
223
  const argName = process.argv[2];
66
224
  const projectName =
67
225
  argName || (await rl.question("Project name (folder): ")).trim();
@@ -115,6 +273,10 @@ const main = async () => {
115
273
  const emitter = degit(REPO, { cache: false, force: true, verbose: true });
116
274
  await emitter.clone(targetDir);
117
275
 
276
+ // Remove template-maintenance folders that should not ship in generated apps.
277
+ await rm(join(targetDir, "create-fullstack-template"), { recursive: true, force: true });
278
+ await rm(join(targetDir, ".github"), { recursive: true, force: true });
279
+
118
280
  if (mode === "backend") {
119
281
  await rm(join(targetDir, "frontend"), { recursive: true, force: true });
120
282
  } else if (mode === "frontend") {
@@ -189,13 +351,22 @@ const main = async () => {
189
351
  if (hasFrontend) {
190
352
  let frontendEnv = await readEnvTemplate(
191
353
  frontendEnvExample,
192
- ["NEXT_PUBLIC_API_URL=http://localhost:3000", ""].join("\n"),
354
+ [
355
+ "NEXT_PUBLIC_API_URL=http://localhost:3000",
356
+ "NEXT_PUBLIC_DESIGN_MODE=false",
357
+ "",
358
+ ].join("\n"),
193
359
  );
194
360
  frontendEnv = setEnvValue(
195
361
  frontendEnv,
196
362
  "NEXT_PUBLIC_API_URL",
197
363
  `http://localhost:${backendPort}`,
198
364
  );
365
+ frontendEnv = setEnvValue(
366
+ frontendEnv,
367
+ "NEXT_PUBLIC_DESIGN_MODE",
368
+ mode === "frontend" ? "true" : "false",
369
+ );
199
370
  await writeFile(frontendEnvPath, frontendEnv, "utf8");
200
371
  }
201
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aime-aatrick/create-fullstack-template",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "description": "Interactive installer for the fullstack template",
5
5
  "type": "module",
6
6
  "license": "MIT",