diff --git a/.changeset/quick-beans-play.md b/.changeset/quick-beans-play.md new file mode 100644 index 0000000..ed600a4 --- /dev/null +++ b/.changeset/quick-beans-play.md @@ -0,0 +1,6 @@ +--- +"@flare-city/test": minor +"@flare-city/cli": minor +--- + +Removes `@flare-city/cli` dependency from `@flare-city/test` and adds templates to power the `flare-city init` CLI command diff --git a/.gitignore b/.gitignore index 44e9743..0c6cabe 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,9 @@ bin !.yarn/sdks !.yarn/versions -.turbo \ No newline at end of file +.turbo + +# Placed here to avoid .gitignore for yarn pack +packages/cli/templates/* +packages/cli/!templates/.gitignore +packages/cli/!templates/readme.md \ No newline at end of file diff --git a/.yarn/cache/get-tsconfig-npm-4.7.2-8fbccd9fcf-f21135848f.zip b/.yarn/cache/get-tsconfig-npm-4.7.2-8fbccd9fcf-f21135848f.zip new file mode 100644 index 0000000..8343d76 Binary files /dev/null and b/.yarn/cache/get-tsconfig-npm-4.7.2-8fbccd9fcf-f21135848f.zip differ diff --git a/.yarn/cache/resolve-pkg-maps-npm-1.0.0-135b70c854-0763150adf.zip b/.yarn/cache/resolve-pkg-maps-npm-1.0.0-135b70c854-0763150adf.zip new file mode 100644 index 0000000..8e3561c Binary files /dev/null and b/.yarn/cache/resolve-pkg-maps-npm-1.0.0-135b70c854-0763150adf.zip differ diff --git a/.yarn/cache/tsx-npm-3.14.0-0e7fee0a4e-7147970975.zip b/.yarn/cache/tsx-npm-3.14.0-0e7fee0a4e-7147970975.zip new file mode 100644 index 0000000..1c3e183 Binary files /dev/null and b/.yarn/cache/tsx-npm-3.14.0-0e7fee0a4e-7147970975.zip differ diff --git a/package.json b/package.json index d2d1864..a890104 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "description": "FlareCity simplifies API development on Cloudflare Workers, offering a purposeful and efficient framework for building, testing, and deploying serverless APIs.", "workspaces": { "packages": [ - "packages/**" + "packages/**", + "examples/**" ] }, "repository": { @@ -19,7 +20,7 @@ }, "homepage": "https://github.com/drewdecarme/flare-city#readme", "scripts": { - "build": "yarn turbo build --filter='!with-*'", + "build": "yarn turbo build --filter='!@flare-city/with-*'", "packages:version": "yarn changeset version", "packages:publish": "yarn build && yarn workspaces foreach --all --no-private npm publish --access public", "packages:tag": "yarn changeset tag" diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 8d2ee11..99afc57 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1 +1,2 @@ -app-city \ No newline at end of file +app-city +bin \ No newline at end of file diff --git a/packages/cli/examples/with-sample-route/.eslintrc.cjs b/packages/cli/examples/with-sample-route/eslint.config.cjs similarity index 100% rename from packages/cli/examples/with-sample-route/.eslintrc.cjs rename to packages/cli/examples/with-sample-route/eslint.config.cjs diff --git a/packages/cli/package.json b/packages/cli/package.json index 7880691..366cbe4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -8,11 +8,12 @@ }, "files": [ "bin", - "examples" + "templates" ], "scripts": { - "build": "yarn tsc", - "dev": "yarn tsc --watch", + "build": "yarn process && yarn tsc --project tsconfig.build.json", + "dev": "yarn build --watch", + "process": "yarn tsx ./scripts/process.ts", "lint": "npx eslint ./src/**/* --fix" }, "repository": { @@ -41,7 +42,9 @@ "@types/find-node-modules": "^2.1.1", "@types/fs-extra": "^11.0.3", "@types/inquirer": "^9.0.6", + "@types/node": "^20.8.10", "semantic-release": "^22.0.6", + "tsx": "^3.14.0", "typescript": "5.2.2" } } diff --git a/packages/cli/scripts/process.ts b/packages/cli/scripts/process.ts new file mode 100644 index 0000000..74ca875 --- /dev/null +++ b/packages/cli/scripts/process.ts @@ -0,0 +1,100 @@ +import * as fs from "fs"; +import * as path from "path"; + +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +interface WordReplacement { + oldWord: string | RegExp; + newWord: string; +} + +function replaceWordsInFile( + filePath: string, + outputFilePath: string, + replacements: WordReplacement[] +) { + const fileContent = fs.readFileSync(filePath, "utf-8"); + let updatedContent = fileContent; + + replacements.forEach(({ oldWord, newWord }) => { + updatedContent = updatedContent.replace(new RegExp(oldWord, "g"), newWord); + }); + + fs.writeFileSync(outputFilePath, updatedContent); +} + +function processDirectory( + packageSampleDirPath: string, + packageTemplateDirPath: string, + replacements: WordReplacement[], + excludedDirectories: string[] +) { + fs.readdirSync(packageSampleDirPath).forEach((file) => { + const inputFilePath = path.join(packageSampleDirPath, file); + const outputFilePath = path + .join(packageTemplateDirPath, file) + .concat(".hbs"); + + if (fs.statSync(inputFilePath).isDirectory()) { + if (excludedDirectories.includes(file)) { + // Skip excluded directory + return; + } + const subDirectory = path.join(packageTemplateDirPath, file); + fs.mkdirSync(subDirectory, { recursive: true }); + processDirectory( + inputFilePath, + subDirectory, + replacements, + excludedDirectories + ); + } else { + replaceWordsInFile(inputFilePath, outputFilePath, replacements); + } + }); +} + +const examplesDirPath = path.resolve(__dirname, "../examples"); +const templatesDirPath = path.resolve(__dirname, "../templates"); +const excludedDirectories = [".turbo", "node_modules", ".wrangler"]; + +try { + const directories = fs + .readdirSync(examplesDirPath, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + + // create the templates directory + fs.mkdirSync(templatesDirPath, { recursive: true }); + + directories.forEach((packageName) => { + const packageExampleDirPath = examplesDirPath.concat(`/${packageName}`); + const packageTemplateDirPath = templatesDirPath.concat(`/${packageName}`); + + fs.mkdirSync(packageTemplateDirPath, { recursive: true }); + + const wordReplacements: WordReplacement[] = [ + { + oldWord: `${packageName}-project-description`, + newWord: "{{projectDescription}}", + }, + { oldWord: `${packageName}-project-name`, newWord: `{{projectName}}` }, + { oldWord: packageName, newWord: `{{projectName}}` }, + { oldWord: /workspace:\*/, newWord: "latest" }, + // Add more replacements as needed + ]; + + processDirectory( + packageExampleDirPath, + packageTemplateDirPath, + wordReplacements, + excludedDirectories + ); + + console.log(`Complete processing template: ${packageName}`); + }); +} catch (error) { + throw new Error(error as string); +} diff --git a/packages/cli/src/init/init.command.ts b/packages/cli/src/init/init.command.ts index 814e5e4..7416e2e 100644 --- a/packages/cli/src/init/init.command.ts +++ b/packages/cli/src/init/init.command.ts @@ -39,80 +39,50 @@ async function init(params: InitOptions) { }) : join(process.cwd(), params.location, projectName); + const templatesDirPath = resolve(__dirname, "../../templates"); + + const directories = fs + .readdirSync(templatesDirPath, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + const template = await select({ message: "Please select a template", - choices: [ - { - name: "with-sample-route", - description: - "A simple example that contains 1 route with 2 endpoints at `/sample`.", - value: "with-sample-route", - }, - ], + choices: directories.map((dir) => ({ + name: dir, + value: dir, + })), }); - const srcDir = resolve(__dirname, `../../examples/${template}`); - - if (!srcDir) { - throw new Error(`Cannot locate "${srcDir}"`); - } - - const filesTs = resolve(srcDir, `./**/*.ts`); - const filesLint = resolve(srcDir, `./.eslintrc.cjs`); - const filesGit = resolve(srcDir, `./.gitignore`); - const filesJson = resolve(srcDir, `./*.json`); - const filesToml = resolve(srcDir, `./*.toml`); - - const outputDir = projectLocation; + const templateDirGlob = resolve(templatesDirPath, `./${template}/**/*.*`); + const templateDirDotGlob = resolve(templatesDirPath, `./${template}/**/.*`); // 2. Process TS files - const filesToProcess = glob.sync([ - filesTs, - filesLint, - filesGit, - filesJson, - filesToml, - ]); - - console.log("Processing and transforming template..."); + const filesToProcess = glob.sync([templateDirGlob, templateDirDotGlob]); + + console.log("Instantiating template..."); filesToProcess.forEach((sourcePath) => { const filePath = sourcePath.split(template)[1]; // Extract relative file path from the source path - const outputPath = `${outputDir}${filePath}`; - - let existingContent = fs.readFileSync(sourcePath, "utf-8"); - existingContent = existingContent.replace( - new RegExp(template, "g"), - `{{projectName}}` - ); - existingContent = existingContent.replace( - new RegExp(`${template}-project-name`, "g"), - `{{projectName}}` - ); - existingContent = existingContent.replace( - new RegExp(`${template}-project-description`, "g"), - `{{projectDescription}}` - ); - existingContent = existingContent.replace( - new RegExp(`workspace:`, "g"), - `latest` - ); + const outputPath = `${projectLocation}${filePath}`; + + const existingContent = fs.readFileSync(sourcePath, "utf-8"); // Compile the Handlebars template if it exists - const compiledTemplate = existingContent - ? handlebars.compile(existingContent) - : undefined; + const compiledTemplate = handlebars.compile(existingContent); // Replace placeholders with actual values - const replacedContent = compiledTemplate - ? compiledTemplate({ projectName, projectDescription }) - : existingContent; - - // Write the new TypeScript file - fs.ensureFileSync(outputPath); - fs.writeFileSync(outputPath, replacedContent); + const replacedContent = compiledTemplate({ + projectName, + projectDescription, + }); + + // Write the new file + const outputPathSansHbs = outputPath.split(".hbs")[0]; + fs.ensureFileSync(outputPathSansHbs); + fs.writeFileSync(outputPathSansHbs, replacedContent); }); - console.log("Processing and transforming template... done."); + console.log("Instantiating template... done."); console.log( filesToProcess.length.toString().concat(" files successfully processed.") ); diff --git a/packages/cli/tsconfig.build.json b/packages/cli/tsconfig.build.json new file mode 100644 index 0000000..12afe1e --- /dev/null +++ b/packages/cli/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "@flare-city/tsconfig/library/build", + "compilerOptions": { + "outDir": "./bin", + "declarationDir": "./bin" + }, + "include": ["./src/**/*"] +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 1c9c508..19d5bb2 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,8 +1,4 @@ { - "extends": "@flare-city/tsconfig/library/build", - "compilerOptions": { - "outDir": "./bin", - "declarationDir": "./bin" - }, - "include": ["./src/**/*.ts"] + "extends": "@flare-city/tsconfig/library", + "include": ["./src/**/*", "scripts", "examples"] } diff --git a/packages/eslint-config/.eslintrc.cjs b/packages/eslint-config/.eslintrc.cjs index 3495d2c..c768db4 100644 --- a/packages/eslint-config/.eslintrc.cjs +++ b/packages/eslint-config/.eslintrc.cjs @@ -12,6 +12,7 @@ module.exports = { plugins: ["@typescript-eslint"], ignorePatterns: [ ".eslintrc.cjs", + "eslint.config.cjs", "bin/", "node_modules/", "dist/", diff --git a/packages/test/package.json b/packages/test/package.json index 697bc3e..e0ba85f 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -49,6 +49,7 @@ }, "homepage": "https://github.com/drewdecarme/flare-city#readme", "dependencies": { + "@flare-city/cli": "workspace:*", "@flare-city/core": "workspace:*", "vite": "4.5.0", "vitest": "0.34.6", diff --git a/yarn.lock b/yarn.lock index 33e74bf..4374bc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -907,7 +907,7 @@ __metadata: languageName: node linkType: hard -"@flare-city/cli@workspace:*, @flare-city/cli@workspace:packages/cli": +"@flare-city/cli@workspace:*, @flare-city/cli@workspace:^, @flare-city/cli@workspace:packages/cli": version: 0.0.0-use.local resolution: "@flare-city/cli@workspace:packages/cli" dependencies: @@ -917,12 +917,14 @@ __metadata: "@types/find-node-modules": "npm:^2.1.1" "@types/fs-extra": "npm:^11.0.3" "@types/inquirer": "npm:^9.0.6" + "@types/node": "npm:^20.8.10" commander: "npm:11.1.0" fs-extra: "npm:^11.1.1" glob: "npm:^10.3.10" handlebars: "npm:^4.7.8" inquirer: "npm:^9.2.11" semantic-release: "npm:^22.0.6" + tsx: "npm:^3.14.0" typescript: "npm:5.2.2" vite: "npm:4.5.0" vitest: "npm:0.34.6" @@ -999,6 +1001,7 @@ __metadata: version: 0.0.0-use.local resolution: "@flare-city/test@workspace:packages/test" dependencies: + "@flare-city/cli": "workspace:^" "@flare-city/core": "workspace:*" "@flare-city/eslint-config": "workspace:*" "@flare-city/tsconfig": "workspace:*" @@ -4818,7 +4821,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.10": +"esbuild@npm:^0.18.10, esbuild@npm:~0.18.20": version: 0.18.20 resolution: "esbuild@npm:0.18.20" dependencies: @@ -5718,6 +5721,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.2": + version: 4.7.2 + resolution: "get-tsconfig@npm:4.7.2" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: f21135848fb5d16012269b7b34b186af7a41824830f8616aba17a15eb4d9e54fdc876833f1e21768395215a826c8145582f5acd594ae2b4de3284d10b38d20f8 + languageName: node + linkType: hard + "git-log-parser@npm:^1.2.0": version: 1.2.0 resolution: "git-log-parser@npm:1.2.0" @@ -10410,6 +10422,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 0763150adf303040c304009231314d1e84c6e5ebfa2d82b7d94e96a6e82bacd1dcc0b58ae257315f3c8adb89a91d8d0f12928241cba2df1680fbe6f60bf99b0e + languageName: node + linkType: hard + "resolve.exports@npm:^2.0.2": version: 2.0.2 resolution: "resolve.exports@npm:2.0.2" @@ -11005,7 +11024,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.21": +"source-map-support@npm:0.5.21, source-map-support@npm:^0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -11632,6 +11651,23 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^3.14.0": + version: 3.14.0 + resolution: "tsx@npm:3.14.0" + dependencies: + esbuild: "npm:~0.18.20" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.2" + source-map-support: "npm:^0.5.21" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 7147970975344d779f495169c0246dff5110a3e110b0a89e8cec6403fcd6c03ede3abbf4c7ccc03dae7fb17d7f1a4c21992c5c6d7ab2130cb2f7a3ffaeae1ce1 + languageName: node + linkType: hard + "tty-table@npm:^4.1.5": version: 4.2.3 resolution: "tty-table@npm:4.2.3"