tailjng 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -44,7 +44,7 @@ El CLI instala **dependencias transitivas** automáticamente (ej. `button` → `
44
44
  - Angular **19.2+** (19.x dentro del mismo major; no Angular 20/21 aún)
45
45
  - Tailwind CSS **4.x** (incluido si eliges Tailwind al crear el proyecto con `ng new`)
46
46
  -{
47
- - Peers: `lucide-angular`, `date-fns`, `exceljs`, `xlsx`, `file-saver` (el CLI los instala con `init:app` si faltan)
47
+ - Peers: `lucide-angular`, `date-fns`, `exceljs`, `file-saver` (el CLI los instala con `init:app` si faltan)
48
48
  - Estilos globales en **CSS o SCSS** — `init:app` detecta cuál usa tu app
49
49
 
50
50
  ---
@@ -134,24 +134,46 @@ import { JAlertToastComponent } from './tailjng/alert/alert-toast/toast-alert.co
134
134
  <JButton [text]="'Guardar'" classes="primary" [icon]="icons.save" (clicked)="save()" />
135
135
  ```
136
136
 
137
- ### Colores — `JColorsService` + safelist Tailwind
137
+ ### Colores — `src/app/tailjng/colors/`
138
138
 
139
- Las variantes (`primary`, `success_soft`, …) las resuelve **`JColorsService`** en runtime. Tailwind v4 **no ve** esas clases en el HTML estático, así que el paquete incluye `node_modules/tailjng/src/colors.safelist.css` con `@source inline(...)`.
139
+ Las variantes (`primary`, `success_soft`, …) las resuelve **`JColorsService`**. Tailwind v4 no genera clases solo en runtime, así que cada proyecto tiene una carpeta editable:
140
140
 
141
- `init:app` importa ese archivo en tu `styles.css` / `styles.scss` automáticamente.
141
+ | Archivo | Para qué |
142
+ |---------|----------|
143
+ | `colors.config.ts` | Lista de variantes (incluye las 77 por defecto) + las tuyas (`brand`, etc.) |
144
+ | `colors.safelist.css` | Clases Tailwind que el build debe generar (`@apply`) |
142
145
 
143
- **Variantes personalizadas del proyecto:**
146
+ **`init:app`** y **`sync:app`** crean esa carpeta (sin sobrescribir si ya existe), importan `colors.safelist.css` en `styles.css` y registran `tailjngColorsProvider`.
147
+
148
+ Si falta la carpeta en un proyecto existente:
144
149
 
145
150
  ```powershell
146
- npx tailjng add colors-config
151
+ npx tailjng sync:app
152
+ # o solo los archivos:
153
+ npx tailjng add colors
147
154
  ```
148
155
 
149
- Copia `src/app/tailjng/colors/` con:
156
+ **Añadir un color propio** — edita `colors.config.ts` y registra las clases nuevas en el bloque `.__tailjng_custom_colors__` de `colors.safelist.css`:
150
157
 
151
- - `colors.config.ts` — define variantes extra (`TAILJNG_COLORS_CONFIG`)
152
- - `colors.safelist.css` — añade tus clases Tailwind al `@source inline` para que el build las genere
158
+ ```typescript
159
+ // colors.config.ts
160
+ brand: 'bg-indigo-600 text-white hover:bg-indigo-700 border border-indigo-500 shadow-md',
161
+ ```
162
+
163
+ ```css
164
+ /* colors.safelist.css */
165
+ .__tailjng_custom_colors__ {
166
+ @apply bg-indigo-600 text-white hover:bg-indigo-700 border-indigo-500;
167
+ }
168
+ ```
169
+
170
+ Uso en componentes:
171
+
172
+ ```html
173
+ <JButton classes="brand" text="Guardar" />
174
+ ```
153
175
 
154
- Registra el provider en `app.config.ts`:
176
+ `init:app` / `sync:app` registran el provider automáticamente. Manualmente en `app.config.ts`:
155
177
 
156
178
  ```typescript
157
179
  import { tailjngColorsProvider } from './tailjng/colors/colors.config';
@@ -204,6 +226,7 @@ Pasa `endpoint`, `type="searchable"` o `loadOnInit` según el componente; usa `J
204
226
  | `npx tailjng init:app` | Prepara proyecto: Tailwind, deps, providers, estilos |
205
227
  | `npx tailjng init:app --yes` | Sin prompts interactivos |
206
228
  | `npx tailjng init:app --with-components` | init + mode-toggle y alerts |
229
+ | `npx tailjng sync:app` | Tras `npm install tailjng@latest` — solo lo nuevo (estilos, deps, angular.json) |
207
230
  | `npx tailjng add <nombre>` | Instala componente + dependencias |
208
231
  | `npx tailjng install-all` | Instala los 35 componentes |
209
232
  | `npx tailjng list` | Lista registry e indica cuáles ya están instalados |
@@ -1,11 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
4
3
  const readline = require('readline');
5
4
  const { COLORS } = require('../settings/colors');
6
5
  const { addComponent } = require('../component-manager');
7
6
  const { getComponentList } = require('../settings/components-list');
8
7
  const { buildInitFiles } = require('../templates/app.generator');
8
+ const { RUNTIME_PACKAGES, DEV_PACKAGES, installPackages } = require('../settings/init-packages');
9
9
  const {
10
10
  findAngularWorkspace,
11
11
  readJson,
@@ -29,22 +29,10 @@ const {
29
29
  resolveStyleFilePath,
30
30
  fileExists,
31
31
  } = require('../settings/project-utils');
32
-
33
- const RUNTIME_PACKAGES = {
34
- 'lucide-angular': '^0.525.0',
35
- '@ng-icons/lucide': '>=32.0.0',
36
- 'date-fns': '^4.1.0',
37
- 'exceljs': '^4.4.0',
38
- 'xlsx': '^0.18.5',
39
- 'file-saver': '^2.0.5',
40
- };
41
-
42
- const DEV_PACKAGES = {
43
- tailwindcss: '^4.0.9',
44
- '@tailwindcss/postcss': '^4.0.9',
45
- postcss: '^8.5.3',
46
- autoprefixer: '^10.4.20',
47
- };
32
+ const {
33
+ ensureProjectColorsConfig,
34
+ patchAppConfigColors,
35
+ } = require('../settings/colors-config-utils');
48
36
 
49
37
  /** Solo si pasas --with-components (opcional; no es el flujo normal). */
50
38
  const OPTIONAL_BASE_COMPONENTS = ['mode-toggle', 'alert-dialog', 'alert-toast'];
@@ -81,25 +69,6 @@ function askYesNo(question, defaultYes = true) {
81
69
  });
82
70
  }
