diff --git a/.gitignore b/.gitignore index 77c630a..a82c3ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.idea/* dist/* +/node_modules/* +/package-lock.json diff --git a/3-styles/ingeli-std/1-behaviors/style.behavior.css b/3-styles/ingeli-std/1-behaviors/style.behavior.css new file mode 100644 index 0000000..ec682b1 --- /dev/null +++ b/3-styles/ingeli-std/1-behaviors/style.behavior.css @@ -0,0 +1,7 @@ +.istd-sty-fill { + width: 100%; +} + +.istd-sty-fill > * { + width: 100%; +} diff --git a/4-clients/1-demo/client.config.json b/4-clients/1-demo/client.config.json new file mode 100644 index 0000000..5f2b373 --- /dev/null +++ b/4-clients/1-demo/client.config.json @@ -0,0 +1,13 @@ +{ + "name": "1-demo", + + "csharpRoot": "../../ingeli/ingelistd", + + "applications": [ + { + "name": "WebDemo", + "deployPath": "IngeliStdBlazorWebDemo", + "variants": ["blank", "ingeli"] + } + ] +} \ No newline at end of file diff --git a/5-build/build-css.js b/5-build/build-css.js index 391b989..c469c6d 100644 --- a/5-build/build-css.js +++ b/5-build/build-css.js @@ -1,12 +1,6 @@ import fs from "fs"; import path from "path"; - -function stripBOM(str) { - if (str.charCodeAt(0) === 0xFEFF) { - return str.slice(1); - } - return str; -} +import { stripBOM } from "./utils/fs-utils.js"; const [ , , clientName, variantName ] = process.argv; diff --git a/5-build/deploy-all.js b/5-build/deploy-all.js new file mode 100644 index 0000000..87aa1fc --- /dev/null +++ b/5-build/deploy-all.js @@ -0,0 +1,107 @@ +import fs from "fs"; +import path from "path"; +import { execSync } from "child_process"; +import { stripBOM } from "./utils/fs-utils.js"; + +const [, , clientName] = process.argv; + +if (!clientName) { + console.error("❌ Usage: node deploy-all.js "); + process.exit(1); +} + +const ROOT = process.cwd(); + +const CLIENT_DIR = path.join(ROOT, "4-clients", clientName); +const CONFIG_FILE = path.join(CLIENT_DIR, "client.config.json"); +const VARIANTS_DIR = path.join(CLIENT_DIR, "2-variants"); + +if (!fs.existsSync(CONFIG_FILE)) { + console.error(`❌ Config not found: ${CONFIG_FILE}`); + process.exit(1); +} + +const raw = fs.readFileSync(CONFIG_FILE, "utf8"); +const config = JSON.parse(stripBOM(raw)); + +console.log(`🚀 Deploy ALL for client: ${clientName}`); + +// -------------------------------------------------- +// Collect variants + validate existence +// -------------------------------------------------- +const variantsSet = new Set(); +const invalidVariants = new Set(); + +for (const app of config.applications) { + for (const variant of app.variants) { + const variantPath = path.join(VARIANTS_DIR, variant); + + if (!fs.existsSync(variantPath)) { + console.error( + `❌ Variant "${variant}" declared but not found:\n ${variantPath}` + ); + invalidVariants.add(variant); + } else { + variantsSet.add(variant); + } + } +} + +const validVariants = Array.from(variantsSet); + +if (validVariants.length === 0) { + console.error("\n🚨 No valid variants to deploy. Aborting."); + process.exit(1); +} + +if (invalidVariants.size > 0) { + console.warn( + `\n⚠️ ${invalidVariants.size} invalid variant(s) will be skipped` + ); +} + +// ================================================== +// PHASE 1 — BUILD (once per valid variant) +// ================================================== +console.log("\n🔨 Phase 1 — Build variants"); + +for (const variant of validVariants) { + console.log(`\n▶ build variant: ${variant}`); + execSync( + `node 5-build/build-css.js ${clientName} ${variant}`, + { stdio: "inherit" } + ); +} + +// ================================================== +// PHASE 2 — DEPLOY (application × variant) +// ================================================== +console.log("\n📤 Phase 2 — Deploy"); + +for (const app of config.applications) { + console.log(`\n📦 Application: ${app.name}`); + + for (const variant of app.variants) { + if (invalidVariants.has(variant)) { + console.warn(` ⏭️ skipped ${variant} (not found)`); + continue; + } + + console.log(` ▶ deploy ${app.name} / ${variant}`); + execSync( + `node 5-build/deploy.js ${clientName} ${app.name} ${variant}`, + { stdio: "inherit" } + ); + } +} + +// -------------------------------------------------- +if (invalidVariants.size > 0) { + + console.error( + `❌ Completed with ${invalidVariants.size} invalid variant(s)` + ); +} +else { + console.log("\n✅ Deploy ALL completed"); +} diff --git a/5-build/deploy-one.js b/5-build/deploy-one.js new file mode 100644 index 0000000..c8e2519 --- /dev/null +++ b/5-build/deploy-one.js @@ -0,0 +1,81 @@ +import fs from "fs"; +import path from "path"; +import { stripBOM } from "./utils/fs-utils.js"; + +const [ , , clientName, appName, variantName ] = process.argv; + +if (!clientName || !appName || !variantName) { + console.error("❌ Usage: node deploy.js "); + process.exit(1); +} + +const ROOT = process.cwd(); + +const CONFIG_FILE = path.join( + ROOT, + "4-clients", + clientName, + "client.config.json" +); + +if (!fs.existsSync(CONFIG_FILE)) { + console.error(`❌ Config not found: ${CONFIG_FILE}`); + process.exit(1); +} + +const rawConfig = fs.readFileSync(CONFIG_FILE, "utf8"); +const config = JSON.parse(stripBOM(rawConfig)); + +const app = config.applications.find(a => a.name === appName); + +if (!app) { + console.error(`❌ Application "${appName}" not found for client "${clientName}"`); + process.exit(1); +} + +if (!app.variants.includes(variantName)) { + console.error( + `❌ Variant "${variantName}" not defined for application "${appName}"` + ); + process.exit(1); +} + +const DIST_DIR = path.join( + ROOT, + "dist", + clientName, + variantName +); + +if (!fs.existsSync(DIST_DIR)) { + console.error(`❌ Build output not found: ${DIST_DIR}`); + process.exit(1); +} + +const TARGET_DIR = path.join( + ROOT, + config.csharpRoot, + app.deployPath, + "wwwroot", + "themes", + variantName +); + +console.log("📤 Deploy"); +console.log(` Client : ${clientName}`); +console.log(` Application : ${appName}`); +console.log(` Variant : ${variantName}`); +console.log(` From : ${DIST_DIR}`); +console.log(` To : ${TARGET_DIR}`); + +fs.mkdirSync(TARGET_DIR, { recursive: true }); + +for (const file of fs.readdirSync(DIST_DIR)) { + const src = path.join(DIST_DIR, file); + const dest = path.join(TARGET_DIR, file); + + fs.copyFileSync(src, dest); + console.log(` ✔ ${file}`); +} + +console.log("✅ Deploy finished"); diff --git a/5-build/live-dev.js b/5-build/live-dev.js new file mode 100644 index 0000000..40878c5 --- /dev/null +++ b/5-build/live-dev.js @@ -0,0 +1,51 @@ +import chokidar from "chokidar"; +import { execSync } from "child_process"; + +const [ , , clientName, appName, variantName ] = process.argv; + +if (!clientName || !appName || !variantName) { + console.error("❌ Usage: node watch.js "); + process.exit(1); +} + +console.log("👀 Watch mode"); +console.log(` Client : ${clientName}`); +console.log(` Application : ${appName}`); +console.log(` Variant : ${variantName}`); + +const WATCH_PATHS = [ + "2-tokens", + "3-styles", + `4-clients/${clientName}` +]; + +let timeout = null; + +function rebuild() { + console.log("\n🔄 Change detected"); + + try { + execSync( + `node 5-build/build-css.js ${clientName} ${variantName}`, + { stdio: "inherit" } + ); + + execSync( + `node 5-build/deploy.js ${clientName} ${appName} ${variantName}`, + { stdio: "inherit" } + ); + + console.log("✅ Rebuild + deploy done"); + } catch (err) { + console.error("❌ Error during rebuild/deploy"); + } +} + +const watcher = chokidar.watch(WATCH_PATHS, { + ignoreInitial: true +}); + +watcher.on("all", () => { + clearTimeout(timeout); + timeout = setTimeout(rebuild, 100); +}); diff --git a/5-build/utils/fs-utils.js b/5-build/utils/fs-utils.js new file mode 100644 index 0000000..0050174 --- /dev/null +++ b/5-build/utils/fs-utils.js @@ -0,0 +1,7 @@ +export function stripBOM(str) { + if (!str) return str; + if (str.charCodeAt(0) === 0xFEFF) { + return str.slice(1); + } + return str; +} \ No newline at end of file diff --git a/package.json b/package.json index 4cd12df..eeed56d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,12 @@ "node": ">=20.0.0" }, "scripts": { - "build": "node 5-build/build-css.js", - "build web demo": "node 5-build/build-css.js 1-demo blank" + "build web demo": "node 5-build/build-css.js 1-demo blank", + "deploy web demo": "node 5-build/deploy-one.js 1-demo WebDemo blank", + "watch web demo": "node 5-build/live-dev.js 1-demo WebDemo blank", + "deploy all demo": "node 5-build/deploy-all.js 1-demo" + }, + "devDependencies": { + "chokidar": "^3.6.0" } } \ No newline at end of file diff --git a/readme.md b/readme.md index 6b38904..bf61022 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,7 @@ │ ├── 4-clients/ │ ├── client-xxx/ +│ │ ├── client.config.json │ │ ├── 1-commons/ │ │ │ └── styles/ Même structure que dans le dossier 3-styles à la racine │ │ ├── app-xxx/ @@ -48,9 +49,10 @@ │ └── client-demo/ │ ├── build/ -│ ├── build-tokens.js │ ├── build-css.js -│ └── watch.js +│ ├── deploy-one.js +│ ├── deploy-all.js +│ └── live-dev.js │ ├── dist/ Fichiers à déployer dans le projet C# │ ├── client-xxx/