@aime-aatrick/create-fullstack-template 1.0.6 → 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();
@@ -193,13 +351,22 @@ const main = async () => {
193
351
  if (hasFrontend) {
194
352
  let frontendEnv = await readEnvTemplate(
195
353
  frontendEnvExample,
196
- ["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"),
197
359
  );
198
360
  frontendEnv = setEnvValue(
199
361
  frontendEnv,
200
362
  "NEXT_PUBLIC_API_URL",
201
363
  `http://localhost:${backendPort}`,
202
364
  );
365
+ frontendEnv = setEnvValue(
366
+ frontendEnv,
367
+ "NEXT_PUBLIC_DESIGN_MODE",
368
+ mode === "frontend" ? "true" : "false",
369
+ );
203
370
  await writeFile(frontendEnvPath, frontendEnv, "utf8");
204
371
  }
205
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aime-aatrick/create-fullstack-template",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Interactive installer for the fullstack template",
5
5
  "type": "module",
6
6
  "license": "MIT",