Skip to content

Commit e501b6c

Browse files
authored
chore: add pkg api shape validation (#7498)
* chore: add pkg api shape validation * chore: add api validator to precommit and integration
1 parent f806a2b commit e501b6c

File tree

3 files changed

+2381
-1
lines changed

3 files changed

+2381
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"lint:release": "lerna exec --ignore '@aws-sdk/client-*' --ignore '@aws-sdk/aws-*' 'eslint --quiet src/**/*.ts'",
3535
"lint:versions": "node scripts/runtime-dependency-version-check/runtime-dep-version-check.js",
3636
"lint:dependencies": "node scripts/runtime-dependency-version-check/check-dependencies.js",
37+
"lint:api": "node scripts/validation/api-snapshot-validation.js",
3738
"local-publish": "node ./scripts/verdaccio-publish/index.js",
3839
"test:all": "yarn build:all && jest --passWithNoTests && lerna run test --scope '@aws-sdk/{fetch-http-handler,hash-blob-browser}' && yarn test:versions && yarn test:integration",
3940
"test:ci": "lerna run test --since origin/main",
@@ -134,7 +135,7 @@
134135
},
135136
"husky": {
136137
"hooks": {
137-
"pre-commit": "lint-staged && yarn lint:versions && yarn lint:dependencies",
138+
"pre-commit": "lint-staged && yarn lint:versions && yarn lint:dependencies && yarn lint:api",
138139
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
139140
}
140141
},
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
This script uses the JSON file api-snapshot/api.json to validate that previously present symbols
5+
are still exported by the packages within the assessed group.
6+
7+
Data may only be deleted from api.json in an intentional backwards-incompatible change.
8+
*/
9+
10+
const fs = require("node:fs");
11+
const path = require("node:path");
12+
13+
const root = path.join(__dirname, "..", "..");
14+
const dataPath = path.join(root, "scripts", "validation", "api.json");
15+
const api = require(dataPath);
16+
17+
api.$schema = "https://json-schema.org/draft/2020-12/schema";
18+
19+
const packageDirs = [
20+
...fs.readdirSync(path.join(root, "packages")).map((f) => path.join(root, "packages", f)),
21+
...fs.readdirSync(path.join(root, "lib")).map((f) => path.join(root, "lib", f)),
22+
path.join(root, "clients", "client-s3"), // rest xml
23+
path.join(root, "clients", "client-s3-control"), // rest xml
24+
path.join(root, "clients", "client-dynamodb"), // json rpc
25+
path.join(root, "clients", "client-cloudwatch"), // query
26+
path.join(root, "clients", "client-sts"), // query
27+
path.join(root, "clients", "client-sagemaker"), // json rpc
28+
path.join(root, "clients", "client-bedrock"), // rest json
29+
];
30+
const errors = [];
31+
32+
for (const packageRoot of packageDirs) {
33+
const pkgJsonPath = path.join(packageRoot, "package.json");
34+
const cjsPath = path.join(packageRoot, "dist-cjs", "index.js");
35+
36+
if (fs.existsSync(pkgJsonPath) && fs.existsSync(cjsPath)) {
37+
const packageJson = require(pkgJsonPath);
38+
const { name, version } = packageJson;
39+
const module = require(cjsPath);
40+
41+
for (const key of Object.keys(module)) {
42+
if (module[key] === undefined) {
43+
console.warn(`symbol ${key} in ${name}@${version} has a value of undefined.`);
44+
}
45+
}
46+
47+
if (!api[name]) {
48+
api[name] = {};
49+
for (const key of Object.keys(module)) {
50+
api[name][key] = [typeof module[key], `<=${version}`].join(", since ");
51+
}
52+
} else {
53+
for (const symbol of [...new Set([...Object.keys(api[name]), ...Object.keys(module)])]) {
54+
if (symbol in module && !(symbol in api[name])) {
55+
errors.push(`You must commit changes in api.json.`);
56+
api[name][symbol] = [typeof module[symbol], version].join(", since ");
57+
}
58+
if (!(symbol in module) && symbol in api[name]) {
59+
errors.push(`Symbol [${symbol}] is missing from ${name}, (${api[name][symbol]}).`);
60+
}
61+
if (symbol in module && symbol in api[name]) {
62+
if (api[name][symbol].split(", ")[0] !== typeof module[symbol]) {
63+
errors.push(
64+
`Symbol [${symbol}] has a different type than expected in ${name}, actual=${typeof module[
65+
symbol
66+
]} expected=${api[name][symbol]}.`
67+
);
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
fs.writeFileSync(dataPath, JSON.stringify(api, null, 2) + "\n");
76+
77+
if (errors.length) {
78+
throw new Error(errors.join("\n"));
79+
} else {
80+
console.log(`✅ API snapshot test passed.`);
81+
}

0 commit comments

Comments
 (0)