diff --git a/package-lock.json b/package-lock.json index f2a05e8..d5e8a30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-unicorn": "^62.0.0", + "front-matter": "^4.0.2", "globby": "^16.1.0", "json5": "^2.2.3", "ky": "^1.14.2", @@ -1760,6 +1761,7 @@ "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", @@ -2062,6 +2064,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2381,6 +2384,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3114,6 +3118,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3444,6 +3449,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -3705,6 +3724,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5322,6 +5375,7 @@ "integrity": "sha512-Slm000Gd8/AO9z4Kxl4r8mp/iakrbAuJ1L+7ddpkNxgQ+Vf37WPvY63l3oeyZcfuPD1DRrUYBsRPIXSOhvOsmw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.107.0", "@rolldown/pluginutils": "1.0.0-beta.59" @@ -5710,6 +5764,13 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6041,6 +6102,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -6152,6 +6214,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6301,6 +6364,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/package.json b/package.json index 7c4e648..894c834 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-unicorn": "^62.0.0", + "front-matter": "^4.0.2", "globby": "^16.1.0", "json5": "^2.2.3", "ky": "^1.14.2", diff --git a/src/core/project/template.ts b/src/core/project/template.ts index 7182eda..767c194 100644 --- a/src/core/project/template.ts +++ b/src/core/project/template.ts @@ -1,6 +1,7 @@ -import { join } from "node:path"; +import { join, dirname } from "node:path"; import { globby } from "globby"; import ejs from "ejs"; +import frontmatter from 'front-matter'; import { getTemplatesDir, getTemplatesIndexPath } from "../config.js"; import { readJsonFile, writeFile, copyFile } from "../utils/fs.js"; import { TemplatesConfigSchema } from "./schema.js"; @@ -12,6 +13,10 @@ export interface TemplateData { projectId: string; } +interface TemplateFrontmatter { + outputFileName?: string; +} + export async function listTemplates(): Promise { const parsed = await readJsonFile(getTemplatesIndexPath()); const result = TemplatesConfigSchema.parse(parsed); @@ -21,6 +26,7 @@ export async function listTemplates(): Promise { /** * Render a template directory to a destination path. * - Files ending in .ejs are rendered with EJS and written without the .ejs extension + * - EJS files can have frontmatter with custom attributes * - All other files are copied directly */ export async function renderTemplate( @@ -42,11 +48,13 @@ export async function renderTemplate( try { if (file.endsWith(".ejs")) { - // Render EJS template and write without .ejs extension - const destFile = file.replace(/\.ejs$/, ""); - const destFilePath = join(destPath, destFile); + // Render EJS template and write to outputFileName or filename without .ejs extension const rendered = await ejs.renderFile(srcPath, data); - await writeFile(destFilePath, rendered); + const { attributes, body } = frontmatter(rendered); + const destFile = attributes.outputFileName ? join(dirname(file), attributes.outputFileName) : file.replace(/\.ejs$/, ""); + const destFilePath = join(destPath, destFile); + + await writeFile(destFilePath, body); } else { // Copy file directly const destFilePath = join(destPath, file); diff --git a/templates/backend-and-client/.nvmrc b/templates/backend-and-client/.nvmrc new file mode 100644 index 0000000..5767036 --- /dev/null +++ b/templates/backend-and-client/.nvmrc @@ -0,0 +1 @@ +22.21.1 diff --git a/templates/backend-only/base44/.env.local.ejs b/templates/backend-and-client/base44/env.local.ejs similarity index 88% rename from templates/backend-only/base44/.env.local.ejs rename to templates/backend-and-client/base44/env.local.ejs index 31f6944..5731d77 100644 --- a/templates/backend-only/base44/.env.local.ejs +++ b/templates/backend-and-client/base44/env.local.ejs @@ -1,3 +1,6 @@ +--- +outputFileName: .env.local +--- # Base44 Project Environment Variables # This file contains environment-specific configuration for your Base44 project. # Do not commit this file to version control if it contains sensitive data. diff --git a/templates/backend-only/base44/env.local.ejs b/templates/backend-only/base44/env.local.ejs new file mode 100644 index 0000000..5731d77 --- /dev/null +++ b/templates/backend-only/base44/env.local.ejs @@ -0,0 +1,9 @@ +--- +outputFileName: .env.local +--- +# Base44 Project Environment Variables +# This file contains environment-specific configuration for your Base44 project. +# Do not commit this file to version control if it contains sensitive data. + +# Your Base44 Application ID +BASE44_CLIENT_ID=<%= projectId %>