create-ng-tailwind 4.0.0 → 4.1.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/CHANGELOG.md +24 -0
- package/README.md +22 -4
- package/lib/cli/index.js +45 -0
- package/lib/managers/ProjectManager.js +2 -1
- package/lib/utils/nodeCompat.js +85 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.1.0] - 2026-06-06
|
|
9
|
+
|
|
10
|
+
### ✨ New Features
|
|
11
|
+
|
|
12
|
+
- **Node.js / Angular auto-compatibility**: The CLI now detects the current Node.js runtime and automatically picks the highest compatible Angular CLI release instead of always pulling `@angular/cli@latest`. This fixes the common `EBADENGINE` failure when scaffolding on a Node version below Angular 22's minimum.
|
|
13
|
+
- **`--ng-version <version>` flag**: New CLI flag to pin a specific Angular CLI major (e.g. `--ng-version=21`, `--ng-version=latest`). When omitted, the highest compatible version is selected automatically.
|
|
14
|
+
- **Friendly preflight error**: If no supported Angular version matches the current Node.js, the CLI exits early with a formatted message listing supported Node↔Angular combinations and upgrade hints — instead of forwarding npm's cryptic engine warnings.
|
|
15
|
+
|
|
16
|
+
### 📋 Supported Angular Versions
|
|
17
|
+
|
|
18
|
+
| Angular CLI | Required Node.js |
|
|
19
|
+
| ----------- | ---------------- |
|
|
20
|
+
| 22 (latest) | `^22.22.3 \|\| ^24.15.0 \|\| >=26.0.0` |
|
|
21
|
+
| 21 | `^20.19.0 \|\| ^22.12.0 \|\| >=24.0.0` |
|
|
22
|
+
| 20 | `^20.11.1 \|\| ^22.0.0 \|\| ^24.0.0` |
|
|
23
|
+
| 19 | `^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0` |
|
|
24
|
+
|
|
25
|
+
### 🏗️ Internal
|
|
26
|
+
|
|
27
|
+
- New module `lib/utils/nodeCompat.js` containing the compatibility table, a tiny range matcher, and the resolver.
|
|
28
|
+
- `ProjectManager` now reads `config.angularVersion` instead of hardcoding `@latest` in the scaffold command.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
8
32
|
## [4.0.0] - 2025-12-25
|
|
9
33
|
|
|
10
34
|
### ✨ New Template
|
package/README.md
CHANGED
|
@@ -128,16 +128,34 @@ npx create-ng-tailwind my-app --template=starter --ssr --zoneless
|
|
|
128
128
|
--template=<minimal|starter|dashboard>
|
|
129
129
|
|
|
130
130
|
# Other flags
|
|
131
|
-
--ssr
|
|
132
|
-
--zoneless
|
|
133
|
-
--ai-config
|
|
131
|
+
--ssr # Enable Server-Side Rendering
|
|
132
|
+
--zoneless # Create zoneless application
|
|
133
|
+
--ai-config # Configure AI tools (Claude, Cursor, etc.)
|
|
134
|
+
--ng-version=<v> # Pin Angular CLI version (latest|21|20|19).
|
|
135
|
+
# Default: auto-pick the highest version compatible
|
|
136
|
+
# with your current Node.js runtime.
|
|
134
137
|
```
|
|
135
138
|
|
|
136
139
|
---
|
|
137
140
|
|
|
141
|
+
## Node.js / Angular Compatibility
|
|
142
|
+
|
|
143
|
+
This CLI auto-detects your Node.js version and picks the highest compatible Angular CLI release. You don't normally need to think about it — but if you want to pin a specific Angular version, pass `--ng-version=<v>`.
|
|
144
|
+
|
|
145
|
+
| Angular CLI | Required Node.js |
|
|
146
|
+
| ----------- | --------------------------------- |
|
|
147
|
+
| 22 (latest) | `^22.22.3 \|\| ^24.15.0 \|\| >=26.0.0` |
|
|
148
|
+
| 21 | `^20.19.0 \|\| ^22.12.0 \|\| >=24.0.0` |
|
|
149
|
+
| 20 | `^20.11.1 \|\| ^22.0.0 \|\| ^24.0.0` |
|
|
150
|
+
| 19 | `^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0` |
|
|
151
|
+
|
|
152
|
+
If no Angular version supports your Node, the CLI prints an upgrade hint and exits.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
138
156
|
## Requirements
|
|
139
157
|
|
|
140
|
-
- Node.js 18+
|
|
158
|
+
- Node.js 18+ (Node 20.11.1+ recommended for Angular 20+)
|
|
141
159
|
- npm 9+
|
|
142
160
|
|
|
143
161
|
---
|
package/lib/cli/index.js
CHANGED
|
@@ -3,6 +3,12 @@ const inquirer = require("inquirer");
|
|
|
3
3
|
const chalk = require("chalk");
|
|
4
4
|
const CLI = require("./interactive");
|
|
5
5
|
const { createLogger } = require("../utils/logger");
|
|
6
|
+
const {
|
|
7
|
+
ANGULAR_COMPAT,
|
|
8
|
+
pickAngularVersion,
|
|
9
|
+
satisfies,
|
|
10
|
+
buildIncompatMessage,
|
|
11
|
+
} = require("../utils/nodeCompat");
|
|
6
12
|
|
|
7
13
|
class CLIHandler {
|
|
8
14
|
constructor() {
|
|
@@ -27,6 +33,10 @@ class CLIHandler {
|
|
|
27
33
|
"--ai-config <tools>",
|
|
28
34
|
"AI tools to configure (none, claude, cursor, gemini, copilot, jetbrains, windsurf)",
|
|
29
35
|
)
|
|
36
|
+
.option(
|
|
37
|
+
"--ng-version <version>",
|
|
38
|
+
"Pin Angular CLI version (e.g. latest, 21, 20, 19). Default: auto-pick highest compatible with current Node",
|
|
39
|
+
)
|
|
30
40
|
.option("--skip-install", "Skip npm install")
|
|
31
41
|
.parse();
|
|
32
42
|
|
|
@@ -167,7 +177,41 @@ class CLIHandler {
|
|
|
167
177
|
return answers;
|
|
168
178
|
}
|
|
169
179
|
|
|
180
|
+
resolveAngularVersion(requested) {
|
|
181
|
+
const nodeVer = process.versions.node;
|
|
182
|
+
|
|
183
|
+
// User explicitly pinned a version — honor it, but warn if known-incompatible.
|
|
184
|
+
if (requested) {
|
|
185
|
+
const entry = ANGULAR_COMPAT.find((e) => e.version === requested);
|
|
186
|
+
if (entry && !satisfies(nodeVer, entry.nodeRanges)) {
|
|
187
|
+
this.logger.warn(
|
|
188
|
+
`Angular ${requested} requires Node ${entry.nodeRanges.join(" || ")}, but you're on ${nodeVer}. Proceeding anyway — expect engine warnings.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return { version: requested, label: entry ? entry.label : `Angular ${requested}`, pinned: true };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-pick the highest compatible version.
|
|
195
|
+
const picked = pickAngularVersion(nodeVer);
|
|
196
|
+
if (!picked) {
|
|
197
|
+
console.error(chalk.red("\n❌ Incompatible Node.js version\n"));
|
|
198
|
+
console.error(buildIncompatMessage(nodeVer));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Tell the user when we fell back from latest.
|
|
203
|
+
if (picked.version !== "latest") {
|
|
204
|
+
this.logger.info(
|
|
205
|
+
`Node ${nodeVer} doesn't support Angular latest — using ${picked.label}. Pass --ng-version=latest to override.`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { ...picked, pinned: false };
|
|
210
|
+
}
|
|
211
|
+
|
|
170
212
|
async buildConfiguration(options, projectName, configOptions) {
|
|
213
|
+
const angular = this.resolveAngularVersion(options.ngVersion);
|
|
214
|
+
|
|
171
215
|
const config = {
|
|
172
216
|
projectName,
|
|
173
217
|
template: options.template || "starter",
|
|
@@ -176,6 +220,7 @@ class CLIHandler {
|
|
|
176
220
|
ssr: configOptions.ssr || false,
|
|
177
221
|
zoneless: configOptions.zoneless || false,
|
|
178
222
|
aiConfig: configOptions.aiConfig || ["none"], // Array of AI tools
|
|
223
|
+
angularVersion: angular.version,
|
|
179
224
|
skipInstall: options.skipInstall || false,
|
|
180
225
|
fullPath: require("path").resolve(process.cwd(), projectName),
|
|
181
226
|
};
|
|
@@ -51,7 +51,8 @@ class ProjectManager {
|
|
|
51
51
|
const zoneless = this.config.zoneless || false;
|
|
52
52
|
const aiConfig = this.config.aiConfig || ['none'];
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const ngVersion = this.config.angularVersion || "latest";
|
|
55
|
+
let cmd = `npx @angular/cli@${ngVersion} new ${this.config.projectName}`;
|
|
55
56
|
cmd += ` --routing=${routing}`;
|
|
56
57
|
cmd += ` --style=css`; // Always use CSS (Tailwind v4 official approach)
|
|
57
58
|
cmd += ` --ssr=${ssr}`;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Node.js -> Angular CLI compatibility matrix.
|
|
2
|
+
// Ordered newest → oldest. The picker walks this list and returns the first
|
|
3
|
+
// Angular version whose engine ranges accept the current Node runtime.
|
|
4
|
+
const ANGULAR_COMPAT = [
|
|
5
|
+
{
|
|
6
|
+
version: "latest",
|
|
7
|
+
label: "Angular 22 (latest)",
|
|
8
|
+
nodeRanges: ["^22.22.3", "^24.15.0", ">=26.0.0"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
version: "21",
|
|
12
|
+
label: "Angular 21",
|
|
13
|
+
nodeRanges: ["^20.19.0", "^22.12.0", ">=24.0.0"],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
version: "20",
|
|
17
|
+
label: "Angular 20",
|
|
18
|
+
nodeRanges: ["^20.11.1", "^22.0.0", "^24.0.0"],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
version: "19",
|
|
22
|
+
label: "Angular 19",
|
|
23
|
+
nodeRanges: ["^18.19.1", "^20.11.1", "^22.0.0"],
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function parseVer(v) {
|
|
28
|
+
const [maj = 0, min = 0, pat = 0] = v.replace(/^v/, "").split(".").map(Number);
|
|
29
|
+
return { maj, min, pat };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function gte(a, b) {
|
|
33
|
+
if (a.maj !== b.maj) return a.maj > b.maj;
|
|
34
|
+
if (a.min !== b.min) return a.min > b.min;
|
|
35
|
+
return a.pat >= b.pat;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function matchesRange(nodeVer, range) {
|
|
39
|
+
const node = parseVer(nodeVer);
|
|
40
|
+
if (range.startsWith("^")) {
|
|
41
|
+
const target = parseVer(range.slice(1));
|
|
42
|
+
return node.maj === target.maj && gte(node, target);
|
|
43
|
+
}
|
|
44
|
+
if (range.startsWith(">=")) {
|
|
45
|
+
const target = parseVer(range.slice(2));
|
|
46
|
+
return gte(node, target);
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function satisfies(nodeVer, ranges) {
|
|
52
|
+
return ranges.some((r) => matchesRange(nodeVer, r));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pickAngularVersion(nodeVer) {
|
|
56
|
+
for (const entry of ANGULAR_COMPAT) {
|
|
57
|
+
if (satisfies(nodeVer, entry.nodeRanges)) return entry;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatRanges(entry) {
|
|
63
|
+
return entry.nodeRanges.join(" || ");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildIncompatMessage(nodeVer) {
|
|
67
|
+
const lines = [
|
|
68
|
+
`Node ${nodeVer} is not supported by any Angular CLI version this tool knows about.`,
|
|
69
|
+
"",
|
|
70
|
+
"Supported combinations:",
|
|
71
|
+
...ANGULAR_COMPAT.map((e) => ` • ${e.label.padEnd(22)} requires Node ${formatRanges(e)}`),
|
|
72
|
+
"",
|
|
73
|
+
"Options:",
|
|
74
|
+
" • Upgrade Node: nvm install 24 && nvm use 24 (or use Homebrew / nodejs.org)",
|
|
75
|
+
" • Or downgrade Angular explicitly via the flag: --ng-version=20",
|
|
76
|
+
];
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
ANGULAR_COMPAT,
|
|
82
|
+
pickAngularVersion,
|
|
83
|
+
satisfies,
|
|
84
|
+
buildIncompatMessage,
|
|
85
|
+
};
|