diff --git a/.gitignore b/.gitignore index 0f1f6e42..8e3e69a2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ node_modules /postgres-data /app/styles/tailwind.css + +/.idea \ No newline at end of file diff --git a/nx.json b/nx.json new file mode 100644 index 00000000..655c9d1d --- /dev/null +++ b/nx.json @@ -0,0 +1,31 @@ +{ + "extends": "nx/presets/core.json", + "npmScope": "remix", + "tasksRunnerOptions": { + "default": { + "runner": "nx/tasks-runners/default", + "options": { + "cacheableOperations": [ + "build-all", + "validate-all", + "build:css", + "build:remix", + "build:server", + "typecheck", + "test:run", + "lint", + "test:e2e:run" + ] + } + } + }, + "cli": { + "defaultProjectName": "blues-stack-template" + }, + "pluginsConfig": { + "@nrwl/js": { + "analyzeSourceFiles": false, + "analyzePackageJson": false + } + } +} diff --git a/package.json b/package.json index e6d2a54e..3fc68f7e 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,9 @@ "license": "", "sideEffects": false, "scripts": { - "build": "run-s build:*", "build:css": "npm run generate:css -- --minify", "build:remix": "remix build", "build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build", - "dev": "run-p dev:*", "dev:server": "cross-env NODE_ENV=development node --inspect --require ./node_modules/dotenv/config --require ./mocks ./build/server.js", "dev:build": "cross-env NODE_ENV=development npm run build:server -- --watch", "dev:remix": "cross-env NODE_ENV=development remix watch", @@ -22,11 +20,13 @@ "start": "cross-env NODE_ENV=production node ./build/server.js", "start:mocks": "cross-env NODE_ENV=production node --require ./mocks --require dotenv/config ./build/server.js", "test": "vitest", + "test:run": "vitest --run", "test:e2e:dev": "start-server-and-test dev http://localhost:3000 \"cypress open\"", - "pretest:e2e:run": "npm run build", "test:e2e:run": "cross-env PORT=8811 start-server-and-test start:mocks http://localhost:8811 \"cypress run\"", "typecheck": "tsc -b && tsc -b cypress", - "validate": "run-p \"test -- --run\" lint typecheck test:e2e:run" + "build": "nx build-all --output-style=compact", + "dev": "nx build-all --output-style=compact && nx dev-all", + "validate": "nx validate-all --output-style=compact" }, "prettier": {}, "eslintIgnore": [ @@ -76,7 +76,7 @@ "eslint-config-prettier": "^8.5.0", "happy-dom": "^2.55.0", "msw": "^0.39.2", - "npm-run-all": "^4.1.5", + "nx": "14.1.4", "postcss": "^8.4.12", "prettier": "^2.6.1", "prettier-plugin-tailwindcss": "^0.1.8", @@ -95,5 +95,39 @@ }, "prisma": { "seed": "ts-node --require tsconfig-paths/register prisma/seed.ts" + }, + "nx": { + "targets": { + "build:remix": { + "outputs": [ + "/build/index.js", + "/public/build" + ] + }, + "build:server": { + "outputs": [ + "/build/server.js" + ] + }, + "build:css": { + "outputs": [ + "/app/styles/tailwind.css" + ] + }, + "test:e2e:run": { + "outputs": [ + "/cypress/screenshots", + "/cypress/videos" + ], + "dependsOn": [ + "build-all" + ] + }, + "test:e2e:dev": { + "dependsOn": [ + "build-all" + ] + } + } } } diff --git a/project.json b/project.json new file mode 100644 index 00000000..aa650c9b --- /dev/null +++ b/project.json @@ -0,0 +1,40 @@ +{ + "name": "blues-stack-template", + "targets": { + "dev-all": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "nx dev:server blues-stack-template", + "prefix": "[SERVER]", + "color": "blue" + }, + { + "command": "nx dev:build blues-stack-template", + "prefix": "[BUILD-]", + "color": "green" + }, + { + "command": "nx dev:remix blues-stack-template", + "prefix": "[REMIX-]", + "color": "yellow" + }, + { + "command": "nx dev:css blues-stack-template", + "prefix": "[CSS---]", + "color": "cyan" + } + ] + } + }, + "validate-all": { + "executor": "nx:noop", + "dependsOn": ["test:run", "lint", "typecheck", "test:e2e:run"] + }, + "build-all": { + "executor": "nx:noop", + "dependsOn": ["build:css", "build:remix", "build:server"] + } + } +} diff --git a/remix.init/index.js b/remix.init/index.js index 56f718f0..b78bd8fc 100644 --- a/remix.init/index.js +++ b/remix.init/index.js @@ -2,75 +2,58 @@ const crypto = require("crypto"); const fs = require("fs/promises"); const path = require("path"); -const toml = require("@iarna/toml"); -const sort = require("sort-package-json"); - -function escapeRegExp(string) { - // $& means the whole matched string - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); -} - function getRandomString(length) { return crypto.randomBytes(length).toString("hex"); } async function main({ rootDirectory }) { - const README_PATH = path.join(rootDirectory, "README.md"); - const FLY_TOML_PATH = path.join(rootDirectory, "fly.toml"); - const EXAMPLE_ENV_PATH = path.join(rootDirectory, ".env.example"); - const ENV_PATH = path.join(rootDirectory, ".env"); - const PACKAGE_JSON_PATH = path.join(rootDirectory, "package.json"); - - const REPLACER = "blues-stack-template"; - - const DIR_NAME = path.basename(rootDirectory); - const SUFFIX = getRandomString(2); - - const APP_NAME = (DIR_NAME + "-" + SUFFIX) + const APP_NAME = path.basename(rootDirectory); + const APP_ID = (APP_NAME + "-" + getRandomString(2)) // get rid of anything that's not allowed in an app name .replace(/[^a-zA-Z0-9-_]/g, "-"); - const [prodContent, readme, env, packageJson] = await Promise.all([ - fs.readFile(FLY_TOML_PATH, "utf-8"), - fs.readFile(README_PATH, "utf-8"), - fs.readFile(EXAMPLE_ENV_PATH, "utf-8"), - fs.readFile(PACKAGE_JSON_PATH, "utf-8"), - ]); + // copy files + const filesToCopy = [["remix.init/gitignore", ".gitignore"]]; + for (const [from, to] of filesToCopy) { + await fs.copyFile( + path.join(rootDirectory, from), + path.join(rootDirectory, to) + ); + } + // update env to have SESSION_SECRET + const EXAMPLE_ENV_PATH = path.join(rootDirectory, ".env.example"); + const ENV_PATH = path.join(rootDirectory, ".env"); + const env = await fs.readFile(EXAMPLE_ENV_PATH, "utf-8"); const newEnv = env.replace( /^SESSION_SECRET=.*$/m, `SESSION_SECRET="${getRandomString(16)}"` ); - - const prodToml = toml.parse(prodContent); - prodToml.app = prodToml.app.replace(REPLACER, APP_NAME); - - const newReadme = readme.replace( - new RegExp(escapeRegExp(REPLACER), "g"), - APP_NAME - ); - - const newPackageJson = - JSON.stringify( - sort({ ...JSON.parse(packageJson), name: APP_NAME }), - null, - 2 - ) + "\n"; - - await Promise.all([ - fs.writeFile(FLY_TOML_PATH, toml.stringify(prodToml)), - fs.writeFile(README_PATH, newReadme), - fs.writeFile(ENV_PATH, newEnv), - fs.writeFile(PACKAGE_JSON_PATH, newPackageJson), - fs.copyFile( - path.join(rootDirectory, "remix.init", "gitignore"), - path.join(rootDirectory, ".gitignore") - ), - fs.rm(path.join(rootDirectory, ".github/ISSUE_TEMPLATE"), { - recursive: true, - }), - fs.rm(path.join(rootDirectory, ".github/PULL_REQUEST_TEMPLATE.md")), - ]); + await fs.writeFile(ENV_PATH, newEnv); + + // delete files only needed for the template + const filesToDelete = [ + ".github/ISSUE_TEMPLATE", + ".github/PULL_REQUEST_TEMPLATE.md", + ]; + for (const file of filesToDelete) { + await fs.rm(path.join(rootDirectory, file), { recursive: true }); + } + + // replace "blues-stack-template" in all files with the app name + const filesToReplaceIn = [ + "README.md", + "package.json", + "fly.toml", + "project.json", + "nx.json", + ]; + for (const file of filesToReplaceIn) { + const filePath = path.join(rootDirectory, file); + const contents = await fs.readFile(filePath, "utf-8"); + const newContents = contents.replace(/blues-stack-template/g, APP_ID); + await fs.writeFile(filePath, newContents); + } console.log( ` diff --git a/remix.init/package.json b/remix.init/package.json index b8fe14b4..d219d9f2 100644 --- a/remix.init/package.json +++ b/remix.init/package.json @@ -3,8 +3,5 @@ "private": true, "main": "index.js", "license": "MIT", - "dependencies": { - "@iarna/toml": "^2.2.5", - "sort-package-json": "^1.55.0" - } + "dependencies": {} }