Skip to content

Commit 90915fa

Browse files
authored
fix(check-node): align with CDK Node.js versions support policy (#4865)
Align with the CDK support policy for Node.js versions (https://docs.aws.amazon.com/cdk/v2/guide/node-versions.html) Previously `@jsii/check-node` would support Node.js versions until their EOL date. We would also warn users 30 days before the EOL. With our updated Node.js versions support policy, we will support Node.js versions usually for 6 months after the EOL. This change reflects this new policy. We now warn users in the extended support timeframe, but not anymore before the EOL. Also adds Node.js 24. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent e1e0ee3 commit 90915fa

File tree

6 files changed

+171
-94
lines changed

6 files changed

+171
-94
lines changed

.github/workflows/main.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ jobs:
232232
node: '22' # EOL 2027-04-30
233233
os: ubuntu-latest
234234
python: '3.9'
235+
- title: 'Node 24'
236+
java: '8'
237+
dotnet: '6.0.x'
238+
go: '1.18'
239+
node: '22' # EOL 2028-04-30
240+
os: ubuntu-latest
241+
python: '3.9'
235242
# Test alternate .NETs
236243
- title: '.NET 7.0'
237244
java: '8'

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ our own CI/CD: the ["superchain" image][superchain] from.
2020
[superchain]: https://github.com/aws/jsii-superchain
2121

2222
Once built locally, you can launch a shell from your root project directory in a self-removing container like so:
23+
2324
```bash
2425
# runs the built container, creates a volume mount to /project inside the container
2526
docker run --rm -it -v "$(pwd)":/project -w /project jsii/superchain:local
@@ -31,7 +32,7 @@ The following tools need to be installed to develop on JSII locally. We recommen
3132
using the docker image from the above section, but if you wish to, you can install
3233
in your development environment.
3334

34-
- [Node `18.0.0`] or later
35+
- [Node `20.0.0`] or later
3536
- [Yarn `1.19.1`] or later
3637
- An OpenJDK-8 distribution (e.g: [Oracle's OpenJDK8], [Amazon Corretto 8])
3738
- [`maven >= 3.0.5`](https://maven.apache.org)

gh-pages/partials/node-support-table.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
| `^18.0.0` | :white_check_mark: Supported | `2025-04-30` |
66
| `^20.0.0` | :white_check_mark: Supported | `2026-04-30` |
77
| `^22.0.0` | :white_check_mark: Supported | `2027-04-30` |
8+
| `^24.0.0` | :white_check_mark: Supported | `2028-04-30` |
89

910
??? question "Status Definitions"
1011
- **:white_check_mark: Supported**: Long Term Support (LTS) releases (those with an even major version) are
Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
import { NodeRelease } from './constants';
22

3-
test('supported release', () => {
4-
const endOfLife = new Date(Date.now() + 31 * 86_400_000);
5-
const subject = new NodeRelease(42, { endOfLife });
3+
describe('before EOL', () => {
4+
test('supported release', () => {
5+
const endOfLife = new Date(Date.now() + 31 * 86_400_000);
6+
const subject = new NodeRelease(42, { endOfLife });
67

7-
expect(subject).toMatchObject({
8-
deprecated: false,
9-
endOfLife: false,
10-
endOfLifeDate: endOfLife,
11-
untested: false,
12-
supported: true,
13-
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
8+
expect(subject).toMatchObject({
9+
supported: true,
10+
deprecated: false,
11+
endOfLife: false,
12+
endOfLifeDate: endOfLife,
13+
endOfJsiiSupportDate: datePlusMonth(endOfLife, 6),
14+
untested: false,
15+
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
16+
});
1417
});
15-
});
1618

17-
test('supported release with range', () => {
18-
const endOfLife = new Date(Date.now() + 31 * 86_400_000);
19-
const subject = new NodeRelease(42, {
20-
endOfLife,
21-
supportedRange: '^42.1337.0',
22-
});
19+
test('supported release with range', () => {
20+
const endOfLife = new Date(Date.now() + 31 * 86_400_000);
21+
const subject = new NodeRelease(42, {
22+
endOfLife,
23+
supportedRange: '^42.1337.0',
24+
});
2325

24-
expect(subject).toMatchObject({
25-
deprecated: false,
26-
endOfLife: false,
27-
endOfLifeDate: endOfLife,
28-
untested: false,
29-
supported: true,
30-
supportedRange: expect.objectContaining({ raw: '^42.1337.0' }),
26+
expect(subject).toMatchObject({
27+
deprecated: false,
28+
endOfLife: false,
29+
endOfLifeDate: endOfLife,
30+
endOfJsiiSupportDate: datePlusMonth(endOfLife, 6),
31+
untested: false,
32+
supported: true,
33+
supportedRange: expect.objectContaining({ raw: '^42.1337.0' }),
34+
});
3135
});
3236
});
3337

@@ -36,39 +40,88 @@ test('untested release', () => {
3640
const subject = new NodeRelease(42, { endOfLife, untested: true });
3741

3842
expect(subject).toMatchObject({
43+
supported: false,
3944
deprecated: false,
4045
endOfLife: false,
4146
endOfLifeDate: endOfLife,
47+
endOfJsiiSupportDate: datePlusMonth(endOfLife, 6),
4248
untested: true,
43-
supported: false,
4449
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
4550
});
4651
});
4752

48-
test('deprecated release', () => {
49-
const endOfLife = new Date(Date.now() + 25 * 86_400_000);
50-
const subject = new NodeRelease(42, { endOfLife });
53+
describe('deprecated', () => {
54+
test('after EOL, before EOS', () => {
55+
const endOfLife = new Date(Date.now() - 30 * 86_400_000);
56+
const endOfJsiiSupport = new Date(Date.now() + 30 * 86_400_000);
57+
const subject = new NodeRelease(42, {
58+
endOfLife,
59+
endOfJsiiSupport,
60+
});
5161

52-
expect(subject).toMatchObject({
53-
deprecated: true,
54-
endOfLife: false,
55-
endOfLifeDate: endOfLife,
56-
untested: false,
57-
supported: true,
58-
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
62+
expect(subject).toMatchObject({
63+
supported: true,
64+
deprecated: true,
65+
endOfLife: true,
66+
endOfLifeDate: endOfLife,
67+
endOfJsiiSupportDate: endOfJsiiSupport,
68+
untested: false,
69+
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
70+
});
71+
});
72+
73+
test('Within default EOS time', () => {
74+
const endOfLife = new Date(Date.now() - 30 * 86_400_000);
75+
const subject = new NodeRelease(42, { endOfLife });
76+
77+
expect(subject).toMatchObject({
78+
supported: true,
79+
deprecated: true,
80+
endOfLife: true,
81+
endOfLifeDate: endOfLife,
82+
endOfJsiiSupportDate: datePlusMonth(endOfLife, 6),
83+
untested: false,
84+
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
85+
});
5986
});
6087
});
6188

62-
test('EOL release', () => {
63-
const endOfLife = new Date();
64-
const subject = new NodeRelease(42, { endOfLife });
89+
describe('EOS', () => {
90+
test('EOS release', () => {
91+
const endOfLife = new Date(Date.now() - 30 * 86_400_000);
92+
const endOfJsiiSupport = new Date(Date.now() - 10 * 86_400_000);
93+
const subject = new NodeRelease(42, {
94+
endOfLife,
95+
endOfJsiiSupport,
96+
});
6597

66-
expect(subject).toMatchObject({
67-
deprecated: false,
68-
endOfLife: true,
69-
endOfLifeDate: endOfLife,
70-
untested: false,
71-
supported: false,
72-
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
98+
expect(subject).toMatchObject({
99+
supported: false,
100+
endOfLife: true,
101+
deprecated: false,
102+
endOfLifeDate: endOfLife,
103+
endOfJsiiSupportDate: endOfJsiiSupport,
104+
untested: false,
105+
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
106+
});
107+
});
108+
109+
test('EOS default time frame', () => {
110+
const endOfLife = new Date(Date.now() - 200 * 86_400_000);
111+
const subject = new NodeRelease(42, { endOfLife });
112+
113+
expect(subject).toMatchObject({
114+
supported: false,
115+
endOfLife: true,
116+
deprecated: false,
117+
endOfLifeDate: endOfLife,
118+
endOfJsiiSupportDate: datePlusMonth(endOfLife, 6),
119+
untested: false,
120+
supportedRange: expect.objectContaining({ raw: '^42.0.0' }),
121+
});
73122
});
74123
});
124+
125+
function datePlusMonth(date: Date, months: number): Date {
126+
return new Date(date.getFullYear(), date.getMonth() + months, date.getDate());
127+
}

packages/@jsii/check-node/src/constants.ts

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ const ONE_DAY_IN_MILLISECONDS = 86_400_000;
1010
*/
1111
export class NodeRelease {
1212
/**
13-
* How long before enf-of-life do we start warning customers? Expressed in
14-
* milliseconds to make it easier to deal with JS dates.
13+
* How long after end-of-life do we continue to support a node version.
1514
*/
16-
private static readonly DEPRECATION_WINDOW_MS = 30 * ONE_DAY_IN_MILLISECONDS;
15+
private static readonly DEFAULT_EXTENDED_SUPPORT_MONTHS = 6;
1716

1817
/**
1918
* All registered node releases.
2019
*/
2120
public static readonly ALL_RELEASES: readonly NodeRelease[] = [
2221
// Historical releases (not relevant at time of writing this as they're all EOL now...)
2322
...([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] as const).map(
24-
(majorVersion) => new NodeRelease(majorVersion, { endOfLife: true }),
23+
(majorVersion) =>
24+
new NodeRelease(majorVersion, {
25+
endOfLife: new Date('2018-01-01'),
26+
untested: true,
27+
}),
2528
),
2629

2730
// Past end-of-life releases
@@ -46,11 +49,16 @@ export class NodeRelease {
4649
}),
4750
new NodeRelease(19, { endOfLife: new Date('2023-06-01'), untested: true }),
4851
new NodeRelease(21, { endOfLife: new Date('2024-06-01'), untested: true }),
52+
new NodeRelease(23, { endOfLife: new Date('2025-06-01'), untested: true }),
4953

5054
// Currently active releases (as of last edit to this file...)
51-
new NodeRelease(18, { endOfLife: new Date('2025-04-30') }),
55+
new NodeRelease(18, {
56+
endOfLife: new Date('2025-04-30'),
57+
endOfJsiiSupport: new Date('2025-11-30'),
58+
}),
5259
new NodeRelease(20, { endOfLife: new Date('2026-04-30') }),
5360
new NodeRelease(22, { endOfLife: new Date('2027-04-30') }),
61+
new NodeRelease(24, { endOfLife: new Date('2028-04-30') }),
5462

5563
// Future (planned releases)
5664
];
@@ -88,21 +96,33 @@ export class NodeRelease {
8896

8997
/**
9098
* The date on which this release range starts to be considered end-of-life.
91-
* May be `undefined` for "ancient" releases (before Node 12).
99+
* Defaults to a pre-CDK date for "ancient" releases (before Node 12).
92100
*/
93-
public readonly endOfLifeDate: Date | undefined;
101+
public readonly endOfLifeDate: Date;
94102

95103
/**
96-
* Determines whether this release is currently considered end-of-life.
104+
* Determines whether this release has reached end of support for jsii.
105+
* This is usually longer then endOfLife;
97106
*/
98-
public readonly endOfLife: boolean;
107+
public readonly endOfJsiiSupportDate: Date;
99108

100109
/**
101110
* Determines whether this release is within the deprecation window ahead of
102111
* it's end-of-life date.
103112
*/
104113
public readonly deprecated: boolean;
105114

115+
/**
116+
* Determines whether this release has reached end-of-life.
117+
*/
118+
public readonly endOfLife: boolean;
119+
120+
/**
121+
* Determines whether this major version line is currently "in support",
122+
* meaning it is not end-of-life or within our extended support time frame.
123+
*/
124+
public readonly supported: boolean;
125+
106126
/**
107127
* If `true` denotes that this version of node has not been added to the test
108128
* matrix yet. This is used when adding not-yet-released versions of node that
@@ -119,51 +139,45 @@ export class NodeRelease {
119139
*/
120140
public readonly supportedRange: Range;
121141

122-
/**
123-
* Determines whether this major version line is currently "in support",
124-
* meaning it is not end-of-life nor pending.
125-
*/
126-
public readonly supported: boolean;
127-
128-
/** @internal visible for testing */
129-
public constructor(
130-
majorVersion: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11,
131-
opts: { endOfLife: true },
132-
);
133142
/** @internal visible for testing */
134143
public constructor(
135144
majorVersion: number,
136145
opts: {
137146
endOfLife: Date;
138-
untested?: boolean;
139-
supportedRange?: string;
140-
},
141-
);
142-
/** @internal visible for testing */
143-
public constructor(
144-
majorVersion: number,
145-
opts: {
146-
endOfLife: Date | true;
147+
endOfJsiiSupport?: Date;
147148
untested?: boolean;
148149
supportedRange?: string;
149150
},
150151
) {
151-
this.majorVersion = majorVersion;
152-
this.endOfLifeDate = opts.endOfLife === true ? undefined : opts.endOfLife;
153152
this.untested = opts.untested ?? false;
153+
this.majorVersion = majorVersion;
154154
this.supportedRange = new Range(
155155
opts.supportedRange ?? `^${majorVersion}.0.0`,
156156
);
157157

158+
this.endOfLifeDate = opts.endOfLife;
158159
this.endOfLife =
159-
opts.endOfLife === true || opts.endOfLife.getTime() <= Date.now();
160-
this.deprecated =
161-
!this.endOfLife &&
162-
opts.endOfLife !== true &&
163-
opts.endOfLife.getTime() - NodeRelease.DEPRECATION_WINDOW_MS <=
164-
Date.now();
165-
166-
this.supported = !this.untested && !this.endOfLife;
160+
opts.endOfLife.getTime() + ONE_DAY_IN_MILLISECONDS <= Date.now();
161+
162+
// jsii EOS defaults to 6 months after EOL
163+
this.endOfJsiiSupportDate =
164+
opts.endOfJsiiSupport ??
165+
new Date(
166+
this.endOfLifeDate.getFullYear(),
167+
this.endOfLifeDate.getMonth() +
168+
NodeRelease.DEFAULT_EXTENDED_SUPPORT_MONTHS,
169+
this.endOfLifeDate.getDate(),
170+
);
171+
172+
const endOfJsiiSupport =
173+
this.endOfJsiiSupportDate.getTime() + ONE_DAY_IN_MILLISECONDS <=
174+
Date.now();
175+
176+
// We deprecate (warn) from EOL to jsii EOS
177+
this.deprecated = this.endOfLife && !endOfJsiiSupport;
178+
179+
// All tested and not EOS versions are supported
180+
this.supported = !this.untested && !endOfJsiiSupport;
167181
}
168182

169183
public toString(): string {

0 commit comments

Comments
 (0)