83
71
 
84
- function installPackages(workspaceRoot, packages, isDev = false) {
85
- const entries = Object.entries(packages);
86
- if (entries.length === 0) return;
87
-
88
- const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
89
- const flag = isDev ? '--save-dev' : '--save';
90
- console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
91
-
92
- const run = (extra = '') => {
93
- execSync(`npm install ${flag} ${extra} ${spec}`.replace(/\s+/g, ' ').trim(), {
94
- cwd: workspaceRoot,
95
- stdio: 'inherit',
96
- shell: true,
97
- });
98
- };
99
-
100
- run('--legacy-peer-deps');
101
- }
102
-
103
72
  function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
104
73
  const appRoot = path.join(workspaceRoot, appProject.root || '');
105
74
 
@@ -201,6 +170,9 @@ async function runInitApp() {
201
170
  installPackages(workspaceRoot, missingDev, true);
202
171
  }
203
172
 
173
+ const { appRoot } = resolveAppPaths(workspaceRoot, selectedApp);
174
+ ensureProjectColorsConfig(workspaceRoot, appRoot, overwrite);
175
+
204
176
  const hasAppConfig = fileExists(appConfigPath);
205
177
  const safelistImport = getTailjngSafelistCssImport(workspaceRoot, primaryStylePath, selectedApp);
206
178
  const primaryStyleFullPath = resolveStyleFilePath(workspaceRoot, primaryStylePath, selectedApp);
