Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
110 changes: 97 additions & 13 deletions create/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
]);
Expand Down Expand Up @@ -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' });
}
Expand All @@ -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) {
Expand Down
9 changes: 1 addition & 8 deletions template/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
"@repo/eslint-config": "*",
"@repo/typescript-config": "*"
},
"peerDependencies": {
"msw": "^2.4.9"
},
"engines": {
"node": ">=22.0.0"
},
Expand All @@ -42,9 +39,5 @@
"*.{json,md}": [
"prettier --write"
]
},
"dependencies": {
"memoize": "^10.0.0",
"msw": "^2.4.9"
}
}
}
20 changes: 10 additions & 10 deletions template/config/eslint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
12 changes: 6 additions & 6 deletions template/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
20 changes: 10 additions & 10 deletions template/mock-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -59,4 +59,4 @@
"prettier --write"
]
}
}
}
18 changes: 9 additions & 9 deletions template/mock-data/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -61,4 +61,4 @@
"prettier --write"
]
}
}
}
32 changes: 16 additions & 16 deletions template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -48,7 +50,5 @@
"prototype",
"ui"
],
"dependencies": {
"concurrently": "^9.1.2"
}
}
"packageManager": "npm@11.4.1"
}
Loading