perfect-payload 1.4.0-beta.0 → 1.5.0-beta.1
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 +184 -5
- package/index.js +49 -2
- package/package.json +11 -1
- package/.babelrc +0 -4
- package/.github/workflows/publish.yml +0 -332
package/README.md
CHANGED
|
@@ -4,14 +4,16 @@ A lightweight JavaScript payload validation utility for validating API
|
|
|
4
4
|
and JSON payloads with simple rule-based configuration.
|
|
5
5
|
|
|
6
6
|
`perfect-payload` supports structured validation errors, nested field
|
|
7
|
-
paths, synchronous custom validators,
|
|
8
|
-
transformation/sanitization
|
|
7
|
+
paths, synchronous custom validators, synchronous payload
|
|
8
|
+
transformation/sanitization, array size constraints, and deeply nested
|
|
9
|
+
array/object validation while keeping the validation schema simple.
|
|
9
10
|
|
|
10
11
|
## Quick Links
|
|
11
12
|
|
|
12
13
|
- [Installation](#installation)
|
|
13
14
|
- [Basic Usage](#basic-usage)
|
|
14
15
|
- [Validation Rules](#validation-rules)
|
|
16
|
+
- [Array Size and Nested Validation](#array-size-and-nested-validation)
|
|
15
17
|
- [Transformations and
|
|
16
18
|
Sanitization](#transformations-and-sanitization)
|
|
17
19
|
- [Custom Validators](#customvalidator)
|
|
@@ -293,6 +295,68 @@ Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
|
293
295
|
|
|
294
296
|
---
|
|
295
297
|
|
|
298
|
+
### `minItems`
|
|
299
|
+
|
|
300
|
+
Defines the minimum number of items required in an array.
|
|
301
|
+
|
|
302
|
+
Default: Not applied when omitted.
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
const rules = {
|
|
306
|
+
tags: {
|
|
307
|
+
type: "array",
|
|
308
|
+
minItems: 2,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
{
|
|
317
|
+
path: "tags",
|
|
318
|
+
code: "MIN_ITEMS",
|
|
319
|
+
message: "Attribute tags must contain at least 2 item(s)"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`minItems` is enforced even when `allowEmptyArray: true` is set. For example, `minItems: 2` still rejects `[]`.
|
|
324
|
+
|
|
325
|
+
Error code: `MIN_ITEMS`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `maxItems`
|
|
330
|
+
|
|
331
|
+
Defines the maximum number of items allowed in an array.
|
|
332
|
+
|
|
333
|
+
Default: Not applied when omitted.
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
const rules = {
|
|
337
|
+
tags: {
|
|
338
|
+
type: "array",
|
|
339
|
+
maxItems: 5,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
An array with more than 5 items returns `MAX_ITEMS`.
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
{
|
|
348
|
+
path: "tags",
|
|
349
|
+
code: "MAX_ITEMS",
|
|
350
|
+
message: "Attribute tags must contain at most 5 item(s)"
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`minItems` and `maxItems` can be used together.
|
|
355
|
+
|
|
356
|
+
Error code: `MAX_ITEMS`
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
296
360
|
### `type`
|
|
297
361
|
|
|
298
362
|
Validates the expected data type.
|
|
@@ -415,6 +479,8 @@ INVALID_UUID_V5
|
|
|
415
479
|
INVALID_OBJECT_ID
|
|
416
480
|
```
|
|
417
481
|
|
|
482
|
+
For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
483
|
+
|
|
418
484
|
---
|
|
419
485
|
|
|
420
486
|
### `regex`
|
|
@@ -731,6 +797,103 @@ Example error:
|
|
|
731
797
|
|
|
732
798
|
---
|
|
733
799
|
|
|
800
|
+
## Array Size and Nested Validation
|
|
801
|
+
|
|
802
|
+
`perfectPayload()` supports array size constraints and recursive validation of arrays and objects at multiple depths. Array indexes and nested object keys are preserved in structured error paths.
|
|
803
|
+
|
|
804
|
+
### Array size constraints
|
|
805
|
+
|
|
806
|
+
Use `minItems` and `maxItems` with `type: "array"`:
|
|
807
|
+
|
|
808
|
+
```js
|
|
809
|
+
const rules = {
|
|
810
|
+
products: {
|
|
811
|
+
type: "array",
|
|
812
|
+
minItems: 1,
|
|
813
|
+
maxItems: 3,
|
|
814
|
+
elementConstraints: {
|
|
815
|
+
type: "object",
|
|
816
|
+
objectAttr: {
|
|
817
|
+
productId: { mandatory: true, type: "string" },
|
|
818
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
If the array is empty, `minItems` reports the array path itself:
|
|
826
|
+
|
|
827
|
+
```js
|
|
828
|
+
{
|
|
829
|
+
path: "products",
|
|
830
|
+
code: "MIN_ITEMS",
|
|
831
|
+
message: "Attribute products must contain at least 1 item(s)"
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Arrays of objects
|
|
836
|
+
|
|
837
|
+
`elementConstraints` can contain `objectAttr`, allowing every object in an array to use a nested schema. An invalid quantity in the second product is reported as:
|
|
838
|
+
|
|
839
|
+
```text
|
|
840
|
+
products[1].quantity
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Deeply nested arrays and objects
|
|
844
|
+
|
|
845
|
+
`objectAttr` and `elementConstraints` can be combined recursively:
|
|
846
|
+
|
|
847
|
+
```js
|
|
848
|
+
const rules = {
|
|
849
|
+
orders: {
|
|
850
|
+
type: "array",
|
|
851
|
+
minItems: 1,
|
|
852
|
+
maxItems: 2,
|
|
853
|
+
elementConstraints: {
|
|
854
|
+
type: "object",
|
|
855
|
+
objectAttr: {
|
|
856
|
+
orderId: { mandatory: true, type: "string" },
|
|
857
|
+
items: {
|
|
858
|
+
mandatory: true,
|
|
859
|
+
type: "array",
|
|
860
|
+
minItems: 1,
|
|
861
|
+
maxItems: 2,
|
|
862
|
+
elementConstraints: {
|
|
863
|
+
type: "object",
|
|
864
|
+
objectAttr: {
|
|
865
|
+
productId: { mandatory: true, type: "string" },
|
|
866
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
A deep validation failure preserves the complete indexed path, for example:
|
|
877
|
+
|
|
878
|
+
```text
|
|
879
|
+
orders[1].items[2].quantity
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
Array constraints work at nested levels too. A nested array can report paths such as:
|
|
883
|
+
|
|
884
|
+
```text
|
|
885
|
+
orders[1].items
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Nested arrays are supported and every array index is preserved:
|
|
889
|
+
|
|
890
|
+
```text
|
|
891
|
+
matrix[1][1]
|
|
892
|
+
matrix[1][1][1]
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Transformations applied inside nested objects or array elements are preserved in `validatedPayload`, while the original input remains unchanged.
|
|
896
|
+
|
|
734
897
|
### Transformations and Sanitization
|
|
735
898
|
|
|
736
899
|
`perfectPayload()` can transform a field before its validation rules
|
|
@@ -922,9 +1085,17 @@ not passed to transformation functions; null handling remains controlled
|
|
|
922
1085
|
by `allowNull`.
|
|
923
1086
|
|
|
924
1087
|
**Important:** `transform` is synchronous. A non-function transformer,
|
|
925
|
-
an `async` transformer,
|
|
926
|
-
supported and throws an error.
|
|
927
|
-
|
|
1088
|
+
an `async` transformer, a transformer that returns a Promise, or a
|
|
1089
|
+
transformer that returns `undefined` is not supported and throws an error.
|
|
1090
|
+
Returning `null`, `""`, `0`, or `false` is allowed; the transformed value is
|
|
1091
|
+
then processed by the normal validation rules. Exceptions thrown inside the
|
|
1092
|
+
transformer propagate to the caller.
|
|
1093
|
+
|
|
1094
|
+
For example, returning `undefined` throws:
|
|
1095
|
+
|
|
1096
|
+
```text
|
|
1097
|
+
perfect-payload:- transform must not return undefined for attribute username
|
|
1098
|
+
```
|
|
928
1099
|
|
|
929
1100
|
### `customValidator`
|
|
930
1101
|
|
|
@@ -1051,6 +1222,10 @@ EMPTY_OBJECT_NOT_ALLOWED
|
|
|
1051
1222
|
|
|
1052
1223
|
EMPTY_ARRAY_NOT_ALLOWED
|
|
1053
1224
|
|
|
1225
|
+
MIN_ITEMS
|
|
1226
|
+
|
|
1227
|
+
MAX_ITEMS
|
|
1228
|
+
|
|
1054
1229
|
INVALID_ARRAY_ELEMENT
|
|
1055
1230
|
|
|
1056
1231
|
REGEX_MISMATCH
|
|
@@ -1086,6 +1261,8 @@ MIN_VALUE
|
|
|
1086
1261
|
MAX_VALUE
|
|
1087
1262
|
|
|
1088
1263
|
OUT_OF_RANGE
|
|
1264
|
+
|
|
1265
|
+
CUSTOM_VALIDATION_FAILED
|
|
1089
1266
|
```
|
|
1090
1267
|
|
|
1091
1268
|
These codes are designed for programmatic handling while `message`
|
|
@@ -1733,6 +1910,8 @@ marks[1]
|
|
|
1733
1910
|
marks[2]
|
|
1734
1911
|
```
|
|
1735
1912
|
|
|
1913
|
+
Array-level constraints such as `minItems` and `maxItems` report the path of the array itself. For nested arrays, the complete parent path is retained, for example `orders[1].items`.
|
|
1914
|
+
|
|
1736
1915
|
### Nested Fields Inside Arrays
|
|
1737
1916
|
|
|
1738
1917
|
Paths can also identify fields inside array elements.
|
package/index.js
CHANGED
|
@@ -633,6 +633,12 @@ function perfectPayloadStructured(
|
|
|
633
633
|
);
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
if (transformedValue === undefined) {
|
|
637
|
+
throw new Error(
|
|
638
|
+
`perfect-payload:- transform must not return undefined for attribute ${attributePath}`,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
636
642
|
attributeValue = transformedValue;
|
|
637
643
|
}
|
|
638
644
|
// TRANSFORMATIONS END
|
|
@@ -803,6 +809,45 @@ function perfectPayloadStructured(
|
|
|
803
809
|
|
|
804
810
|
break;
|
|
805
811
|
|
|
812
|
+
case "minItems":
|
|
813
|
+
if (
|
|
814
|
+
addNextError &&
|
|
815
|
+
attrExist &&
|
|
816
|
+
attributeValue !== null &&
|
|
817
|
+
isArray(attributeValue) &&
|
|
818
|
+
attributeValue.length < attributeRules[ruleName]
|
|
819
|
+
) {
|
|
820
|
+
addStructuredError(
|
|
821
|
+
rowErrors,
|
|
822
|
+
attributePath,
|
|
823
|
+
"MIN_ITEMS",
|
|
824
|
+
`Attribute ${attributePath} must contain at least ${attributeRules[ruleName]} item(s)`,
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
addNextError = false;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
break;
|
|
831
|
+
|
|
832
|
+
case "maxItems":
|
|
833
|
+
if (
|
|
834
|
+
addNextError &&
|
|
835
|
+
attrExist &&
|
|
836
|
+
attributeValue !== null &&
|
|
837
|
+
isArray(attributeValue) &&
|
|
838
|
+
attributeValue.length > attributeRules[ruleName]
|
|
839
|
+
) {
|
|
840
|
+
addStructuredError(
|
|
841
|
+
rowErrors,
|
|
842
|
+
attributePath,
|
|
843
|
+
"MAX_ITEMS",
|
|
844
|
+
`Attribute ${attributePath} must contain at most ${attributeRules[ruleName]} item(s)`,
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
addNextError = false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
break;
|
|
806
851
|
// ==================================================
|
|
807
852
|
// REGEX
|
|
808
853
|
// ==================================================
|
|
@@ -837,7 +882,10 @@ function perfectPayloadStructured(
|
|
|
837
882
|
|
|
838
883
|
switch (expectedType) {
|
|
839
884
|
case "number":
|
|
840
|
-
if (
|
|
885
|
+
if (
|
|
886
|
+
!isNumber(attributeValue) ||
|
|
887
|
+
Number.isNaN(attributeValue)
|
|
888
|
+
) {
|
|
841
889
|
addStructuredError(
|
|
842
890
|
rowErrors,
|
|
843
891
|
attributePath,
|
|
@@ -854,7 +902,6 @@ function perfectPayloadStructured(
|
|
|
854
902
|
}
|
|
855
903
|
|
|
856
904
|
break;
|
|
857
|
-
|
|
858
905
|
case "string":
|
|
859
906
|
if (!isString(attributeValue)) {
|
|
860
907
|
addStructuredError(
|
package/package.json
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfect-payload",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightweight JSON payload validation library with structured errors, nested validation, field paths, and customizable validation rules.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js"
|
|
12
|
+
],
|
|
13
|
+
"jest": {
|
|
14
|
+
"testPathIgnorePatterns": [
|
|
15
|
+
"/node_modules/",
|
|
16
|
+
"/test.js",
|
|
17
|
+
"/testingV1.js"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
10
20
|
"repository": {
|
|
11
21
|
"type": "git",
|
|
12
22
|
"url": "git+https://github.com/kiranpoojary/perfect-payload.git"
|
package/.babelrc
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
name: Stage npm package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
# Beta:
|
|
6
|
-
# Run for pushes to every branch except main.
|
|
7
|
-
branches:
|
|
8
|
-
- "**"
|
|
9
|
-
- "!main"
|
|
10
|
-
|
|
11
|
-
# Production:
|
|
12
|
-
# Run only when a version tag is pushed.
|
|
13
|
-
# Examples: v1.3.0, v1.3.1, v2.0.0
|
|
14
|
-
tags:
|
|
15
|
-
- "v*.*.*"
|
|
16
|
-
|
|
17
|
-
permissions:
|
|
18
|
-
contents: read
|
|
19
|
-
id-token: write
|
|
20
|
-
|
|
21
|
-
jobs:
|
|
22
|
-
# ==========================================================
|
|
23
|
-
# 1. PACKAGE INFO
|
|
24
|
-
# ==========================================================
|
|
25
|
-
package-info:
|
|
26
|
-
name: Package Info
|
|
27
|
-
runs-on: ubuntu-latest
|
|
28
|
-
|
|
29
|
-
outputs:
|
|
30
|
-
name: ${{ steps.package.outputs.name }}
|
|
31
|
-
version: ${{ steps.package.outputs.version }}
|
|
32
|
-
release_type: ${{ steps.package.outputs.release_type }}
|
|
33
|
-
|
|
34
|
-
steps:
|
|
35
|
-
- name: Checkout repository
|
|
36
|
-
uses: actions/checkout@v7
|
|
37
|
-
|
|
38
|
-
- name: Read package information
|
|
39
|
-
id: package
|
|
40
|
-
shell: bash
|
|
41
|
-
run: |
|
|
42
|
-
NAME=$(node -p "require('./package.json').name")
|
|
43
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
44
|
-
|
|
45
|
-
echo "name=$NAME" >> "$GITHUB_OUTPUT"
|
|
46
|
-
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
47
|
-
|
|
48
|
-
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
|
49
|
-
RELEASE_TYPE="production"
|
|
50
|
-
else
|
|
51
|
-
RELEASE_TYPE="beta"
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT"
|
|
55
|
-
|
|
56
|
-
echo "======================================"
|
|
57
|
-
echo "PACKAGE INFORMATION"
|
|
58
|
-
echo "======================================"
|
|
59
|
-
echo "Package: $NAME"
|
|
60
|
-
echo "Version: $VERSION"
|
|
61
|
-
echo "Release type: $RELEASE_TYPE"
|
|
62
|
-
echo "Git ref: $GITHUB_REF_NAME"
|
|
63
|
-
echo "Git ref type: $GITHUB_REF_TYPE"
|
|
64
|
-
echo "======================================"
|
|
65
|
-
|
|
66
|
-
# ==========================================================
|
|
67
|
-
# 2. VALIDATE RELEASE
|
|
68
|
-
# ==========================================================
|
|
69
|
-
validate-release:
|
|
70
|
-
name: Validate Release
|
|
71
|
-
needs: package-info
|
|
72
|
-
runs-on: ubuntu-latest
|
|
73
|
-
|
|
74
|
-
steps:
|
|
75
|
-
- name: Validate branch / tag and version
|
|
76
|
-
shell: bash
|
|
77
|
-
run: |
|
|
78
|
-
VERSION="${{ needs.package-info.outputs.version }}"
|
|
79
|
-
|
|
80
|
-
echo "Version: $VERSION"
|
|
81
|
-
echo "Ref: $GITHUB_REF_NAME"
|
|
82
|
-
echo "Ref type: $GITHUB_REF_TYPE"
|
|
83
|
-
|
|
84
|
-
# --------------------------------------------------
|
|
85
|
-
# PRODUCTION
|
|
86
|
-
# Must come from a Git tag.
|
|
87
|
-
#
|
|
88
|
-
# Tag: v1.3.0
|
|
89
|
-
# package version: 1.3.0
|
|
90
|
-
# --------------------------------------------------
|
|
91
|
-
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
|
|
92
|
-
|
|
93
|
-
TAG="$GITHUB_REF_NAME"
|
|
94
|
-
|
|
95
|
-
if [[ "$VERSION" == *"-beta"* ]]; then
|
|
96
|
-
echo "::error::Production releases cannot contain -beta."
|
|
97
|
-
echo "::error::Current package version: $VERSION"
|
|
98
|
-
exit 1
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
EXPECTED_TAG="v$VERSION"
|
|
102
|
-
|
|
103
|
-
if [[ "$TAG" != "$EXPECTED_TAG" ]]; then
|
|
104
|
-
echo "::error::Git tag does not match package.json version."
|
|
105
|
-
echo "::error::Tag: $TAG"
|
|
106
|
-
echo "::error::Expected tag: $EXPECTED_TAG"
|
|
107
|
-
exit 1
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
echo "Valid production release."
|
|
111
|
-
echo "Tag $TAG matches package version $VERSION."
|
|
112
|
-
|
|
113
|
-
# --------------------------------------------------
|
|
114
|
-
# BETA
|
|
115
|
-
# Any non-main branch.
|
|
116
|
-
#
|
|
117
|
-
# Example:
|
|
118
|
-
# feature/custom-validator
|
|
119
|
-
# package version: 1.3.0-beta.0
|
|
120
|
-
# --------------------------------------------------
|
|
121
|
-
else
|
|
122
|
-
|
|
123
|
-
if [[ "$GITHUB_REF_NAME" == "main" ]]; then
|
|
124
|
-
echo "::error::Production releases from main pushes are disabled."
|
|
125
|
-
echo "::error::Create a Git tag to release production."
|
|
126
|
-
exit 1
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
if [[ "$VERSION" != *"-beta"* ]]; then
|
|
130
|
-
echo "::error::Non-main branches must contain a beta version."
|
|
131
|
-
echo "::error::Current version: $VERSION"
|
|
132
|
-
echo "::error::Expected something like 1.3.0-beta.0"
|
|
133
|
-
exit 1
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
echo "Valid beta release."
|
|
137
|
-
echo "Branch: $GITHUB_REF_NAME"
|
|
138
|
-
echo "Version: $VERSION"
|
|
139
|
-
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
# ==========================================================
|
|
143
|
-
# 3. INSTALL DEPENDENCIES
|
|
144
|
-
# ==========================================================
|
|
145
|
-
install:
|
|
146
|
-
name: Install Dependencies
|
|
147
|
-
needs:
|
|
148
|
-
- package-info
|
|
149
|
-
- validate-release
|
|
150
|
-
|
|
151
|
-
runs-on: ubuntu-latest
|
|
152
|
-
|
|
153
|
-
steps:
|
|
154
|
-
- name: Checkout repository
|
|
155
|
-
uses: actions/checkout@v7
|
|
156
|
-
|
|
157
|
-
- name: Setup Node.js
|
|
158
|
-
uses: actions/setup-node@v7
|
|
159
|
-
with:
|
|
160
|
-
node-version: 24
|
|
161
|
-
registry-url: https://registry.npmjs.org
|
|
162
|
-
package-manager-cache: false
|
|
163
|
-
|
|
164
|
-
- name: Setup npm
|
|
165
|
-
run: npm install --global npm@11.19.1
|
|
166
|
-
|
|
167
|
-
- name: Verify Node and npm versions
|
|
168
|
-
run: |
|
|
169
|
-
node -v
|
|
170
|
-
npm -v
|
|
171
|
-
|
|
172
|
-
- name: Install dependencies
|
|
173
|
-
run: npm ci
|
|
174
|
-
|
|
175
|
-
# ==========================================================
|
|
176
|
-
# 4. RUN TESTS
|
|
177
|
-
# ==========================================================
|
|
178
|
-
test:
|
|
179
|
-
name: Run Tests
|
|
180
|
-
needs: install
|
|
181
|
-
runs-on: ubuntu-latest
|
|
182
|
-
|
|
183
|
-
steps:
|
|
184
|
-
- name: Checkout repository
|
|
185
|
-
uses: actions/checkout@v7
|
|
186
|
-
|
|
187
|
-
- name: Setup Node.js
|
|
188
|
-
uses: actions/setup-node@v7
|
|
189
|
-
with:
|
|
190
|
-
node-version: 24
|
|
191
|
-
package-manager-cache: false
|
|
192
|
-
|
|
193
|
-
- name: Install dependencies
|
|
194
|
-
run: npm ci
|
|
195
|
-
|
|
196
|
-
- name: Run tests
|
|
197
|
-
run: npm test -- --passWithNoTests
|
|
198
|
-
|
|
199
|
-
# ==========================================================
|
|
200
|
-
# 5. CHECK NPM VERSION
|
|
201
|
-
# ==========================================================
|
|
202
|
-
check-npm:
|
|
203
|
-
name: Check npm Version
|
|
204
|
-
needs:
|
|
205
|
-
- package-info
|
|
206
|
-
- validate-release
|
|
207
|
-
- test
|
|
208
|
-
|
|
209
|
-
runs-on: ubuntu-latest
|
|
210
|
-
|
|
211
|
-
steps:
|
|
212
|
-
- name: Setup Node.js
|
|
213
|
-
uses: actions/setup-node@v7
|
|
214
|
-
with:
|
|
215
|
-
node-version: 24
|
|
216
|
-
registry-url: https://registry.npmjs.org
|
|
217
|
-
package-manager-cache: false
|
|
218
|
-
|
|
219
|
-
- name: Check whether version already exists
|
|
220
|
-
shell: bash
|
|
221
|
-
run: |
|
|
222
|
-
NAME="${{ needs.package-info.outputs.name }}"
|
|
223
|
-
VERSION="${{ needs.package-info.outputs.version }}"
|
|
224
|
-
|
|
225
|
-
echo "Checking npm for $NAME@$VERSION..."
|
|
226
|
-
|
|
227
|
-
if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
|
|
228
|
-
echo "::error::$NAME@$VERSION is already published."
|
|
229
|
-
echo "::error::Use a new package version."
|
|
230
|
-
exit 1
|
|
231
|
-
fi
|
|
232
|
-
|
|
233
|
-
echo "$NAME@$VERSION is not published."
|
|
234
|
-
echo "Release is ready to stage."
|
|
235
|
-
|
|
236
|
-
# ==========================================================
|
|
237
|
-
# 6. STAGE NPM PACKAGE
|
|
238
|
-
#
|
|
239
|
-
# This environment can optionally have a GitHub
|
|
240
|
-
# Required Reviewer configured.
|
|
241
|
-
#
|
|
242
|
-
# npm itself still requires approval + 2FA after staging.
|
|
243
|
-
# ==========================================================
|
|
244
|
-
stage-npm:
|
|
245
|
-
name: Stage npm Package
|
|
246
|
-
needs:
|
|
247
|
-
- package-info
|
|
248
|
-
- check-npm
|
|
249
|
-
|
|
250
|
-
runs-on: ubuntu-latest
|
|
251
|
-
|
|
252
|
-
environment:
|
|
253
|
-
name: npm-stage
|
|
254
|
-
|
|
255
|
-
steps:
|
|
256
|
-
- name: Checkout repository
|
|
257
|
-
uses: actions/checkout@v7
|
|
258
|
-
|
|
259
|
-
- name: Setup Node.js
|
|
260
|
-
uses: actions/setup-node@v7
|
|
261
|
-
with:
|
|
262
|
-
node-version: 24
|
|
263
|
-
registry-url: https://registry.npmjs.org
|
|
264
|
-
package-manager-cache: false
|
|
265
|
-
|
|
266
|
-
- name: Setup npm
|
|
267
|
-
run: npm install --global npm@11.19.1
|
|
268
|
-
|
|
269
|
-
- name: Show release information
|
|
270
|
-
shell: bash
|
|
271
|
-
run: |
|
|
272
|
-
echo "======================================"
|
|
273
|
-
echo "READY TO STAGE"
|
|
274
|
-
echo "======================================"
|
|
275
|
-
echo "Package: ${{ needs.package-info.outputs.name }}"
|
|
276
|
-
echo "Version: ${{ needs.package-info.outputs.version }}"
|
|
277
|
-
echo "Type: ${{ needs.package-info.outputs.release_type }}"
|
|
278
|
-
echo "Ref: $GITHUB_REF_NAME"
|
|
279
|
-
echo "======================================"
|
|
280
|
-
|
|
281
|
-
# ------------------------------------------------------
|
|
282
|
-
# BETA
|
|
283
|
-
# Triggered only from non-main branch pushes.
|
|
284
|
-
# ------------------------------------------------------
|
|
285
|
-
- name: Stage Beta Package
|
|
286
|
-
if: >
|
|
287
|
-
github.ref_type == 'branch' &&
|
|
288
|
-
needs.package-info.outputs.release_type == 'beta'
|
|
289
|
-
run: |
|
|
290
|
-
echo "Staging beta package..."
|
|
291
|
-
echo "${{ needs.package-info.outputs.name }}@${{ needs.package-info.outputs.version }}"
|
|
292
|
-
|
|
293
|
-
npm stage publish --tag beta
|
|
294
|
-
|
|
295
|
-
# ------------------------------------------------------
|
|
296
|
-
# PRODUCTION
|
|
297
|
-
# Triggered ONLY from Git tags.
|
|
298
|
-
# ------------------------------------------------------
|
|
299
|
-
- name: Stage Production Package
|
|
300
|
-
if: >
|
|
301
|
-
github.ref_type == 'tag' &&
|
|
302
|
-
needs.package-info.outputs.release_type == 'production'
|
|
303
|
-
run: |
|
|
304
|
-
echo "Staging production package..."
|
|
305
|
-
echo "${{ needs.package-info.outputs.name }}@${{ needs.package-info.outputs.version }}"
|
|
306
|
-
|
|
307
|
-
npm stage publish
|
|
308
|
-
|
|
309
|
-
# ==========================================================
|
|
310
|
-
# 7. COMPLETION
|
|
311
|
-
# ==========================================================
|
|
312
|
-
complete:
|
|
313
|
-
name: npm Package Staged
|
|
314
|
-
needs:
|
|
315
|
-
- package-info
|
|
316
|
-
- stage-npm
|
|
317
|
-
|
|
318
|
-
runs-on: ubuntu-latest
|
|
319
|
-
|
|
320
|
-
steps:
|
|
321
|
-
- name: Staging completed
|
|
322
|
-
run: |
|
|
323
|
-
echo "======================================"
|
|
324
|
-
echo "NPM STAGING COMPLETED"
|
|
325
|
-
echo "======================================"
|
|
326
|
-
echo "Package: ${{ needs.package-info.outputs.name }}"
|
|
327
|
-
echo "Version: ${{ needs.package-info.outputs.version }}"
|
|
328
|
-
echo "Type: ${{ needs.package-info.outputs.release_type }}"
|
|
329
|
-
echo ""
|
|
330
|
-
echo "The package has been staged on npm."
|
|
331
|
-
echo "Review and approve it on npm with 2FA."
|
|
332
|
-
echo "======================================"
|