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.
- package/bin.mjs +191 -12
- 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 (
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
console.log(
|
|
339
|
-
|
|
340
|
-
|
|
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)')}
|