init
This commit is contained in:
26
packages/cli/README.md
Normal file
26
packages/cli/README.md
Normal 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
30
packages/cli/package.json
Normal 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
228
packages/cli/src/index.ts
Normal 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();
|
||||
11
packages/cli/tsconfig.json
Normal file
11
packages/cli/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@mintel/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["esnext"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user