@@ -249,6 +221,11 @@ async function runInitApp() {
249
221
  if (patched.changed) {
250
222
  console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, appConfigPath)}${COLORS.reset}`);
251
223
  }
224
+
225
+ const colorsPatched = patchAppConfigColors(appConfigPath, srcRoot);
226
+ if (colorsPatched.changed) {
227
+ console.log(`${COLORS.green}✔ Linked tailjngColorsProvider from src/app/tailjng/colors/${COLORS.reset}`);
228
+ }
252
229
  }
253
230
 
254
231
  const indexPatched = patchIndexHtml(indexPath);
@@ -0,0 +1,192 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { COLORS } = require('../settings/colors');
4
+ const { RUNTIME_PACKAGES, DEV_PACKAGES, installPackages } = require('../settings/init-packages');
5
+ const { buildPathsJson, buildPathsReadme } = require('../templates/app.generator');
6
+ const {
7
+ findAngularWorkspace,
8
+ readJson,
9
+ getApplicationProjects,
10
+ resolveAppPaths,
11
+ getBuildOptions,
12
+ detectStyleLanguage,
13
+ getPrimaryStyleEntry,
14
+ writeFileSafe,
15
+ ensureTailjngStylesInAngularJson,
16
+ patchIndexHtml,
17
+ patchAppConfig,
18
+ getMissingPackages,
19
+ resolveRuntimePackages,
20
+ resolveDevPackages,
21
+ hasTailwindSetup,
22
+ getTailjngSafelistCssImport,
23
+ ensureTailjngSafelistImport,
24
+ alignAngularAnimationsInPackageJson,
25
+ resolveStyleFilePath,
26
+ fileExists,
27
+ } = require('../settings/project-utils');
28
+ const {
29
+ ensureProjectColorsConfig,
30
+ patchAppConfigColors,
31
+ COLORS_FILES,
32
+ } = require('../settings/colors-config-utils');
33
+
34
+ function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
35
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
36
+
37
+ if (appRootRelative) {
38
+ return path.join(appRoot, relativePath);
39
+ }
40
+
41
+ if (relativePath.startsWith('projects/')) {
42
+ return path.join(workspaceRoot, relativePath);
43
+ }
44
+
45
+ return path.join(workspaceRoot, appProject.sourceRoot, relativePath.replace(/^src\//, ''));
46
+ }
47
+
48
+ async function runSyncApp() {
49
+ const workspace = findAngularWorkspace(process.cwd());
50
+
51
+ if (!workspace) {
52
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: angular.json not found. Run this inside an Angular project.${COLORS.reset}`);
53
+ process.exit(1);
54
+ }
55
+
56
+ const { workspaceRoot, angularJsonPath } = workspace;
57
+ const angularJson = readJson(angularJsonPath);
58
+ const apps = getApplicationProjects(angularJson);
59
+
60
+ if (apps.length === 0) {
61
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: No application project found in angular.json.${COLORS.reset}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ const selectedApp = apps[0];
66
+ const { appRoot, srcRoot } = resolveAppPaths(workspaceRoot, selectedApp);
67
+ const buildOptions = getBuildOptions(angularJson, selectedApp.name);
68
+ const styleLanguage = detectStyleLanguage(buildOptions);
69
+ const primaryStylePath = getPrimaryStyleEntry(buildOptions);
70
+ const primaryStyleFullPath = resolveStyleFilePath(workspaceRoot, primaryStylePath, selectedApp);
71
+ const indexPath = path.join(workspaceRoot, buildOptions.index || path.join(selectedApp.sourceRoot, 'index.html'));
72
+ const appConfigPath = path.join(srcRoot, 'app', 'app.config.ts');
73
+ const packageJsonPath = path.join(workspaceRoot, 'package.json');
74
+ const packageJson = fileExists(packageJsonPath) ? readJson(packageJsonPath) : { dependencies: {}, devDependencies: {} };
75
+ const tailwindReady = hasTailwindSetup(workspaceRoot, packageJson, primaryStylePath);
76
+ const installDeps = !process.argv.includes('--no-install');
77
+ const componentsPath = 'src/app/tailjng';
78
+
79
+ console.log(`\n${COLORS.bright}${COLORS.blue}[tailjng CLI] sync:app${COLORS.reset}`);
80
+ console.log(`${COLORS.dim}Updates integration only — does not overwrite your config (environment, providers, styles).${COLORS.reset}`);
81
+ console.log(`${COLORS.dim}Workspace: ${workspaceRoot} | App: ${selectedApp.name}${COLORS.reset}\n`);
82
+
83
+ if (!packageJson.dependencies?.tailjng) {
84
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng is not in package.json. Run: npm install tailjng@latest --legacy-peer-deps${COLORS.reset}`);
85
+ }
86
+
87
+ let changes = 0;
88
+
89
+ const animationsAligned = alignAngularAnimationsInPackageJson(workspaceRoot);
90
+ if (animationsAligned.changed) {
91
+ changes += 1;
92
+ console.log(`${COLORS.green}✔ Aligned @angular/animations → ${animationsAligned.version}${COLORS.reset}`);
93
+ Object.assign(packageJson, readJson(packageJsonPath));
94
+ }
95
+
96
+ if (installDeps) {
97
+ const runtimePackages = resolveRuntimePackages(packageJson, RUNTIME_PACKAGES, workspaceRoot);
98
+ const devPackages = resolveDevPackages(packageJson, DEV_PACKAGES, tailwindReady);
99
+ const missingRuntime = Object.fromEntries(getMissingPackages(packageJson, runtimePackages, workspaceRoot));
100
+ const missingDev = Object.fromEntries(getMissingPackages(packageJson, devPackages, workspaceRoot));
101
+
102
+ if (Object.keys(missingRuntime).length > 0) {
103
+ installPackages(workspaceRoot, missingRuntime, false);
104
+ changes += 1;
105
+ console.log(`${COLORS.green}✔ Installed missing runtime packages${COLORS.reset}`);
106
+ }
107
+
108
+ if (Object.keys(missingDev).length > 0) {
109
+ installPackages(workspaceRoot, missingDev, true);
110
+ changes += 1;
111
+ console.log(`${COLORS.green}✔ Installed missing dev packages${COLORS.reset}`);
112
+ }
113
+
114
+ if (Object.keys(missingRuntime).length === 0 && Object.keys(missingDev).length === 0) {
115
+ console.log(`${COLORS.dim}↷ Dependencies already satisfied${COLORS.reset}`);
116
+ }
117
+ } else {
118
+ console.log(`${COLORS.dim}↷ Skipped dependency install (--no-install)${COLORS.reset}`);
119
+ }
120
+
121
+ const pathsJsonPath = resolveTargetPath(workspaceRoot, selectedApp, '.tailjng/paths.json', true);
122
+ if (writeFileSafe(pathsJsonPath, buildPathsJson(componentsPath), false)) {
123
+ changes += 1;
124
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, pathsJsonPath)}${COLORS.reset}`);
125
+ }
126
+
127
+ const pathsReadmePath = resolveTargetPath(workspaceRoot, selectedApp, '.tailjng/README.md', true);
128
+ if (writeFileSafe(pathsReadmePath, buildPathsReadme(), false)) {
129
+ changes += 1;
130
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, pathsReadmePath)}${COLORS.reset}`);
131
+ }
132
+
133
+ const colorsResult = ensureProjectColorsConfig(workspaceRoot, appRoot, false);
134
+ if (colorsResult.created > 0) {
135
+ changes += colorsResult.created;
136
+ } else if (colorsResult.skipped === COLORS_FILES.length) {
137
+ console.log(`${COLORS.dim}↷ Project colors config already present${COLORS.reset}`);
138
+ }
139
+
140
+ const safelistImport = getTailjngSafelistCssImport(workspaceRoot, primaryStylePath, selectedApp);
141
+ const safelistPatched = ensureTailjngSafelistImport(primaryStyleFullPath, safelistImport);
142
+ if (safelistPatched.changed) {
143
+ changes += 1;
144
+ const label = safelistPatched.migrated
145
+ ? 'Migrated colors safelist to project folder'
146
+ : 'Added tailjng colors safelist';
147
+ console.log(`${COLORS.green}✔ ${label} → ${path.relative(workspaceRoot, primaryStyleFullPath)}${COLORS.reset}`);
148
+ } else {
149
+ console.log(`${COLORS.dim}↷ Colors safelist already in styles${COLORS.reset}`);
150
+ }
151
+
152
+ const stylesPatched = ensureTailjngStylesInAngularJson(angularJsonPath, selectedApp.name, styleLanguage);
153
+ if (stylesPatched.changed) {
154
+ changes += 1;
155
+ console.log(`${COLORS.green}✔ Added node_modules/tailjng/src/styles.css to angular.json${COLORS.reset}`);
156
+ } else {
157
+ console.log(`${COLORS.dim}↷ tailjng styles already in angular.json${COLORS.reset}`);
158
+ }
159
+
160
+ if (fileExists(appConfigPath)) {
161
+ const patched = patchAppConfig(appConfigPath);
162
+ if (patched.changed) {
163
+ changes += 1;
164
+ console.log(`${COLORS.green}✔ Patched app.config.ts (added tailjngProviders)${COLORS.reset}`);
165
+ }
166
+
167
+ const colorsPatched = patchAppConfigColors(appConfigPath, srcRoot);
168
+ if (colorsPatched.changed) {
169
+ changes += 1;
170
+ console.log(`${COLORS.green}✔ Linked tailjngColorsProvider from src/app/tailjng/colors/${COLORS.reset}`);
171
+ }
172
+ }
173
+
174
+ if (patchIndexHtml(indexPath)) {
175
+ changes += 1;
176
+ console.log(`${COLORS.green}✔ Patched index.html (body classes)${COLORS.reset}`);
177
+ }
178
+
179
+ console.log(`\n${COLORS.greenBright}${COLORS.bright}[tailjng CLI] sync:app completed.${COLORS.reset}`);
180
+ console.log(`${COLORS.dim}Changes applied: ${changes}${COLORS.reset}`);
181
+
182
+ if (changes === 0) {
183
+ console.log(`${COLORS.cyan}Project already up to date with this tailjng version.${COLORS.reset}`);
184
+ }
185
+
186
+ console.log(`\n${COLORS.dim}Tip: update UI components with ${COLORS.cyan}npx tailjng add <name>${COLORS.reset}${COLORS.dim} (asks before overwrite).${COLORS.reset}\n`);
187
+ }
188
+
189
+ runSyncApp().catch((error) => {
190
+ console.error(`${COLORS.red}[tailjng CLI] sync:app failed:${COLORS.reset}`, error);
191
+ process.exit(1);
192
+ });
package/cli/index.js CHANGED
@@ -18,6 +18,11 @@ async function main() {
18
18
  return
19
19
  }
20
20
 
21
+ if (command === "sync:app") {
22
+ require("./execute/sync-app")
23
+ return
24
+ }
25
+
21
26
  if (command === "add") {
22
27
  const componentName = args[1]
23
28
  if (!componentName) {
@@ -53,8 +58,10 @@ function showHelp() {
53
58
  console.log(`${COLORS.bright}${COLORS.blue}tailjng CLI${COLORS.reset} v${getPackageVersion(__dirname)}
