cs-init
Version:
341 lines (290 loc) • 11.8 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { execSync, spawnSync } = require('child_process');
const os = require('os');
const rootDir = process.env.INIT_CWD || process.cwd();
const isWindows = os.platform() === 'win32';
const packageJsonStr = fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8');
const packageJson = JSON.parse(packageJsonStr);
packageJson.scripts = {
"dev": "webpack serve",
"build": "webpack",
"dns": "node node_modules/cs-init/setup-local-dev.js",
"inlinehtml": "codeschmiede-inlinehtml",
};
// create package.json
const packageJsonPath = path.join(rootDir, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log('package.json created:', packageJsonPath);
} else {
console.log('package.json already exists:', packageJsonPath);
}
// create webpack.config.js
const webpackConfigPath = path.join(rootDir, 'webpack.config.js');
const webpackConfigContent = `
const fs = require('fs');
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
function readEnv() {
const envPath = path.join(__dirname, '.env');
if (!fs.existsSync(envPath)) return {};
return fs.readFileSync(envPath, 'utf8').split('\\n').reduce((acc, line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) return acc;
const eq = trimmed.indexOf('=');
if (eq === -1) return acc;
acc[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim();
return acc;
}, {});
}
const fileNames = fs.readdirSync('./src').reduce((acc, name) => {
return { ...acc, [name.replace('.scss', '')]: './src/' + name };
}, {});
module.exports = () => {
const isDev = !!process.env.WEBPACK_SERVE || !!process.env.npm_config_debug;
return {
mode: isDev ? 'development' : 'production',
entry: fileNames,
watch: isDev,
watchOptions: {
ignored: '**/node_modules',
},
output: {
filename: '[name]',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\\.s?css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
],
},
],
},
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: !isDev,
},
},
}),
new CssMinimizerPlugin(),
],
usedExports: true,
},
plugins: [
...(!isDev ? [
new MiniCssExtractPlugin({
filename: '[name].css',
})
] : []),
{
apply: (compiler) => {
compiler.hooks.done.tap('CleanDistPlugin', (stats) => {
if (isDev) return;
const distPath = path.resolve(compiler.options.output.path);
fs.readdir(distPath, (err, files) => {
if (err) throw err;
for (const file of files) {
if (file.indexOf('.') === -1) {
fs.unlink(path.join(distPath, file), (err) => {
if (err) throw err;
});
}
}
});
});
}
}
],
devServer: {
allowedHosts: "all",
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
},
static: [
{
directory: path.join(__dirname, 'assets'),
publicPath: '/assets',
},
{
directory: path.join(__dirname, 'dist'),
},
],
hot: false,
client: false,
compress: true,
port: 8080,
server: (() => {
if (!isDev) return {};
const env = readEnv();
const sslEnabled = env.CS_DEV_SSL === 'true';
const certsExist = fs.existsSync('./certs/server.key') && fs.existsSync('./certs/server.crt');
if (sslEnabled && certsExist) {
return {
type: 'https',
options: {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.crt'),
},
};
}
return { type: 'http' };
})()
}
}
};
`;
if (!fs.existsSync(webpackConfigPath)) {
fs.writeFileSync(webpackConfigPath, webpackConfigContent.trim());
console.log('webpack.config.js created:', webpackConfigPath);
} else {
console.log('webpack.config.js already exists:', webpackConfigPath);
}
// create certs dir (certs themselves are created by setup or the fallback below)
const certsPath = path.join(rootDir, 'certs');
const keyPath = path.join(certsPath, 'server.key');
const certPath = path.join(certsPath, 'server.crt');
const envPath = path.join(rootDir, '.env');
if (!fs.existsSync(certsPath)) {
fs.mkdirSync(certsPath);
}
// create src
const srcDir = path.join(rootDir, 'src');
if (!fs.existsSync(srcDir)) {
fs.mkdirSync(srcDir);
console.log('Directory created:', srcDir);
} else {
console.log('Directory already exists:', srcDir);
}
const variation01JsContent = `
import './variation-01.scss';
import { waitFor, qs } from 'codeschmiede-toolkit';
(() => {
console.log('V1');
waitFor('body', (body) => {
if(!body) return;
});
})();
`;
const srcScript = path.join(srcDir, 'variation-01.js');
if (!fs.existsSync(srcScript)) {
fs.writeFileSync(srcScript, variation01JsContent);
console.log('variation-01.js created:', srcScript);
} else {
console.log('variation-01.js already exists:', srcScript);
}
const srcStyle = path.join(srcDir, 'variation-01.scss');
if (!fs.existsSync(srcStyle)) {
fs.writeFileSync(srcStyle, '');
console.log('variation-01.scss created:', srcStyle);
} else {
console.log('variation-01.scss already exists:', srcStyle);
}
// create assets
const assetsDir = path.join(rootDir, 'assets');
if (!fs.existsSync(assetsDir)) {
fs.mkdirSync(assetsDir);
console.log('Directory created:', assetsDir);
} else {
console.log('Directory already exists:', assetsDir);
}
// ─── README ───────────────────────────────────────────────────────────────────
const readmePath = path.join(rootDir, 'README.md');
const readmeContent = `# AB-Test Setup
## Dev-Server starten
\`\`\`bash
npm run dev
\`\`\`
Erreichbar unter **https://localhost:8080** (HTTPS wird automatisch beim Setup eingerichtet).
## Scripts
| Command | Beschreibung |
|---|---|
| \`npm run dev\` | Startet den Webpack Dev-Server |
| \`npm run build\` | Produktions-Build nach \`dist/\` |
| \`npm run dns\` | Richtet eine Custom-Domain ein (DNS-Eintrag + Zertifikat) |
| \`npm run inlinehtml\` | Inlined JS/CSS in eine HTML-Datei |
## Custom-Domain einrichten (optional)
Mit \`npm run dns\` kannst du eine lokale Domain wie \`local.codeschmiede.de\` einrichten.
Das Script legt automatisch einen Eintrag in \`/etc/hosts\` an und erstellt ein HTTPS-Zertifikat.
> **macOS-Hinweis:** Verwende keine \`.local\`-Domain — diese werden über mDNS/Bonjour aufgelöst
> und können zu bis zu 30s Verzögerungen führen. Das Script warnt dich automatisch.
## Projektstruktur
\`\`\`
src/ → JS- und SCSS-Quelldateien (variation-01.js, variation-01.scss, ...)
dist/ → Compiled Output (wird von webpack generiert)
assets/ → Statische Dateien (werden unter /assets ausgeliefert)
certs/ → SSL-Zertifikate (automatisch generiert, nicht committen)
\`\`\`
## Neue Variation anlegen
Neue Dateien in \`src/\` werden automatisch von webpack als eigener Entry erkannt —
einfach \`variation-02.js\` anlegen und \`npm run dev\` neu starten.
`;
if (!fs.existsSync(readmePath)) {
fs.writeFileSync(readmePath, readmeContent);
console.log('README.md created:', readmePath);
}
// ─── Localhost HTTPS cert ─────────────────────────────────────────────────────
function commandExists(cmd) {
try {
const r = spawnSync(isWindows ? 'where' : 'which', [cmd], { stdio: 'pipe' });
return r.status === 0;
} catch { return false; }
}
function writeEnvFlag() {
let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
if (!/^CS_DEV_SSL=/m.test(content)) {
content = content ? `${content.trimEnd()}\nCS_DEV_SSL=true\n` : `CS_DEV_SSL=true\n`;
fs.writeFileSync(envPath, content);
}
}
function generateLocalhostCert() {
if (!fs.existsSync(certsPath)) fs.mkdirSync(certsPath, { recursive: true });
if (commandExists('mkcert')) {
spawnSync('mkcert', ['-install'], { stdio: 'pipe' });
const r = spawnSync('mkcert', ['-key-file', keyPath, '-cert-file', certPath, 'localhost', '127.0.0.1'], { stdio: 'pipe' });
if (r.status === 0) return 'mkcert';
}
if (commandExists('openssl')) {
try {
execSync(
`openssl req -x509 -newkey rsa:2048 -nodes -keyout "${keyPath}" -out "${certPath}" -days 3650 -subj "/CN=localhost/O=cs-init/C=DE" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"`,
{ stdio: 'pipe' }
);
return 'openssl';
} catch { return null; }
}
return null;
}
const certResult = generateLocalhostCert();
if (certResult) writeEnvFlag();
// ─── Done ─────────────────────────────────────────────────────────────────────
const protocol = certResult ? 'https' : 'http';
const certNote = certResult === 'mkcert' ? '(mkcert, trusted)'
: certResult === 'openssl' ? '(openssl, Browserwarnung möglich)'
: '(kein openssl/mkcert gefunden)';
console.log('\n---------------------------------------------');
console.log(' cs-init · Setup abgeschlossen');
console.log('---------------------------------------------');
console.log(` Dev-Server starten: npm run dev`);
console.log(` Erreichbar unter: ${protocol}://localhost:8080 ${certResult ? certNote : ''}`);
console.log('');
console.log(' Custom-Domain einrichten (optional):');
console.log(' DNS-Eintrag + Zertifikat: npm run dns');
console.log('---------------------------------------------\n');