From 63e28a7924fc15221c312cb699ec2b06bea29256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Ba=CC=88chler?= Date: Sun, 30 Nov 2025 21:03:09 +0100 Subject: [PATCH 1/2] use latest versions in all template packages --- template/api/package.json | 9 +---- template/config/eslint/package.json | 20 +++++----- template/docs/package.json | 12 +++--- template/mock-backend/package.json | 20 +++++----- template/mock-data/package.json | 18 ++++----- template/package.json | 32 +++++++-------- template/prototype/package.json | 60 ++++++++++++++--------------- template/ui/package.json | 42 ++++++++++---------- 8 files changed, 103 insertions(+), 110 deletions(-) diff --git a/template/api/package.json b/template/api/package.json index 6a06685..c297c84 100644 --- a/template/api/package.json +++ b/template/api/package.json @@ -17,9 +17,6 @@ "@repo/eslint-config": "*", "@repo/typescript-config": "*" }, - "peerDependencies": { - "msw": "^2.4.9" - }, "engines": { "node": ">=22.0.0" }, @@ -42,9 +39,5 @@ "*.{json,md}": [ "prettier --write" ] - }, - "dependencies": { - "memoize": "^10.0.0", - "msw": "^2.4.9" } -} +} \ No newline at end of file diff --git a/template/config/eslint/package.json b/template/config/eslint/package.json index 7461435..3d1380a 100644 --- a/template/config/eslint/package.json +++ b/template/config/eslint/package.json @@ -7,19 +7,19 @@ "react.js" ], "devDependencies": { - "@typescript-eslint/eslint-plugin": "^8.8.0", - "@typescript-eslint/parser": "^8.8.0", - "eslint-config-prettier": "^10.0.1", - "eslint-config-turbo": "^2.1.3", - "eslint-plugin-only-warn": "^1.1.0", - "eslint-plugin-react": "^7.37.1", - "eslint-plugin-react-hooks": "^5.0.0", - "typescript": "^5.6.2", - "typescript-eslint": "^8.8.0" + "@typescript-eslint/eslint-plugin": "latest", + "@typescript-eslint/parser": "latest", + "eslint-config-prettier": "latest", + "eslint-config-turbo": "latest", + "eslint-plugin-only-warn": "latest", + "eslint-plugin-react": "latest", + "eslint-plugin-react-hooks": "latest", + "typescript": "latest", + "typescript-eslint": "latest" }, "type": "module", "exports": { "./base": "./base.js", "./react": "./react.js" } -} +} \ No newline at end of file diff --git a/template/docs/package.json b/template/docs/package.json index 6cfc751..71e3ae1 100644 --- a/template/docs/package.json +++ b/template/docs/package.json @@ -10,13 +10,13 @@ ], "author": "", "dependencies": { - "@antora/cli": "^3.1.9", - "@antora/site-generator-default": "^3.1.9", - "asciidoctor-kroki": "^0.18.1", - "mkdirp": "^3.0.1", - "unxhr": "^1.2.0" + "@antora/cli": "latest", + "@antora/site-generator-default": "latest", + "asciidoctor-kroki": "latest", + "mkdirp": "latest", + "unxhr": "latest" }, "license": "UNLICENSED", "private": true, "description": "Entwicklerdokumentation für das PLANNING STACK TEMPLATE Projekt" -} +} \ No newline at end of file diff --git a/template/mock-backend/package.json b/template/mock-backend/package.json index 72ab27f..f23b476 100644 --- a/template/mock-backend/package.json +++ b/template/mock-backend/package.json @@ -14,21 +14,21 @@ }, "dependencies": { "@repo/api": "*", - "@repo/mock-data": "2024.0.0", - "@tinyhttp/cookie": "^2.1.1", - "memoize": "^10.0.0", - "msw": "^2.4.9", - "query-string": "^9.1.1" + "@repo/mock-data": "*", + "@tinyhttp/cookie": "latest", + "memoize": "latest", + "msw": "latest", + "query-string": "latest" }, "devDependencies": { "@repo/eslint-config": "*", "@repo/typescript-config": "*", - "@types/node": "^22.7.4", - "openapi-typescript": "^7.4.1", - "prettier": "^3.3.3" + "@types/node": "latest", + "openapi-typescript": "latest", + "prettier": "latest" }, "peerDependencies": { - "msw": "^2.4.9" + "msw": "latest" }, "engines": { "node": ">=22.0.0" @@ -59,4 +59,4 @@ "prettier --write" ] } -} +} \ No newline at end of file diff --git a/template/mock-data/package.json b/template/mock-data/package.json index 5793c85..fc85278 100644 --- a/template/mock-data/package.json +++ b/template/mock-data/package.json @@ -29,17 +29,17 @@ "devDependencies": { "@repo/eslint-config": "*", "@repo/typescript-config": "*", - "@types/fs-extra": "^11.0.4", - "@types/node": "^22.7.4", - "fs-extra": "^11.2.0", - "rimraf": "^6.0.1" + "@types/fs-extra": "latest", + "@types/node": "latest", + "fs-extra": "latest", + "rimraf": "latest" }, "dependencies": { - "@faker-js/faker": "^9.0.3", + "@faker-js/faker": "latest", "@repo/api": "*", - "commander": "^13.0.0", - "enforce-unique": "^1.3.0", - "memoize": "^10.0.0" + "commander": "latest", + "enforce-unique": "latest", + "memoize": "latest" }, "engines": { "node": ">=22.0.0" @@ -61,4 +61,4 @@ "prettier --write" ] } -} +} \ No newline at end of file diff --git a/template/package.json b/template/package.json index ca50ad4..61acd25 100644 --- a/template/package.json +++ b/template/package.json @@ -15,27 +15,29 @@ "clean": "rimraf .turbo && turbo run clean", "format": "prettier --write \"**/*.{ts,tsx,md}\"" }, + "dependencies": { + "concurrently": "latest" + }, "devDependencies": { "@repo/api": "*", - "eslint-plugin-import": "^2.31.0", - "patch-package": "^8.0.0", - "prettier": "^3.5.3", - "prettier-plugin-organize-imports": "^4.1.0", - "tsc-alias": "^1.8.16", - "turbo": "^2.5.4", - "typescript": "^5.8.3", - "vite": "^6.4.1", - "vitest": "^3.2.2", - "vite-tsconfig-paths": "^5.1.4" + "eslint-plugin-import": "latest", + "patch-package": "latest", + "prettier": "latest", + "prettier-plugin-organize-imports": "latest", + "tsc-alias": "latest", + "turbo": "latest", + "typescript": "latest", + "vite": "latest", + "vitest": "latest", + "vite-tsconfig-paths": "latest" }, "engines": { "node": ">=22" }, "resolutions": { - "typescript": "5.8.3" + "typescript": "latest" }, "type": "module", - "packageManager": "npm@11.4.1", "license": "MIT", "workspaces": [ "api", @@ -48,7 +50,5 @@ "prototype", "ui" ], - "dependencies": { - "concurrently": "^9.1.2" - } -} + "packageManager": "npm@11.4.1" +} \ No newline at end of file diff --git a/template/prototype/package.json b/template/prototype/package.json index db41ab9..36b9a94 100644 --- a/template/prototype/package.json +++ b/template/prototype/package.json @@ -13,46 +13,46 @@ "format": "prettier --write ." }, "dependencies": { - "@radix-ui/react-avatar": "^1.1.2", - "@radix-ui/react-slot": "^1.1.1", - "@react-router/node": "^7.0.0", + "@radix-ui/react-avatar": "latest", + "@radix-ui/react-slot": "latest", + "@react-router/node": "latest", "@repo/api": "*", "@repo/mock-backend": "*", "@repo/ui": "*", - "@tinyhttp/cookie": "^2.1.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.0.0", - "isbot": "^5", - "ky": "^1.7.2", - "lodash-es": "^4.17.21", - "lucide-react": "^0.472.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-router": "^7.0.1", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "use-container-queries": "^1.0.0" + "@tinyhttp/cookie": "latest", + "class-variance-authority": "latest", + "clsx": "latest", + "cmdk": "latest", + "isbot": "latest", + "ky": "latest", + "lodash-es": "latest", + "lucide-react": "latest", + "react": "latest", + "react-dom": "latest", + "react-router": "latest", + "tailwind-merge": "latest", + "tailwindcss-animate": "latest", + "use-container-queries": "latest" }, "devDependencies": { - "@react-router/dev": "^7.0.0", + "@react-router/dev": "latest", "@repo/eslint-config": "*", "@repo/typescript-config": "*", - "@tailwindcss/typography": "^0.5.15", - "@types/lodash-es": "^4.17.12", - "@types/node": "^22.7.4", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", - "autoprefixer": "^10.4.20", - "eslint-plugin-react-compiler": "^19.0.0-beta-e552027-20250112", - "msw": "2.7.0", - "postcss": "^8.4.47", - "prettier": "^3.3.3", - "tailwindcss": "^3.4.13" + "@tailwindcss/typography": "latest", + "@types/lodash-es": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "autoprefixer": "latest", + "eslint-plugin-react-compiler": "latest", + "msw": "latest", + "postcss": "latest", + "prettier": "latest", + "tailwindcss": "latest" }, "msw": { "workerDirectory": [ "public" ] } -} +} \ No newline at end of file diff --git a/template/ui/package.json b/template/ui/package.json index e9bccbe..dbc7e24 100644 --- a/template/ui/package.json +++ b/template/ui/package.json @@ -23,31 +23,31 @@ "devDependencies": { "@repo/eslint-config": "*", "@repo/typescript-config": "*", - "@tailwindcss/container-queries": "^0.1.1", - "@turbo/gen": "^2.1.3", - "@types/eslint": "^9.6.1", - "@types/node": "^22.7.4", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "typescript": "^5.6.2" + "@tailwindcss/container-queries": "latest", + "@turbo/gen": "latest", + "@types/eslint": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "eslint-plugin-jsx-a11y": "latest", + "react": "latest", + "react-dom": "latest", + "typescript": "latest" }, "dependencies": { - "@radix-ui/react-avatar": "^1.1.1", - "@radix-ui/react-scroll-area": "^1.2.0", - "@radix-ui/react-slot": "^1.1.0", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.1", - "lucide-react": "^0.472.0", - "tailwind-merge": "^2.5.2", - "tailwindcss": "^3.4.13", - "tailwindcss-animate": "^1.0.7", - "use-container-queries": "^1.0.0" + "@radix-ui/react-avatar": "latest", + "@radix-ui/react-scroll-area": "latest", + "@radix-ui/react-slot": "latest", + "class-variance-authority": "latest", + "clsx": "latest", + "lucide-react": "latest", + "tailwind-merge": "latest", + "tailwindcss": "latest", + "tailwindcss-animate": "latest", + "use-container-queries": "latest" }, "peerDependencies": { "react": ">=19.0.0" }, "type": "module" -} +} \ No newline at end of file From e2bec3d36f6a3b0ee8b288f9025290eebef57480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Ba=CC=88chler?= Date: Sat, 6 Dec 2025 23:38:18 +0100 Subject: [PATCH 2/2] pin versions after install --- CONTRIBUTING.md | 2 +- create/main.js | 110 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c07726..07ebbe0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,5 +6,5 @@ To verify the generator before publishing: 1. Run `npm pack` in this repository to produce a tarball (for example `create-tinker-stack-0.2.2.tgz`). -2. In a temporary directory, execute `npx --yes create-tinker-stack@file:/absolute/path/to/create-tinker-stack-0.2.2.tgz`. +2. In a temporary directory, execute `npx --yes create-tinker-stack@file://$(pwd)/create-tinker-stack-0.2.3.tgz`. - Replace the path with the absolute path to the tarball from step 1 (npm `create` does not currently support `file:` specifiers). diff --git a/create/main.js b/create/main.js index 5fa6e2d..0c4fafd 100644 --- a/create/main.js +++ b/create/main.js @@ -7,30 +7,30 @@ import path from 'node:path'; const getRandomString = length => crypto.randomBytes(length).toString('hex'); export async function main({ cwd, templateDir, targetDir }) { - const APP_TITLE = await getTitle(); + const APP_TITLE = await getTitle(); - const APP_NAME = (APP_TITLE) - // get rid of anything that's not allowed in an app name - .replace(/[^a-zA-Z0-9-_]/g, '-') - .toLowerCase(); + const APP_NAME = (APP_TITLE) + // get rid of anything that's not allowed in an app name + .replace(/[^a-zA-Z0-9-_]/g, '-') + .toLowerCase(); - // Use targetDir if it is set, otherwise create it from app-name - targetDir = targetDir ? targetDir : path.join(cwd, APP_NAME); + // Use targetDir if it is set, otherwise create it from app-name + targetDir = targetDir ? targetDir : path.join(cwd, APP_NAME); - console.log({ cwd, templateDir, targetDir, APP_NAME, APP_TITLE }); + console.log({ cwd, templateDir, targetDir, APP_NAME, APP_TITLE }); - await fs.cp(templateDir, targetDir, { recursive: true, force: false, errorOnExist: true }, (err) => { - console.warn(err); - }); + await fs.cp(templateDir, targetDir, { recursive: true, force: false, errorOnExist: true }, (err) => { + console.warn(err); + }); - const EXAMPLE_ENV_PATH = path.join(targetDir, '.env.example'); + const EXAMPLE_ENV_PATH = path.join(targetDir, '.env.example'); const ENV_PATH = path.join(targetDir, '.env'); const PKG_PATH = path.join(targetDir, 'package.json'); const appNameRegex = /planning-stack-template/g; const appTitleRegex = /PLANNING STACK TEMPLATE/g; - const [env, packageJsonString] = await Promise.all([ + const [env, packageJsonString] = await Promise.all([ fs.readFile(EXAMPLE_ENV_PATH, 'utf-8'), fs.readFile(PKG_PATH, 'utf-8'), ]); @@ -93,6 +93,7 @@ export async function main({ cwd, templateDir, targetDir }) { if (!process.env.SKIP_SETUP) { execSync('npm install', { cwd: targetDir, stdio: 'inherit' }); + await pinDependencies(targetDir); execSync('npm run typecheck', { cwd: targetDir, stdio: 'inherit' }); execSync('npm run build:data', { cwd: targetDir, stdio: 'inherit' }); } @@ -118,6 +119,89 @@ What's next? ); } +async function pinDependencies(targetDir) { + const pinPackage = async (dir, isRoot = false) => { + const pkgPath = path.join(dir, 'package.json'); + let pkg; + try { + pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8')); + } catch (e) { + return null; + } + + let installedDeps = {}; + try { + const output = execSync('npm list --json --depth=0', { + cwd: dir, + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + const parsed = JSON.parse(output); + installedDeps = parsed.dependencies || {}; + // In workspaces, npm list often returns the root dependencies with the workspace nested + if (pkg.name && installedDeps[pkg.name] && installedDeps[pkg.name].dependencies) { + installedDeps = installedDeps[pkg.name].dependencies; + } + } catch (e) { + if (e.stdout) { + const parsed = JSON.parse(e.stdout.toString()); + installedDeps = parsed.dependencies || {}; + if (pkg.name && installedDeps[pkg.name] && installedDeps[pkg.name].dependencies) { + installedDeps = installedDeps[pkg.name].dependencies; + } + } + } + + const updateDeps = (depsObj) => { + if (!depsObj) return; + for (const dep of Object.keys(depsObj)) { + if (depsObj[dep] === '*') continue; + if (installedDeps[dep] && installedDeps[dep].version) { + depsObj[dep] = installedDeps[dep].version; + } + } + }; + + updateDeps(pkg.dependencies); + updateDeps(pkg.devDependencies); + + if (isRoot && pkg.resolutions) { + for (const dep of Object.keys(pkg.resolutions)) { + if (pkg.resolutions[dep] === '*' || !installedDeps[dep]) continue; + if (installedDeps[dep].version) { + pkg.resolutions[dep] = installedDeps[dep].version; + } + } + } + + await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + return pkg; + }; + + const rootPkg = await pinPackage(targetDir, true); + + if (rootPkg && Array.isArray(rootPkg.workspaces)) { + for (const workspace of rootPkg.workspaces) { + // Handle basic glob patterns (ends with /*) + if (workspace.endsWith('/*')) { + const parentDir = path.join(targetDir, workspace.slice(0, -2)); + try { + const subdirs = await fs.readdir(parentDir, { withFileTypes: true }); + for (const dirent of subdirs) { + if (dirent.isDirectory()) { + await pinPackage(path.join(parentDir, dirent.name)); + } + } + } catch (e) { + // Ignore if directory doesn't exist + } + } else { + await pinPackage(path.join(targetDir, workspace)); + } + } + } +} + async function getTitle() { // Check if we are in interactive mode if (process.env.CI) {