forked from Axionvera/pocketpay-mobile
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-sdk-api.js
More file actions
149 lines (127 loc) · 5.12 KB
/
Copy pathcheck-sdk-api.js
File metadata and controls
149 lines (127 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env node
/**
* Public API compatibility check for `pocketpay-sdk`.
*
* The real pocketpay-sdk package is not published yet, so this app codes
* against an ambient contract in src/types/pocketpay-sdk.d.ts. That file
* IS the public API — this script extracts a normalized snapshot of it
* and diffs it against the committed baseline in
* api-reports/pocketpay-sdk.api.md, so an accidental signature change
* during refactoring shows up as a failing check instead of silent
* breakage for consumers of the SDK.
*
* Usage:
* node scripts/check-sdk-api.js Check the current API against the baseline (exit 1 on drift)
* node scripts/check-sdk-api.js --update Regenerate the baseline from the current API
*/
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const ROOT = path.resolve(__dirname, '..');
const DECL_FILE = path.join(ROOT, 'src/types/pocketpay-sdk.d.ts');
const BASELINE_FILE = path.join(ROOT, 'api-reports/pocketpay-sdk.api.md');
const MODULE_NAME = 'pocketpay-sdk';
function getStatementName(stmt) {
if (
(ts.isFunctionDeclaration(stmt) ||
ts.isClassDeclaration(stmt) ||
ts.isInterfaceDeclaration(stmt) ||
ts.isTypeAliasDeclaration(stmt)) &&
stmt.name
) {
return stmt.name.text;
}
if (ts.isVariableStatement(stmt)) {
const decl = stmt.declarationList.declarations[0];
if (decl && ts.isIdentifier(decl.name)) {
return decl.name.text;
}
}
return stmt.getText();
}
function extractApi() {
if (!fs.existsSync(DECL_FILE)) {
throw new Error(`SDK declaration file not found: ${path.relative(ROOT, DECL_FILE)}`);
}
const sourceText = fs.readFileSync(DECL_FILE, 'utf8');
const sourceFile = ts.createSourceFile(DECL_FILE, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
const moduleDecl = sourceFile.statements.find(
(s) => ts.isModuleDeclaration(s) && s.name.getText(sourceFile).replace(/^['"]|['"]$/g, '') === MODULE_NAME
);
if (!moduleDecl || !moduleDecl.body || !ts.isModuleBlock(moduleDecl.body)) {
throw new Error(`Could not find "declare module '${MODULE_NAME}'" in ${path.relative(ROOT, DECL_FILE)}`);
}
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments: true });
const members = moduleDecl.body.statements
.map((stmt) => ({
name: getStatementName(stmt),
text: printer.printNode(ts.EmitHint.Unspecified, stmt, sourceFile).trim(),
}))
.sort((a, b) => a.name.localeCompare(b.name));
return members;
}
function renderSnapshot(members) {
return [
'<!-- AUTO-GENERATED by `node scripts/check-sdk-api.js --update`. Do not hand-edit. -->',
`# Public API snapshot: ${MODULE_NAME}`,
'',
`Source: \`${path.relative(ROOT, DECL_FILE).replace(/\\/g, '/')}\``,
'',
'```ts',
...members.map((m) => m.text),
'```',
'',
].join('\n');
}
// Trims the common prefix/suffix and prints only the differing middle
// section as removed/added lines. Good enough for a handful of exports;
// not a general-purpose diff.
function diffLines(oldText, newText) {
const oldLines = oldText.split('\n');
const newLines = newText.split('\n');
let start = 0;
while (start < oldLines.length && start < newLines.length && oldLines[start] === newLines[start]) {
start++;
}
let oldEnd = oldLines.length - 1;
let newEnd = newLines.length - 1;
while (oldEnd >= start && newEnd >= start && oldLines[oldEnd] === newLines[newEnd]) {
oldEnd--;
newEnd--;
}
const removed = oldLines.slice(start, oldEnd + 1).map((l) => `- ${l}`);
const added = newLines.slice(start, newEnd + 1).map((l) => `+ ${l}`);
const out = [...removed, ...added].join('\n');
return out || '(no textual difference detected)';
}
function main() {
const update = process.argv.includes('--update');
const members = extractApi();
const snapshot = renderSnapshot(members);
if (update) {
fs.mkdirSync(path.dirname(BASELINE_FILE), { recursive: true });
fs.writeFileSync(BASELINE_FILE, snapshot, 'utf8');
console.log(
`Updated API baseline: ${path.relative(process.cwd(), BASELINE_FILE)} (${members.length} exported member(s))`
);
return;
}
if (!fs.existsSync(BASELINE_FILE)) {
console.error(`No API baseline found at ${path.relative(process.cwd(), BASELINE_FILE)}.`);
console.error('Run `node scripts/check-sdk-api.js --update` to create one.');
process.exit(1);
}
const baseline = fs.readFileSync(BASELINE_FILE, 'utf8');
if (baseline === snapshot) {
console.log(`pocketpay-sdk public API matches baseline (${members.length} exported member(s)).`);
return;
}
console.error(`pocketpay-sdk public API has changed (${path.relative(ROOT, DECL_FILE).replace(/\\/g, '/')}):\n`);
console.error(diffLines(baseline, snapshot));
console.error('\nIf this change is intentional:');
console.error(' 1. Note it under "Changed" in CHANGELOG.md (see docs/sdk-api-compatibility.md).');
console.error(' 2. Run `node scripts/check-sdk-api.js --update` to accept the new baseline.');
console.error(' 3. Commit the updated api-reports/pocketpay-sdk.api.md with your change.');
process.exit(1);
}
main();