@better-t-stack/template-generator 3.36.5 → 3.37.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/dist/fs-writer.d.mts.map +1 -1
- package/dist/fs-writer.mjs +29 -0
- package/dist/fs-writer.mjs.map +1 -1
- package/dist/index.mjs +5 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/fs-writer.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-writer.d.mts","names":[],"sources":["../src/fs-writer.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"fs-writer.d.mts","names":[],"sources":["../src/fs-writer.ts"],"mappings":";;;;;cAO2F,mBAAA;;;;;;;;cAO9E,cAAA,SAAuB,mBAIhC;;;;AAJJ;iBAUsB,SAAA,CACpB,IAAA,EAAM,eAAA,EACN,OAAA,WACC,OAAA,CAAQ,MAAA,OAAa,cAAA;;;AATpB;AAMJ;iBAmDsB,aAAA,CACpB,IAAA,EAAM,eAAA,EACN,OAAA,UACA,MAAA,GAAS,QAAA,uBACR,OAAA,CAAQ,MAAA,WAAiB,cAAA"}
|
package/dist/fs-writer.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as getBinaryTemplatesRoot } from "./template-reader-DVuwwW6S.mjs";
|
|
2
2
|
import { Result, TaggedError } from "better-result";
|
|
3
3
|
import { dirname, join } from "pathe";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
import * as fs from "node:fs/promises";
|
|
5
6
|
//#region src/fs-writer.ts
|
|
6
7
|
const BINARY_FILE_MARKER = "[Binary file]";
|
|
@@ -31,10 +32,12 @@ async function writeNodeInternal(node, baseDir, relativePath) {
|
|
|
31
32
|
const nodePath = relativePath ? join(relativePath, node.name) : node.name;
|
|
32
33
|
if (node.type === "file") {
|
|
33
34
|
const fileNode = node;
|
|
35
|
+
await assertSafeWritePath(baseDir, fullPath);
|
|
34
36
|
await fs.mkdir(dirname(fullPath), { recursive: true });
|
|
35
37
|
if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) await copyBinaryFile(fileNode.sourcePath, fullPath);
|
|
36
38
|
else if (fileNode.content !== BINARY_FILE_MARKER) await fs.writeFile(fullPath, fileNode.content, "utf-8");
|
|
37
39
|
} else {
|
|
40
|
+
await assertSafeWritePath(baseDir, fullPath);
|
|
38
41
|
await fs.mkdir(fullPath, { recursive: true });
|
|
39
42
|
for (const child of node.children) await writeNodeInternal(child, baseDir, nodePath);
|
|
40
43
|
}
|
|
@@ -64,6 +67,7 @@ async function writeSelectedNodeInternal(node, baseDir, relativePath, filter, wr
|
|
|
64
67
|
if (node.type === "file") {
|
|
65
68
|
if (filter(nodePath)) {
|
|
66
69
|
const fileNode = node;
|
|
70
|
+
await assertSafeWritePath(baseDir, join(baseDir, nodePath));
|
|
67
71
|
await fs.mkdir(dirname(join(baseDir, nodePath)), { recursive: true });
|
|
68
72
|
if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) await copyBinaryFile(fileNode.sourcePath, join(baseDir, nodePath));
|
|
69
73
|
else if (fileNode.content !== BINARY_FILE_MARKER) await fs.writeFile(join(baseDir, nodePath), fileNode.content, "utf-8");
|
|
@@ -75,6 +79,31 @@ async function copyBinaryFile(templatePath, destPath) {
|
|
|
75
79
|
const sourcePath = join(getBinaryTemplatesRoot(), templatePath);
|
|
76
80
|
await fs.copyFile(sourcePath, destPath);
|
|
77
81
|
}
|
|
82
|
+
async function assertSafeWritePath(baseDir, destinationPath) {
|
|
83
|
+
const resolvedBase = path.resolve(baseDir);
|
|
84
|
+
const resolvedDestination = path.resolve(destinationPath);
|
|
85
|
+
const relativeDestination = path.relative(resolvedBase, resolvedDestination);
|
|
86
|
+
if (relativeDestination === ".." || relativeDestination.startsWith(`..${path.sep}`) || path.isAbsolute(relativeDestination)) throw new FileWriteError({
|
|
87
|
+
message: `Refusing to write outside project directory: ${resolvedDestination}`,
|
|
88
|
+
path: resolvedDestination
|
|
89
|
+
});
|
|
90
|
+
let currentPath = resolvedBase;
|
|
91
|
+
const pathSegments = relativeDestination.split(path.sep).filter(Boolean);
|
|
92
|
+
for (const segment of ["", ...pathSegments]) {
|
|
93
|
+
if (segment) currentPath = path.join(currentPath, segment);
|
|
94
|
+
let stats;
|
|
95
|
+
try {
|
|
96
|
+
stats = await fs.lstat(currentPath);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (stats.isSymbolicLink()) throw new FileWriteError({
|
|
102
|
+
message: `Refusing to write through symbolic link: ${currentPath}`,
|
|
103
|
+
path: currentPath
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
78
107
|
//#endregion
|
|
79
108
|
export { FileWriteError, writeSelected, writeTree };
|
|
80
109
|
|
package/dist/fs-writer.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-writer.mjs","names":[],"sources":["../src/fs-writer.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\n\nimport { Result, TaggedError } from \"better-result\";\nimport { join, dirname } from \"pathe\";\n\nimport { getBinaryTemplatesRoot } from \"./core/template-reader\";\nimport type { VirtualFileTree, VirtualNode, VirtualFile, VirtualDirectory } from \"./types\";\n\nconst BINARY_FILE_MARKER = \"[Binary file]\";\n\n/**\n * Error class for filesystem write failures\n */\nexport class FileWriteError extends TaggedError(\"FileWriteError\")<{\n message: string;\n path?: string;\n cause?: unknown;\n}>() {}\n\n/**\n * Writes a virtual file tree to the filesystem.\n * Returns a Result type for type-safe error handling.\n */\nexport async function writeTree(\n tree: VirtualFileTree,\n destDir: string,\n): Promise<Result<void, FileWriteError>> {\n return Result.tryPromise({\n try: async () => {\n for (const child of tree.root.children) {\n await writeNodeInternal(child, destDir, \"\");\n }\n },\n catch: (e) => {\n if (FileWriteError.is(e)) return e;\n return new FileWriteError({\n message: e instanceof Error ? e.message : String(e),\n cause: e,\n });\n },\n });\n}\n\nasync function writeNodeInternal(\n node: VirtualNode,\n baseDir: string,\n relativePath: string,\n): Promise<void> {\n const fullPath = join(baseDir, relativePath, node.name);\n const nodePath = relativePath ? join(relativePath, node.name) : node.name;\n\n if (node.type === \"file\") {\n const fileNode = node as VirtualFile;\n await fs.mkdir(dirname(fullPath), { recursive: true });\n\n if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n await copyBinaryFile(fileNode.sourcePath, fullPath);\n } else if (fileNode.content !== BINARY_FILE_MARKER) {\n await fs.writeFile(fullPath, fileNode.content, \"utf-8\");\n }\n } else {\n await fs.mkdir(fullPath, { recursive: true });\n for (const child of (node as VirtualDirectory).children) {\n await writeNodeInternal(child, baseDir, nodePath);\n }\n }\n}\n\n/**\n * Writes selected files from a virtual file tree to the filesystem.\n * Returns a Result with the list of written file paths.\n */\nexport async function writeSelected(\n tree: VirtualFileTree,\n destDir: string,\n filter: (filePath: string) => boolean,\n): Promise<Result<string[], FileWriteError>> {\n return Result.tryPromise({\n try: async () => {\n const writtenFiles: string[] = [];\n await writeSelectedNodeInternal(tree.root, destDir, \"\", filter, writtenFiles);\n return writtenFiles;\n },\n catch: (e) => {\n if (FileWriteError.is(e)) return e;\n return new FileWriteError({\n message: e instanceof Error ? e.message : String(e),\n cause: e,\n });\n },\n });\n}\n\nasync function writeSelectedNodeInternal(\n node: VirtualNode,\n baseDir: string,\n relativePath: string,\n filter: (filePath: string) => boolean,\n writtenFiles: string[],\n): Promise<void> {\n const nodePath = relativePath ? `${relativePath}/${node.name}` : node.name;\n\n if (node.type === \"file\") {\n if (filter(nodePath)) {\n const fileNode = node as VirtualFile;\n await fs.mkdir(dirname(join(baseDir, nodePath)), { recursive: true });\n\n if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n await copyBinaryFile(fileNode.sourcePath, join(baseDir, nodePath));\n } else if (fileNode.content !== BINARY_FILE_MARKER) {\n await fs.writeFile(join(baseDir, nodePath), fileNode.content, \"utf-8\");\n }\n writtenFiles.push(nodePath);\n }\n } else {\n for (const child of (node as VirtualDirectory).children) {\n await writeSelectedNodeInternal(child, baseDir, nodePath, filter, writtenFiles);\n }\n }\n}\n\nasync function copyBinaryFile(templatePath: string, destPath: string): Promise<void> {\n const templatesRoot = getBinaryTemplatesRoot();\n const sourcePath = join(templatesRoot, templatePath);\n // Let errors propagate - they'll be caught by the Result wrapper\n await fs.copyFile(sourcePath, destPath);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs-writer.mjs","names":[],"sources":["../src/fs-writer.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { Result, TaggedError } from \"better-result\";\nimport { join, dirname } from \"pathe\";\n\nimport { getBinaryTemplatesRoot } from \"./core/template-reader\";\nimport type { VirtualFileTree, VirtualNode, VirtualFile, VirtualDirectory } from \"./types\";\n\nconst BINARY_FILE_MARKER = \"[Binary file]\";\n\n/**\n * Error class for filesystem write failures\n */\nexport class FileWriteError extends TaggedError(\"FileWriteError\")<{\n message: string;\n path?: string;\n cause?: unknown;\n}>() {}\n\n/**\n * Writes a virtual file tree to the filesystem.\n * Returns a Result type for type-safe error handling.\n */\nexport async function writeTree(\n tree: VirtualFileTree,\n destDir: string,\n): Promise<Result<void, FileWriteError>> {\n return Result.tryPromise({\n try: async () => {\n for (const child of tree.root.children) {\n await writeNodeInternal(child, destDir, \"\");\n }\n },\n catch: (e) => {\n if (FileWriteError.is(e)) return e;\n return new FileWriteError({\n message: e instanceof Error ? e.message : String(e),\n cause: e,\n });\n },\n });\n}\n\nasync function writeNodeInternal(\n node: VirtualNode,\n baseDir: string,\n relativePath: string,\n): Promise<void> {\n const fullPath = join(baseDir, relativePath, node.name);\n const nodePath = relativePath ? join(relativePath, node.name) : node.name;\n\n if (node.type === \"file\") {\n const fileNode = node as VirtualFile;\n await assertSafeWritePath(baseDir, fullPath);\n await fs.mkdir(dirname(fullPath), { recursive: true });\n\n if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n await copyBinaryFile(fileNode.sourcePath, fullPath);\n } else if (fileNode.content !== BINARY_FILE_MARKER) {\n await fs.writeFile(fullPath, fileNode.content, \"utf-8\");\n }\n } else {\n await assertSafeWritePath(baseDir, fullPath);\n await fs.mkdir(fullPath, { recursive: true });\n for (const child of (node as VirtualDirectory).children) {\n await writeNodeInternal(child, baseDir, nodePath);\n }\n }\n}\n\n/**\n * Writes selected files from a virtual file tree to the filesystem.\n * Returns a Result with the list of written file paths.\n */\nexport async function writeSelected(\n tree: VirtualFileTree,\n destDir: string,\n filter: (filePath: string) => boolean,\n): Promise<Result<string[], FileWriteError>> {\n return Result.tryPromise({\n try: async () => {\n const writtenFiles: string[] = [];\n await writeSelectedNodeInternal(tree.root, destDir, \"\", filter, writtenFiles);\n return writtenFiles;\n },\n catch: (e) => {\n if (FileWriteError.is(e)) return e;\n return new FileWriteError({\n message: e instanceof Error ? e.message : String(e),\n cause: e,\n });\n },\n });\n}\n\nasync function writeSelectedNodeInternal(\n node: VirtualNode,\n baseDir: string,\n relativePath: string,\n filter: (filePath: string) => boolean,\n writtenFiles: string[],\n): Promise<void> {\n const nodePath = relativePath ? `${relativePath}/${node.name}` : node.name;\n\n if (node.type === \"file\") {\n if (filter(nodePath)) {\n const fileNode = node as VirtualFile;\n await assertSafeWritePath(baseDir, join(baseDir, nodePath));\n await fs.mkdir(dirname(join(baseDir, nodePath)), { recursive: true });\n\n if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n await copyBinaryFile(fileNode.sourcePath, join(baseDir, nodePath));\n } else if (fileNode.content !== BINARY_FILE_MARKER) {\n await fs.writeFile(join(baseDir, nodePath), fileNode.content, \"utf-8\");\n }\n writtenFiles.push(nodePath);\n }\n } else {\n for (const child of (node as VirtualDirectory).children) {\n await writeSelectedNodeInternal(child, baseDir, nodePath, filter, writtenFiles);\n }\n }\n}\n\nasync function copyBinaryFile(templatePath: string, destPath: string): Promise<void> {\n const templatesRoot = getBinaryTemplatesRoot();\n const sourcePath = join(templatesRoot, templatePath);\n // Let errors propagate - they'll be caught by the Result wrapper\n await fs.copyFile(sourcePath, destPath);\n}\n\nasync function assertSafeWritePath(baseDir: string, destinationPath: string): Promise<void> {\n const resolvedBase = path.resolve(baseDir);\n const resolvedDestination = path.resolve(destinationPath);\n const relativeDestination = path.relative(resolvedBase, resolvedDestination);\n\n if (\n relativeDestination === \"..\" ||\n relativeDestination.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativeDestination)\n ) {\n throw new FileWriteError({\n message: `Refusing to write outside project directory: ${resolvedDestination}`,\n path: resolvedDestination,\n });\n }\n\n let currentPath = resolvedBase;\n const pathSegments = relativeDestination.split(path.sep).filter(Boolean);\n for (const segment of [\"\", ...pathSegments]) {\n if (segment) currentPath = path.join(currentPath, segment);\n\n let stats;\n try {\n stats = await fs.lstat(currentPath);\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return;\n }\n throw error;\n }\n\n if (stats.isSymbolicLink()) {\n throw new FileWriteError({\n message: `Refusing to write through symbolic link: ${currentPath}`,\n path: currentPath,\n });\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -272,16 +272,13 @@ function writeBtsConfigToVfs(vfs, projectConfig, version, reproducibleCommand) {
|
|
|
272
272
|
serverDeploy: projectConfig.serverDeploy
|
|
273
273
|
};
|
|
274
274
|
const jsonContent = JSON.stringify(baseContent, null, 2);
|
|
275
|
-
const finalContent = `// Better-T-Stack
|
|
275
|
+
const finalContent = `// Better-T-Stack project metadata
|
|
276
276
|
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// Analytics: https://www.better-t-stack.dev/analytics
|
|
280
|
-
// Showcase: https://www.better-t-stack.dev/showcase
|
|
281
|
-
// Sponsor: https://github.com/sponsors/AmanVarshney01
|
|
277
|
+
// Keep this file to use the \`add\` command.
|
|
278
|
+
// Add addons: ${projectConfig.packageManager === "npm" ? "npx create-better-t-stack@latest add" : projectConfig.packageManager === "pnpm" ? "pnpm dlx create-better-t-stack@latest add" : "bunx create-better-t-stack@latest add"}
|
|
282
279
|
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
280
|
+
// Docs: https://better-t-stack.dev/docs
|
|
281
|
+
// Stack Builder: https://better-t-stack.dev/new
|
|
285
282
|
|
|
286
283
|
${jsonContent}`;
|
|
287
284
|
vfs.writeFile(BTS_CONFIG_FILE, finalContent);
|