init
Some checks failed
Code Quality / lint-and-build (push) Failing after 29s
Release Packages / release (push) Failing after 41s

This commit is contained in:
2026-01-31 19:26:46 +01:00
commit 9a0900e3ff
42 changed files with 8346 additions and 0 deletions

26
packages/cli/README.md Normal file
View File

@@ -0,0 +1,26 @@
# @mintel/cli
CLI tool for managing the Mintel monorepo and scaffolding new client websites.
## Installation
```bash
pnpm install
```
## Commands
### `init <path>`
Initializes a new website project in the specified path (relative to the monorepo root).
```bash
pnpm --filter @mintel/cli start init apps/my-new-website
```
This command will:
1. Create the project directory.
2. Generate `package.json`, `tsconfig.json`, and `eslint.config.mjs` extending `@mintel` defaults.
3. Set up a localized Next.js structure (`src/app/[locale]`).
4. Configure `next-intl` middleware and request config.
5. Inject production-ready `Dockerfile`, `docker-compose.yml`, and Gitea Actions deployment workflows.

30
packages/cli/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "@mintel/cli",
"version": "1.0.0",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"
},
"type": "module",
"bin": {
"mintel": "./dist/index.js"
},
"scripts": {
"build": "tsup src/index.ts --format esm --target es2020",
"start": "node dist/index.js",
"dev": "tsup src/index.ts --format esm --watch --target es2020"
},
"dependencies": {
"commander": "^11.0.0",
"fs-extra": "^11.1.0",
"chalk": "^5.3.0",
"prompts": "^2.4.2"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0",
"@types/fs-extra": "^11.0.0",
"@types/prompts": "^2.4.4",
"@mintel/tsconfig": "workspace:*"
}
}

228
packages/cli/src/index.ts Normal file
View File

@@ -0,0 +1,228 @@
import { Command } from "commander";
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const program = new Command();
program
.name("mintel")
.description("CLI for Mintel monorepo management")
.version("1.0.0");
program
.command("init <path>")
.description("Initialize a new website project")
.action(async (projectPath) => {
const fullPath = path.isAbsolute(projectPath)
? projectPath
: path.resolve(process.cwd(), "../../", projectPath);
const projectName = path.basename(fullPath);
console.log(chalk.blue(`Initializing new project: ${projectName}...`));
try {
// Create directory
await fs.ensureDir(fullPath);
// Create package.json
const pkgJson = {
name: projectName,
version: "0.1.0",
private: true,
type: "module",
scripts: {
dev: "next dev",
build: "next build",
start: "next start",
lint: "next lint",
},
dependencies: {
next: "15.1.6",
react: "^19.0.0",
"react-dom": "^19.0.0",
"@mintel/next-utils": "workspace:*",
},
devDependencies: {
"@types/node": "^20.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
typescript: "^5.0.0",
"@mintel/tsconfig": "workspace:*",
"@mintel/eslint-config": "workspace:*",
"@mintel/next-config": "workspace:*",
},
};
await fs.writeJson(path.join(fullPath, "package.json"), pkgJson, {
spaces: 2,
});
// Create next.config.ts
const nextConfig = `import mintelNextConfig from "@mintel/next-config";
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default mintelNextConfig(nextConfig);
`;
await fs.writeFile(path.join(fullPath, "next.config.ts"), nextConfig);
// Create tsconfig.json
const tsConfig = {
extends: "@mintel/tsconfig/nextjs.json",
compilerOptions: {
paths: {
"@/*": ["./src/*"],
},
},
include: [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
],
exclude: ["node_modules"],
};
await fs.writeJson(path.join(fullPath, "tsconfig.json"), tsConfig, {
spaces: 2,
});
// Create eslint.config.mjs
const eslintConfig = `import { nextConfig } from "@mintel/eslint-config/next";
export default nextConfig;
`;
await fs.writeFile(
path.join(fullPath, "eslint.config.mjs"),
eslintConfig
);
// Create basic src structure
await fs.ensureDir(path.join(fullPath, "src/app/[locale]"));
await fs.writeFile(
path.join(fullPath, "src/middleware.ts"),
`import { createMintelMiddleware } from "@mintel/next-utils";
export default createMintelMiddleware({
locales: ["en", "de"],
defaultLocale: "en",
logRequests: true,
});
export const config = {
matcher: ["/((?!api|_next|_vercel|health|.*\\\\..*).*)", "/", "/(de|en)/:path*"]
};
`
);
// Create i18n/request.ts
await fs.ensureDir(path.join(fullPath, "src/i18n"));
await fs.writeFile(
path.join(fullPath, "src/i18n/request.ts"),
`import { createMintelI18nRequestConfig } from "@mintel/next-utils";
export default createMintelI18nRequestConfig(
["en", "de"],
"en",
(locale) => import(\`../../messages/\${locale}.json\`)
);
`
);
// Create messages directory
await fs.ensureDir(path.join(fullPath, "messages"));
await fs.writeJson(path.join(fullPath, "messages/en.json"), {
Index: {
title: "Welcome"
}
}, { spaces: 2 });
await fs.writeJson(path.join(fullPath, "messages/de.json"), {
Index: {
title: "Willkommen"
}
}, { spaces: 2 });
// Create instrumentation.ts
await fs.writeFile(
path.join(fullPath, "src/instrumentation.ts"),
`import * as Sentry from '@sentry/nextjs';
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// Server-side initialization
}
}
export const onRequestError = Sentry.captureRequestError;
`
);
await fs.writeFile(
path.join(fullPath, "src/app/[locale]/layout.tsx"),
`import type { Metadata } from "next";
export const metadata: Metadata = {
title: "${projectName}",
description: "Created with Mintel CLI",
};
export default function RootLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
`
);
await fs.writeFile(
path.join(fullPath, "src/app/[locale]/page.tsx"),
`export default function Home() {
return (
<main>
<h1>Welcome to ${projectName}</h1>
</main>
);
}
`
);
// Copy infra templates
const infraPath = path.resolve(__dirname, "../../infra");
if (await fs.pathExists(infraPath)) {
await fs.copy(
path.join(infraPath, "docker/Dockerfile.nextjs"),
path.join(fullPath, "Dockerfile")
);
await fs.copy(
path.join(infraPath, "docker/docker-compose.template.yml"),
path.join(fullPath, "docker-compose.yml")
);
await fs.ensureDir(path.join(fullPath, ".gitea/workflows"));
await fs.copy(
path.join(infraPath, "gitea/deploy-action.yml"),
path.join(fullPath, ".gitea/workflows/deploy.yml")
);
}
console.log(
chalk.green(`Successfully initialized ${projectName} at ${fullPath}`)
);
console.log(chalk.yellow("\nNext steps:"));
console.log(chalk.cyan("1. pnpm install"));
console.log(chalk.cyan(`2. cd ${projectPath} && pnpm dev`));
} catch (error) {
console.error(chalk.red("Error initializing project:"), error);
}
});
program.parse();

View File

@@ -0,0 +1,11 @@
{
"extends": "@mintel/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["esnext"]
},
"include": ["src"]
}