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 +33 -10
- package/cli/execute/init-app.js +13 -36
- package/cli/execute/sync-app.js +192 -0
- package/cli/index.js +8 -1
- package/cli/settings/colors-config-utils.js +188 -0
- package/cli/settings/init-packages.js +37 -0
- package/cli/settings/project-utils.js +4 -20
- package/cli/templates/app.generator.js +2 -0
- package/fesm2022/tailjng.mjs +84 -46
- package/fesm2022/tailjng.mjs.map +1 -1
- package/lib/services/report/upload.filter.service.d.ts +3 -0
- package/package.json +2 -4
- package/registry/components.json +1 -1
- package/src/colors.safelist.css +28 -3
- package/src/lib/components/colors-config/README.md +38 -0
- package/src/lib/components/colors-config/colors.config.ts +101 -8
- package/src/lib/components/colors-config/colors.safelist.css +29 -7
- package/tailjng-0.1.5.tgz +0 -0
- package/src/lib/components/colors-config/colors-config.component.ts +0 -12
- package/tailjng-0.1.3.tgz +0 -0
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`, `
|
|
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 — `
|
|
137
|
+
### Colores — `src/app/tailjng/colors/`
|
|
138
138
|
|
|
139
|
-
Las variantes (`primary`, `success_soft`, …) las resuelve **`JColorsService
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
151
|
+
npx tailjng sync:app
|
|
152
|
+
# o solo los archivos:
|
|
153
|
+
npx tailjng add colors
|
|
147
154
|
```
|
|
148
155
|
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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 |
|
package/cli/execute/init-app.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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}
|
|
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
|
|
81
|
-
|
|
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
|
-
|
|
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) {
|