54
59
 
55
60
  ${COLORS.bright}Project setup${COLORS.reset}
56
- ${COLORS.cyan}npx tailjng init:app${COLORS.reset} Configure Tailwind, providers, styles (no UI)
61
+ ${COLORS.cyan}npx tailjng init:app${COLORS.reset} First-time setup (Tailwind, providers, styles)
57
62
  ${COLORS.cyan}npx tailjng init:app --with-components${COLORS.reset} init:app + mode-toggle and alerts
63
+ ${COLORS.cyan}npx tailjng sync:app${COLORS.reset} Update integration after npm upgrade (no overwrite)
64
+ ${COLORS.cyan}npx tailjng sync:app --no-install${COLORS.reset} sync:app without npm install
58
65
 
59
66
  ${COLORS.bright}Components${COLORS.reset} ${COLORS.dim}(run from your Angular app directory)${COLORS.reset}
60
67
  ${COLORS.cyan}npx tailjng add${COLORS.reset} ${COLORS.dim}<name>${COLORS.reset} Install one component + dependencies
@@ -0,0 +1,188 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { COLORS } = require('./colors');
4
+ const { writeFileSafe } = require('./project-utils');
5
+
6
+ const COLORS_SOURCE_REL = 'src/lib/components/colors-config';
7
+ const COLORS_FILES = ['colors.config.ts', 'colors.safelist.css', 'README.md'];
8
+
9
+ function getProjectColorsDir(appRoot) {
10
+ return path.join(appRoot, 'src', 'app', 'tailjng', 'colors');
11
+ }
12
+
13
+ function resolveTailjngPackageRoot(workspaceRoot) {
14
+ const fromNodeModules = path.join(workspaceRoot, 'node_modules', 'tailjng', COLORS_SOURCE_REL);
15
+ if (fs.existsSync(fromNodeModules)) {
16
+ return path.join(workspaceRoot, 'node_modules', 'tailjng');
17
+ }
18
+
19
+ const fromMonorepo = path.join(workspaceRoot, 'projects', 'tailjng', COLORS_SOURCE_REL);
20
+ if (fs.existsSync(fromMonorepo)) {
21
+ return path.join(workspaceRoot, 'projects', 'tailjng');
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ function getColorsSafelistImportLine(workspaceRoot, styleEntry, appProject) {
28
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
29
+ const colorsSafelist = path.join(getProjectColorsDir(appRoot), 'colors.safelist.css');
30
+
31
+ let stylePath;
32
+ if (path.isAbsolute(styleEntry)) {
33
+ stylePath = styleEntry;
34
+ } else if (styleEntry.startsWith('projects/')) {
35
+ stylePath = path.join(workspaceRoot, styleEntry);
36
+ } else {
37
+ stylePath = path.join(appRoot, styleEntry);
38
+ }
39
+
40
+ const targetSafelist = fs.existsSync(colorsSafelist)
41
+ ? colorsSafelist
42
+ : path.join(workspaceRoot, 'node_modules', 'tailjng', 'src', 'colors.safelist.css');
43
+
44
+ let rel = path.relative(path.dirname(stylePath), targetSafelist).split(path.sep).join('/');
45
+ if (!rel.startsWith('.')) rel = `./${rel}`;
46
+ return `@import "${rel}";`;
47
+ }
48
+
49
+ function ensureProjectColorsConfig(workspaceRoot, appRoot, overwrite = false) {
50
+ const packageRoot = resolveTailjngPackageRoot(workspaceRoot);
51
+ if (!packageRoot) {
52
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng package not found — skip colors config copy${COLORS.reset}`);
53
+ return { created: 0, skipped: 0, colorsDir: getProjectColorsDir(appRoot) };
54
+ }
55
+
56
+ const sourceDir = path.join(packageRoot, COLORS_SOURCE_REL);
57
+ const colorsDir = getProjectColorsDir(appRoot);
58
+ let created = 0;
59
+ let skipped = 0;
60
+
61
+ for (const file of COLORS_FILES) {
62
+ const src = path.join(sourceDir, file);
63
+ const dest = path.join(colorsDir, file);
64
+ if (!fs.existsSync(src)) continue;
65
+
66
+ const content = fs.readFileSync(src, 'utf8');
67
+ if (writeFileSafe(dest, content, overwrite)) {
68
+ created += 1;
69
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, dest)}${COLORS.reset}`);
70
+ } else {
71
+ skipped += 1;
72
+ console.log(`${COLORS.dim}↷ Skipped (exists) ${path.relative(workspaceRoot, dest)}${COLORS.reset}`);
73
+ }
74
+ }
75
+
76
+ return { created, skipped, colorsDir };
77
+ }
78
+
79
+ function ensureColorsSafelistImport(styleFilePath, importLine) {
80
+ if (!fs.existsSync(styleFilePath)) return { changed: false };
81
+
82
+ let content = fs.readFileSync(styleFilePath, 'utf8');
83
+ const projectImport = /tailjng\/colors\/colors\.safelist\.css/;
84
+ const npmImport = /node_modules\/tailjng\/src\/colors\.safelist\.css/;
85
+ const anySafelistImport = /@import\s+"[^"]*colors\.safelist\.css";?/;
86
+
87
+ if (projectImport.test(content)) {
88
+ return { changed: false };
89
+ }
90
+
91
+ if (npmImport.test(content) && projectImport.test(importLine)) {
92
+ content = content.replace(anySafelistImport, importLine);
93
+ fs.writeFileSync(styleFilePath, content, 'utf8');
94
+ return { changed: true, migrated: true };
95
+ }
96
+
97
+ if (content.includes('colors.safelist.css')) {
98
+ return { changed: false };
99
+ }
100
+
101
+ const tailwindMatch = content.match(/@(?:import|use)\s+"tailwindcss";?/);
102
+ if (tailwindMatch) {
103
+ const insertAt = tailwindMatch.index + tailwindMatch[0].length;
104
+ content = `${content.slice(0, insertAt)}\n${importLine}\n${content.slice(insertAt)}`;
105
+ } else {
106
+ content = `${importLine}\n${content}`;
107
+ }
108
+
109
+ fs.writeFileSync(styleFilePath, content, 'utf8');
110
+ return { changed: true };
111
+ }
112
+
113
+ function patchTailjngProvidersColors(providersPath) {
114
+ if (!fs.existsSync(providersPath)) return { changed: false };
115
+
116
+ let content = fs.readFileSync(providersPath, 'utf8');
117
+ if (content.includes('tailjngColorsProvider') || content.includes('../tailjng/colors/colors.config')) {
118
+ return { changed: false };
119
+ }
120
+
121
+ const lines = content.split('\n');
122
+ let lastImport = -1;
123
+ for (let i = 0; i < lines.length; i += 1) {
124
+ if (lines[i].startsWith('import ')) lastImport = i;
125
+ }
126
+
127
+ const importLine = "import { tailjngColorsProvider } from '../tailjng/colors/colors.config';";
128
+ if (lastImport === -1) {
129
+ content = `${importLine}\n${content}`;
130
+ } else {
131
+ lines.splice(lastImport + 1, 0, importLine);
132
+ content = lines.join('\n');
133
+ }
134
+
135
+ if (content.includes('export const tailjngProviders = [')) {
136
+ content = content.replace(
137
+ /export const tailjngProviders = \[/,
138
+ 'export const tailjngProviders = [\n tailjngColorsProvider,',
139
+ );
140
+ }
141
+
142
+ fs.writeFileSync(providersPath, content, 'utf8');
143
+ return { changed: true };
144
+ }
145
+
146
+ function patchAppConfigColors(appConfigPath, srcRoot) {
147
+ const providersPath = path.join(srcRoot, 'app', 'config', 'tailjng.providers.ts');
148
+ if (fs.existsSync(providersPath)) {
149
+ return patchTailjngProvidersColors(providersPath);
150
+ }
151
+
152
+ if (!fs.existsSync(appConfigPath)) return { changed: false };
153
+
154
+ let content = fs.readFileSync(appConfigPath, 'utf8');
155
+ if (content.includes('tailjngColorsProvider') || content.includes('./tailjng/colors/colors.config')) {
156
+ return { changed: false };
157
+ }
158
+
159
+ const lines = content.split('\n');
160
+ let lastImport = -1;
161
+ for (let i = 0; i < lines.length; i += 1) {
162
+ if (lines[i].startsWith('import ')) lastImport = i;
163
+ }
164
+
165
+ const importLine = "import { tailjngColorsProvider } from './tailjng/colors/colors.config';";
166
+ if (lastImport === -1) {
167
+ content = `${importLine}\n${content}`;
168
+ } else {
169
+ lines.splice(lastImport + 1, 0, importLine);
170
+ content = lines.join('\n');
171
+ }
172
+
173
+ if (content.includes('providers: [')) {
174
+ content = content.replace(/providers:\s*\[/, 'providers: [\n tailjngColorsProvider,');
175
+ }
176
+
177
+ fs.writeFileSync(appConfigPath, content, 'utf8');
178
+ return { changed: true };
179
+ }
180
+
181
+ module.exports = {
182
+ getProjectColorsDir,
183
+ getColorsSafelistImportLine,
184
+ ensureProjectColorsConfig,
185
+ ensureColorsSafelistImport,
186
+ patchAppConfigColors,
187
+ COLORS_FILES,
188
+ };
@@ -0,0 +1,37 @@
1
+ const { execSync } = require('child_process');
2
+ const { COLORS } = require('./colors');
3
+
4
+ const RUNTIME_PACKAGES = {
5
+ 'lucide-angular': '^0.577.0',
6
+ 'date-fns': '^4.1.0',
7
+ 'exceljs': '^4.4.0',
8
+ 'file-saver': '^2.0.5',
9
+ };
10
+
11
+ const DEV_PACKAGES = {
12
+ tailwindcss: '^4.0.9',
13
+ '@tailwindcss/postcss': '^4.0.9',
14
+ postcss: '^8.5.3',
15
+ autoprefixer: '^10.4.20',
16
+ };
17
+
18
+ function installPackages(workspaceRoot, packages, isDev = false) {
19
+ const entries = Object.entries(packages);
20
+ if (entries.length === 0) return;
21
+
22
+ const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
23
+ const flag = isDev ? '--save-dev' : '--save';
24
+ console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
25
+
26
+ execSync(`npm install ${flag} --legacy-peer-deps ${spec}`.replace(/\s+/g, ' ').trim(), {
27
+ cwd: workspaceRoot,
28
+ stdio: 'inherit',
29
+ shell: true,
30
+ });
31
+ }
32
+
33
+ module.exports = {
34
+ RUNTIME_PACKAGES,
35
+ DEV_PACKAGES,
36
+ installPackages,
37
+ };
@@ -77,29 +77,13 @@ function resolveStyleFilePath(workspaceRoot, styleEntry, appProject) {
77
77
  }
78
78
 
79
79
  function getTailjngSafelistCssImport(workspaceRoot, styleEntry, appProject) {
80
- const stylePath = resolveStyleFilePath(workspaceRoot, styleEntry, appProject);
81
- const safelistPath = path.join(workspaceRoot, 'node_modules', 'tailjng', 'src', 'colors.safelist.css');
82
- let rel = path.relative(path.dirname(stylePath), safelistPath).split(path.sep).join('/');
83
- if (!rel.startsWith('.')) rel = `./${rel}`;
84
- return `@import "${rel}";`;
80
+ const { getColorsSafelistImportLine } = require('./colors-config-utils');
81
+ return getColorsSafelistImportLine(workspaceRoot, styleEntry, appProject);
85
82
  }
86
83
 
87
84
  function ensureTailjngSafelistImport(styleFilePath, importLine) {
88
- if (!fs.existsSync(styleFilePath)) return { changed: false };
89
-
90
- let content = fs.readFileSync(styleFilePath, 'utf8');
91
- if (content.includes('colors.safelist.css')) return { changed: false };
92
-
93
- const tailwindMatch = content.match(/@(?:import|use)\s+"tailwindcss";?/);
94
- if (tailwindMatch) {
95
- const insertAt = tailwindMatch.index + tailwindMatch[0].length;
96
- content = `${content.slice(0, insertAt)}\n${importLine}\n${content.slice(insertAt)}`;
97
- } else {
98
- content = `${importLine}\n${content}`;
99
- }
100
-
101
- fs.writeFileSync(styleFilePath, content, 'utf8');
102
- return { changed: true };
85
+ const { ensureColorsSafelistImport } = require('./colors-config-utils');
86
+ return ensureColorsSafelistImport(styleFilePath, importLine);
103
87
  }
104
88
 
105
89
  function ensureTailjngStylesInAngularJson(angularJsonPath, projectName, styleLanguage) {
@@ -380,4 +380,6 @@ function buildInitFiles(options) {
380
380
  module.exports = {
381
381
  buildInitFiles,
382
382
  buildAppConfigTs,
383
+ buildPathsJson,
384
+ buildPathsReadme,
383
385
  };