guford-ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin.mjs +191 -12
  2. package/package.json +1 -1
package/bin.mjs CHANGED
@@ -25,6 +25,7 @@
25
25
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
26
26
  import { dirname, basename, join, resolve, relative } from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
+ import { execSync } from 'node:child_process';
28
29
 
29
30
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
31
  const ROOT = resolve(__dirname, '..'); // racine du dépôt guford-ui (mode local)
@@ -150,17 +151,141 @@ function loadProjectConfig(cwd) {
150
151
  return null;
151
152
  }
152
153
 
154
+ // --- auto-câblage du projet cible ------------------------------------------
155
+ /** Détecte la structure du projet Angular consommateur. */
156
+ function detectProject(cwd) {
157
+ const root = resolve(process.cwd(), cwd);
158
+ const out = { root, ngMajor: null, ngJsonPath: null, ngJson: null, projName: null, sourceRoot: 'src', stylesFile: null, appConfig: null, hasTailwind: false };
159
+
160
+ const pkgPath = join(root, 'package.json');
161
+ if (existsSync(pkgPath)) {
162
+ try {
163
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
164
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
165
+ const ver = deps['@angular/core'];
166
+ const m = ver && ver.match(/(\d+)/);
167
+ if (m) out.ngMajor = Number(m[1]);
168
+ out.hasTailwind = !!deps['tailwindcss'];
169
+ } catch {}
170
+ }
171
+
172
+ const ngJsonPath = join(root, 'angular.json');
173
+ if (existsSync(ngJsonPath)) {
174
+ try {
175
+ const ngJson = JSON.parse(readFileSync(ngJsonPath, 'utf8'));
176
+ out.ngJsonPath = ngJsonPath;
177
+ out.ngJson = ngJson;
178
+ for (const [name, proj] of Object.entries(ngJson.projects || {})) {
179
+ const build = proj.architect?.build || proj.targets?.build;
180
+ if (build) {
181
+ out.projName = name;
182
+ out.sourceRoot = proj.sourceRoot || 'src';
183
+ const styles = build.options?.styles || [];
184
+ out.stylesFile = styles.find((s) => typeof s === 'string' && /\.(scss|css)$/.test(s)) || `${out.sourceRoot}/styles.scss`;
185
+ break;
186
+ }
187
+ }
188
+ } catch {}
189
+ }
190
+ out.appConfig = join(root, out.sourceRoot, 'app', 'app.config.ts');
191
+ return out;
192
+ }
193
+
194
+ /** Versions CoreUI/CDK alignées sur la version d'Angular du projet. */
195
+ function coreuiVersions(ngMajor) {
196
+ const map = { 19: '^5.4', 20: '^5.5', 21: '^5.6', 22: '^5.6' };
197
+ const cu = map[ngMajor] || '^5.5';
198
+ const ng = ngMajor ? `^${ngMajor}.0.0` : '^20.0.0';
199
+ return {
200
+ list: [`@coreui/angular@${cu}`, '@coreui/coreui@~5.4', `@coreui/icons-angular@${cu}`, `@angular/cdk@${ng}`, `@angular/animations@${ng}`],
201
+ ng,
202
+ };
203
+ }
204
+
205
+ function runNpmInstall(root, pkgs, { dryRun, dev = false } = {}) {
206
+ if (!pkgs.length) return;
207
+ const cmd = `npm install ${dev ? '-D ' : ''}${pkgs.join(' ')} --legacy-peer-deps`;
208
+ console.log(` ${c.cyan('$')} ${cmd}`);
209
+ if (dryRun) return;
210
+ try {
211
+ execSync(cmd, { cwd: root, stdio: 'inherit' });
212
+ } catch {
213
+ console.log(c.yellow(` ⚠ npm install a échoué — lance-le manuellement : ${cmd}`));
214
+ }
215
+ }
216
+
217
+ /** Ajoute une ligne à un fichier si absente (création si besoin). */
218
+ function ensureLine(path, line, { dryRun, prepend = false } = {}) {
219
+ let content = existsSync(path) ? readFileSync(path, 'utf8') : '';
220
+ if (content.includes(line.trim())) return false;
221
+ content = prepend ? `${line}\n${content}` : `${content}${content.endsWith('\n') || content === '' ? '' : '\n'}${line}\n`;
222
+ if (!dryRun) {
223
+ mkdirSync(dirname(path), { recursive: true });
224
+ writeFileSync(path, content);
225
+ }
226
+ return true;
227
+ }
228
+
229
+ /** Crée/complète tailwind.config.js avec le preset partagé. */
230
+ function wireTailwind(root, dryRun) {
231
+ const path = join(root, 'tailwind.config.js');
232
+ if (!existsSync(path)) {
233
+ const content = `/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n presets: [require('./tailwind.preset.cjs')],\n content: ['./src/**/*.{html,ts}'],\n};\n`;
234
+ if (!dryRun) writeFileSync(path, content);
235
+ return 'créé';
236
+ }
237
+ let s = readFileSync(path, 'utf8');
238
+ if (s.includes('tailwind.preset.cjs')) return 'déjà présent';
239
+ const m = s.match(/(module\.exports\s*=\s*\{|export\s+default\s*\{)/);
240
+ if (!m) return 'à compléter à la main';
241
+ const idx = m.index + m[0].length;
242
+ s = `${s.slice(0, idx)}\n presets: [require('./tailwind.preset.cjs')],${s.slice(idx)}`;
243
+ if (!dryRun) writeFileSync(path, s);
244
+ return 'preset ajouté';
245
+ }
246
+
247
+ /** Ajoute le CSS CoreUI au tableau styles d'angular.json. */
248
+ function wireCoreuiCss(proj, dryRun) {
249
+ if (!proj.ngJson || !proj.projName) return 'angular.json introuvable';
250
+ const cssPath = 'node_modules/@coreui/coreui/dist/css/coreui.min.css';
251
+ const p = proj.ngJson.projects[proj.projName];
252
+ const build = p.architect?.build || p.targets?.build;
253
+ build.options = build.options || {};
254
+ build.options.styles = build.options.styles || [];
255
+ if (build.options.styles.includes(cssPath)) return 'déjà présent';
256
+ build.options.styles.unshift(cssPath);
257
+ if (!dryRun) writeFileSync(proj.ngJsonPath, JSON.stringify(proj.ngJson, null, 2) + '\n');
258
+ return 'ajouté';
259
+ }
260
+
261
+ /** Ajoute provideAnimations() dans app.config.ts. */
262
+ function wireProvideAnimations(appConfigPath, dryRun) {
263
+ if (!existsSync(appConfigPath)) return 'app.config.ts introuvable';
264
+ let s = readFileSync(appConfigPath, 'utf8');
265
+ if (s.includes('provideAnimations')) return 'déjà présent';
266
+ if (!s.includes('@angular/platform-browser/animations')) {
267
+ s = `import { provideAnimations } from '@angular/platform-browser/animations';\n${s}`;
268
+ }
269
+ const m = s.match(/providers\s*:\s*\[/);
270
+ if (!m) return 'providers[] introuvable';
271
+ const idx = m.index + m[0].length;
272
+ s = `${s.slice(0, idx)}\n provideAnimations(),${s.slice(idx)}`;
273
+ if (!dryRun) writeFileSync(appConfigPath, s);
274
+ return 'ajouté';
275
+ }
276
+
153
277
  // --- parseur d'arguments ---------------------------------------------------
154
278
  function parseArgs(argv) {
155
279
  const positionals = [];
156
280
  const flags = {
157
281
  cwd: '.', dir: undefined, overwrite: false, dryRun: false,
158
- registry: undefined, token: undefined, repo: undefined, ref: undefined,
282
+ registry: undefined, token: undefined, repo: undefined, ref: undefined, install: true,
159
283
  };
160
284
  for (let i = 0; i < argv.length; i++) {
161
285
  const a = argv[i];
162
286
  if (a === '--overwrite') flags.overwrite = true;
163
287
  else if (a === '--dry-run') flags.dryRun = true;
288
+ else if (a === '--no-install') flags.install = false;
164
289
  else if (a === '--cwd') flags.cwd = argv[++i];
165
290
  else if (a === '--dir') flags.dir = argv[++i];
166
291
  else if (a === '--registry') flags.registry = argv[++i];
@@ -261,14 +386,45 @@ async function cmdAdd(names, flags) {
261
386
  }
262
387
 
263
388
  console.log(log.join('\n'));
389
+
390
+ // --- Dépendances npm + câblage CoreUI automatiques ---
391
+ const proj = detectProject(flags.cwd);
392
+ const usesCoreui = npmDeps.has('@coreui/angular');
393
+
394
+ // Liste à installer : deps registry (hors @coreui/angular et @angular/cdk gérés à part) + peers CoreUI alignés
395
+ const installList = [...npmDeps].filter((d) => d !== '@coreui/angular' && d !== '@angular/cdk');
396
+ if (usesCoreui) {
397
+ const { list } = coreuiVersions(proj.ngMajor);
398
+ installList.push(...list);
399
+ console.log(
400
+ c.dim(
401
+ `\n CoreUI détecté → versions alignées sur Angular ${proj.ngMajor ?? '?'} ` +
402
+ `(${coreuiVersions(proj.ngMajor).list.join(' ')})`,
403
+ ),
404
+ );
405
+ } else if (npmDeps.has('@angular/cdk')) {
406
+ installList.push(`@angular/cdk${proj.ngMajor ? '@^' + proj.ngMajor + '.0.0' : ''}`);
407
+ }
408
+
264
409
  console.log('');
265
- if (npmDeps.size) {
266
- console.log(c.cyan(' Dépendances npm à installer dans le projet cible:'));
267
- console.log(` ${c.bold('npm install ' + [...npmDeps].join(' '))}\n`);
410
+ if (installList.length) {
411
+ if (flags.install) runNpmInstall(proj.root, installList, { dryRun: flags.dryRun });
412
+ else {
413
+ console.log(c.cyan(' Dépendances npm (à installer — sauté: --no-install):'));
414
+ console.log(` ${c.bold('npm install ' + installList.join(' '))}`);
415
+ }
268
416
  }
417
+
418
+ // Câblage CoreUI (CSS + animations) si nécessaire
419
+ if (usesCoreui) {
420
+ console.log(c.cyan('\n Câblage CoreUI:'));
421
+ console.log(` ${c.green('✓')} angular.json styles — ${wireCoreuiCss(proj, flags.dryRun)}`);
422
+ console.log(` ${c.green('✓')} app.config.ts provideAnimations() — ${wireProvideAnimations(proj.appConfig, flags.dryRun)}`);
423
+ }
424
+
269
425
  console.log(
270
426
  c.dim(
271
- ` ${written} fichier(s) ${flags.dryRun ? 'seraient écrits' : 'écrits'}` +
427
+ `\n ${written} fichier(s) ${flags.dryRun ? 'seraient écrits' : 'écrits'}` +
272
428
  (skipped ? `, ${skipped} ignoré(s)` : '') +
273
429
  '.\n',
274
430
  ),
@@ -332,12 +488,30 @@ async function cmdInit(flags) {
332
488
  }
333
489
 
334
490
  console.log(log.join('\n'));
335
- console.log(c.cyan('\n Étapes de câblage:'));
336
- console.log(` 1. Styles globaux ajoutez: ${c.bold(`@import './styles/guford-ui-tokens.css';`)}`);
337
- console.log(` ${c.dim(`(dans ${dir}/styles.scss ou ${dir}/styles.css)`)}`);
338
- console.log(` 2. tailwind.config.js — ajoutez: ${c.bold(`presets: [require('./tailwind.preset.cjs')]`)}`);
339
- console.log(` 3. Peer-deps de base: ${c.bold('npm install clsx tailwind-merge')}`);
340
- console.log(` ${c.dim('(certains composants requièrent aussi @coreui/angular voir `info <name>`)')}`);
491
+
492
+ // --- Câblage automatique du projet cible ---
493
+ const proj = detectProject(flags.cwd);
494
+ console.log(c.cyan('\n Câblage automatique:'));
495
+
496
+ // 1. Fichier de styles global : directives Tailwind (si absentes) + @import tokens
497
+ const stylesFile = proj.stylesFile ? join(proj.root, proj.stylesFile) : join(projectRoot, dir, 'styles.scss');
498
+ if (!existsSync(stylesFile) || !readFileSync(stylesFile, 'utf8').includes('@tailwind base')) {
499
+ ensureLine(stylesFile, '@tailwind base;\n@tailwind components;\n@tailwind utilities;', { dryRun: flags.dryRun, prepend: true });
500
+ }
501
+ const addedImport = ensureLine(stylesFile, `@import './styles/guford-ui-tokens.css';`, { dryRun: flags.dryRun });
502
+ console.log(` ${addedImport ? c.green('✓') : c.dim('•')} tokens importés → ${relative(process.cwd(), stylesFile)}`);
503
+
504
+ // 2. tailwind.config.js (preset partagé)
505
+ console.log(` ${c.green('✓')} tailwind.config.js — ${wireTailwind(proj.root, flags.dryRun)}`);
506
+
507
+ // 3. Peer-deps de base (+ Tailwind si absent)
508
+ if (flags.install) {
509
+ runNpmInstall(proj.root, ['clsx', 'tailwind-merge'], { dryRun: flags.dryRun });
510
+ if (!proj.hasTailwind) runNpmInstall(proj.root, ['tailwindcss@3', 'postcss', 'autoprefixer'], { dryRun: flags.dryRun, dev: true });
511
+ } else {
512
+ console.log(c.dim(' • npm install clsx tailwind-merge' + (proj.hasTailwind ? '' : ' (+ -D tailwindcss@3 postcss autoprefixer)') + ' (sauté: --no-install)'));
513
+ }
514
+
341
515
  console.log(
342
516
  c.dim(
343
517
  `\n ${written} fichier(s) ${flags.dryRun ? 'seraient écrits' : 'écrits'}` +
@@ -364,8 +538,13 @@ function help() {
364
538
  --repo <owner/repo> Dépôt GitHub (défaut: ${DEFAULT_REPO})
365
539
  --ref <branch|tag> Réf GitHub (défaut: ${DEFAULT_REF}) — pin sur un tag pour reproductibilité
366
540
  --token <token> Token GitHub pour dépôt privé (sinon $GUFORD_UI_TOKEN / $GITHUB_TOKEN)
541
+ --no-install Ne lance pas npm install (affiche les commandes)
367
542
  --overwrite Écrase les fichiers existants
368
- --dry-run Simule sans écrire
543
+ --dry-run Simule sans écrire ni installer
544
+
545
+ ${c.cyan('Auto-câblage:')} init/add modifient le projet cible (npm install avec
546
+ CoreUI aligné sur votre Angular, @import des tokens, preset Tailwind,
547
+ CSS CoreUI dans angular.json, provideAnimations dans app.config.ts).
369
548
 
370
549
  ${c.cyan('Dépôt privé:')} crée un PAT GitHub (Contents: read), puis
371
550
  ${c.dim('set GUFORD_UI_TOKEN=<token> # Windows (cmd) | $env:GUFORD_UI_TOKEN="<token>" (PowerShell)')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guford-ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI pour installer les composants du registry guford-ui (façon shadcn).",
5
5
  "type": "module",
6
6
  "bin": {