@finggujadhav/compiler 0.9.6 β 0.9.7
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/cli.js +44 -2
- package/engine.js +1 -1
- package/package.json +6 -3
- package/snapshot.js +384 -0
package/cli.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* FingguFlux CLI
|
|
4
|
-
* v0.9.
|
|
4
|
+
* v0.9.7-RC Release Candidate Preparation
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { scanFiles, getProjectFiles } from './scanner.js';
|
|
9
9
|
import { CompilerEngine } from './engine.js';
|
|
10
10
|
import { runThemeCheck, printThemeCheckReport } from './theme-check.js';
|
|
11
|
+
import {
|
|
12
|
+
generateSnapshot, writeSnapshot, runSnapshotCompare, printSnapshotReport
|
|
13
|
+
} from './snapshot.js';
|
|
11
14
|
|
|
12
15
|
const args = process.argv.slice(2);
|
|
13
16
|
const command = args[0] || 'build';
|
|
@@ -38,9 +41,12 @@ async function main() {
|
|
|
38
41
|
case 'theme-check':
|
|
39
42
|
await runThemeCheckCommand();
|
|
40
43
|
break;
|
|
44
|
+
case 'snapshot':
|
|
45
|
+
await runSnapshotCommand();
|
|
46
|
+
break;
|
|
41
47
|
default:
|
|
42
48
|
console.error(`Unknown command: ${command}`);
|
|
43
|
-
console.log('Available commands: build, analyze, doctor, a11y, theme-check');
|
|
49
|
+
console.log('Available commands: build, analyze, doctor, a11y, theme-check, snapshot');
|
|
44
50
|
process.exit(1);
|
|
45
51
|
}
|
|
46
52
|
}
|
|
@@ -253,6 +259,42 @@ async function runThemeCheckCommand() {
|
|
|
253
259
|
if (!result.pass) process.exit(1);
|
|
254
260
|
}
|
|
255
261
|
|
|
262
|
+
async function runSnapshotCommand() {
|
|
263
|
+
const doWrite = args.includes('--write');
|
|
264
|
+
const doCompare = args.includes('--compare');
|
|
265
|
+
const verbose = args.includes('--verbose');
|
|
266
|
+
|
|
267
|
+
if (!doWrite && !doCompare) {
|
|
268
|
+
console.error('Usage: finggu snapshot --write (generate/update baseline)');
|
|
269
|
+
console.error(' finggu snapshot --compare (CI break-guard diff)');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (doWrite) {
|
|
274
|
+
console.log('\nπΈ Generating API surface snapshotβ¦');
|
|
275
|
+
const snap = writeSnapshot();
|
|
276
|
+
const dim = snap.surface;
|
|
277
|
+
console.log(` β
CSS tokens : ${dim.cssTokens.length}`);
|
|
278
|
+
console.log(` β
JS exports : ${dim.jsExports.length}`);
|
|
279
|
+
console.log(` β
CLI commands : ${dim.cliCommands.length}`);
|
|
280
|
+
console.log(` β
Component CSS : ${dim.componentFiles.length}`);
|
|
281
|
+
console.log(`\nβ¨ Snapshot written β packages/core/API_SURFACE_SNAPSHOT.json (v${snap.version})\n`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (doCompare) {
|
|
286
|
+
let report;
|
|
287
|
+
try {
|
|
288
|
+
report = runSnapshotCompare();
|
|
289
|
+
} catch (e) {
|
|
290
|
+
console.error('\nβ', e.message);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
printSnapshotReport(report, verbose);
|
|
294
|
+
if (!report.pass) process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
256
298
|
main().catch(err => {
|
|
257
299
|
console.error('Command failed:', err);
|
|
258
300
|
process.exit(1);
|
package/engine.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finggujadhav/compiler",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "The FingguFlux static analysis and selector hardening engine.",
|
|
5
5
|
"main": "engine.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,12 +11,15 @@
|
|
|
11
11
|
"scanner.js",
|
|
12
12
|
"cli.js",
|
|
13
13
|
"a11y.js",
|
|
14
|
-
"theme-check.js"
|
|
14
|
+
"theme-check.js",
|
|
15
|
+
"snapshot.js"
|
|
15
16
|
],
|
|
16
17
|
"type": "module",
|
|
17
18
|
"scripts": {
|
|
18
19
|
"test": "node --test test/theme-check.test.js",
|
|
19
|
-
"test:
|
|
20
|
+
"test:snapshot": "node --test test/snapshot.test.js",
|
|
21
|
+
"test:all": "node --test test/**/*.test.js",
|
|
22
|
+
"release-check": "node cli.js theme-check && node cli.js snapshot --compare"
|
|
20
23
|
},
|
|
21
24
|
"engines": {
|
|
22
25
|
"node": ">=16.0.0"
|
package/snapshot.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FingguFlux API Surface Snapshot Engine
|
|
3
|
+
* v0.9.7-RC β Release Candidate Preparation
|
|
4
|
+
*
|
|
5
|
+
* Generates an authoritative, machine-readable snapshot of the ENTIRE
|
|
6
|
+
* public API surface and diffs it against a stored baseline to detect:
|
|
7
|
+
* β’ BREAKING: removed CSS tokens
|
|
8
|
+
* β’ BREAKING: removed JS exports
|
|
9
|
+
* β’ BREAKING: removed CLI commands
|
|
10
|
+
* β’ BREAKING: removed component CSS files
|
|
11
|
+
* β’ WARNING: added tokens (unregistered additions)
|
|
12
|
+
* β’ WARNING: deprecated APIs approaching removal date
|
|
13
|
+
*
|
|
14
|
+
* Zero runtime overhead β this module is a build/CI tool only.
|
|
15
|
+
* It produces no browser-bound code.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
|
|
24
|
+
// βββ Paths ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
|
|
26
|
+
const CORE_DIR = path.resolve(__dirname, '../core');
|
|
27
|
+
const COMPILER_DIR = path.resolve(__dirname, '../compiler');
|
|
28
|
+
const JS_HELPER_DIR = path.resolve(__dirname, '../js-helper');
|
|
29
|
+
const SNAPSHOT_PATH = path.resolve(CORE_DIR, 'API_SURFACE_SNAPSHOT.json');
|
|
30
|
+
const REGISTRY_PATH = path.resolve(CORE_DIR, 'TOKENS_REGISTRY.json');
|
|
31
|
+
const DEPRECATION_PATH = path.resolve(CORE_DIR, 'DEPRECATION_LOG.json');
|
|
32
|
+
|
|
33
|
+
// βββ CSS token extractor ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract all --ff-* CSS custom property definitions from a CSS string.
|
|
37
|
+
* Excludes var() references (same logic as theme-check.js).
|
|
38
|
+
* @param {string} css
|
|
39
|
+
* @returns {string[]} sorted token names
|
|
40
|
+
*/
|
|
41
|
+
export function extractCSSTokens(css) {
|
|
42
|
+
const tokens = new Set();
|
|
43
|
+
const re = /(--ff-[\w-]+)\s*:/g;
|
|
44
|
+
let m;
|
|
45
|
+
while ((m = re.exec(css)) !== null) {
|
|
46
|
+
const before = css.slice(0, m.index);
|
|
47
|
+
const lastVar = before.lastIndexOf('var(');
|
|
48
|
+
const lastClose = before.lastIndexOf(')');
|
|
49
|
+
if (lastVar !== -1 && lastVar > lastClose) continue;
|
|
50
|
+
tokens.add(m[1]);
|
|
51
|
+
}
|
|
52
|
+
return [...tokens].sort();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Collect all --ff-* CSS tokens from tokens.css (includes dark overrides).
|
|
57
|
+
* @returns {string[]}
|
|
58
|
+
*/
|
|
59
|
+
export function collectCSSTokens() {
|
|
60
|
+
const tokensCss = path.join(CORE_DIR, 'tokens.css');
|
|
61
|
+
if (!fs.existsSync(tokensCss)) return [];
|
|
62
|
+
return extractCSSTokens(fs.readFileSync(tokensCss, 'utf8'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// βββ Component surface collector βββββββββββββββββββββββββββββββββββββββββββββ
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Collect all component CSS filenames published in @finggujadhav/core.
|
|
69
|
+
* @returns {string[]} sorted list of component file basenames (e.g. "button.css")
|
|
70
|
+
*/
|
|
71
|
+
export function collectComponentFiles() {
|
|
72
|
+
const compDir = path.join(CORE_DIR, 'components');
|
|
73
|
+
if (!fs.existsSync(compDir)) return [];
|
|
74
|
+
return fs.readdirSync(compDir)
|
|
75
|
+
.filter(f => f.endsWith('.css'))
|
|
76
|
+
.sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// βββ JS export collector βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract named exports from a TypeScript source file.
|
|
83
|
+
* Handles: export function/class/const/type/interface/enum declarations
|
|
84
|
+
* and `export { ... }` re-export blocks.
|
|
85
|
+
* @param {string} src file content
|
|
86
|
+
* @returns {string[]} sorted export names
|
|
87
|
+
*/
|
|
88
|
+
export function extractTSExports(src) {
|
|
89
|
+
const names = new Set();
|
|
90
|
+
|
|
91
|
+
// Named declarations: export function foo / export const foo / export type Foo / etc.
|
|
92
|
+
const declRe = /^export\s+(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([\w$]+)/gm;
|
|
93
|
+
let m;
|
|
94
|
+
while ((m = declRe.exec(src)) !== null) names.add(m[1]);
|
|
95
|
+
|
|
96
|
+
// Re-export blocks: export { foo, bar as baz }
|
|
97
|
+
const blockRe = /export\s*\{([^}]+)\}/g;
|
|
98
|
+
while ((m = blockRe.exec(src)) !== null) {
|
|
99
|
+
m[1].split(',').forEach(part => {
|
|
100
|
+
const alias = part.trim().split(/\s+as\s+/);
|
|
101
|
+
const name = (alias[1] || alias[0]).trim();
|
|
102
|
+
if (name) names.add(name);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return [...names].sort();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Collect all public JS exports from @finggujadhav/js-helper src.
|
|
111
|
+
* @returns {string[]}
|
|
112
|
+
*/
|
|
113
|
+
export function collectJSExports() {
|
|
114
|
+
const srcDir = path.join(JS_HELPER_DIR, 'src');
|
|
115
|
+
if (!fs.existsSync(srcDir)) return [];
|
|
116
|
+
|
|
117
|
+
const allExports = new Set();
|
|
118
|
+
const files = fs.readdirSync(srcDir).filter(f => f.endsWith('.ts') && f !== 'index.ts');
|
|
119
|
+
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const src = fs.readFileSync(path.join(srcDir, file), 'utf8');
|
|
122
|
+
extractTSExports(src).forEach(e => allExports.add(e));
|
|
123
|
+
}
|
|
124
|
+
return [...allExports].sort();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// βββ CLI command surface collector βββββββββββββββββββββββββββββββββββββββββββ
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extract CLI command names from cli.js by scanning the switch statement.
|
|
131
|
+
* @returns {string[]} sorted command names
|
|
132
|
+
*/
|
|
133
|
+
export function collectCLICommands() {
|
|
134
|
+
const cliPath = path.join(COMPILER_DIR, 'cli.js');
|
|
135
|
+
if (!fs.existsSync(cliPath)) return [];
|
|
136
|
+
|
|
137
|
+
const src = fs.readFileSync(cliPath, 'utf8');
|
|
138
|
+
const commands = new Set();
|
|
139
|
+
const re = /case\s+'([\w:-]+)'\s*:/g;
|
|
140
|
+
let m;
|
|
141
|
+
while ((m = re.exec(src)) !== null) commands.add(m[1]);
|
|
142
|
+
return [...commands].sort();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// βββ Snapshot builder βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate a complete API surface snapshot object.
|
|
149
|
+
* @returns {{ generatedAt: string, version: string, surface: object }}
|
|
150
|
+
*/
|
|
151
|
+
export function generateSnapshot() {
|
|
152
|
+
// Read version from core package.json
|
|
153
|
+
const corePkg = JSON.parse(fs.readFileSync(path.join(CORE_DIR, 'package.json'), 'utf8'));
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
$schema: 'https://fingguflux.dev/schemas/api-surface-snapshot.json',
|
|
157
|
+
generatedAt: new Date().toISOString(),
|
|
158
|
+
version: corePkg.version,
|
|
159
|
+
surface: {
|
|
160
|
+
cssTokens: collectCSSTokens(),
|
|
161
|
+
jsExports: collectJSExports(),
|
|
162
|
+
cliCommands: collectCLICommands(),
|
|
163
|
+
componentFiles: collectComponentFiles(),
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Write the snapshot to disk at packages/core/API_SURFACE_SNAPSHOT.json.
|
|
170
|
+
* @param {object} [snapshotOverride] optionally pass a pre-built snapshot
|
|
171
|
+
* @returns {object} the written snapshot
|
|
172
|
+
*/
|
|
173
|
+
export function writeSnapshot(snapshotOverride) {
|
|
174
|
+
const snap = snapshotOverride ?? generateSnapshot();
|
|
175
|
+
fs.writeFileSync(SNAPSHOT_PATH, JSON.stringify(snap, null, 4), 'utf8');
|
|
176
|
+
return snap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// βββ Snapshot comparator ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Compare two API surface sets and produce structured diff.
|
|
183
|
+
* @param {string[]} baseline items in the stored snapshot
|
|
184
|
+
* @param {string[]} current items in the freshly generated snapshot
|
|
185
|
+
* @param {string} label human-readable label for reporting
|
|
186
|
+
* @returns {{ removed: string[], added: string[] }}
|
|
187
|
+
*/
|
|
188
|
+
export function diffSurface(baseline, current, label) {
|
|
189
|
+
const baseSet = new Set(baseline);
|
|
190
|
+
const currentSet = new Set(current);
|
|
191
|
+
|
|
192
|
+
const removed = [...baseSet].filter(x => !currentSet.has(x)).sort();
|
|
193
|
+
const added = [...currentSet].filter(x => !baseSet.has(x)).sort();
|
|
194
|
+
|
|
195
|
+
return { label, removed, added };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Load the deprecation log.
|
|
200
|
+
* @returns {Array<{name:string, type:string, since:string, until:string, replacement:string|null, reason:string}>}
|
|
201
|
+
*/
|
|
202
|
+
export function loadDeprecationLog() {
|
|
203
|
+
if (!fs.existsSync(DEPRECATION_PATH)) return [];
|
|
204
|
+
return JSON.parse(fs.readFileSync(DEPRECATION_PATH, 'utf8')).deprecations ?? [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Check for deprecated items that are past their `until` version and
|
|
209
|
+
* should have been removed β these are breaking-change guards.
|
|
210
|
+
* @param {string} currentVersion e.g. "0.9.7"
|
|
211
|
+
* @returns {Array} overdue deprecations
|
|
212
|
+
*/
|
|
213
|
+
export function detectOverdueDeprecations(currentVersion) {
|
|
214
|
+
const log = loadDeprecationLog();
|
|
215
|
+
return log.filter(d => {
|
|
216
|
+
if (!d.until) return false;
|
|
217
|
+
return compareVersions(d.until, currentVersion) < 0;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Simple semver comparator (major.minor.patch only). */
|
|
222
|
+
function compareVersions(a, b) {
|
|
223
|
+
const pa = a.split('.').map(Number);
|
|
224
|
+
const pb = b.split('.').map(Number);
|
|
225
|
+
for (let i = 0; i < 3; i++) {
|
|
226
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return 1;
|
|
227
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return -1;
|
|
228
|
+
}
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// βββ Snapshot comparison runner βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Full snapshot comparison pipeline.
|
|
236
|
+
* Loads the stored baseline, generates a fresh snapshot, diffs all surface
|
|
237
|
+
* dimensions, detects overdue deprecations, and returns a structured report.
|
|
238
|
+
*
|
|
239
|
+
* @param {object} [opts]
|
|
240
|
+
* @param {object} [opts.currentSnapshot] override the live snapshot (for tests)
|
|
241
|
+
* @param {object} [opts.baselineSnapshot] override the stored baseline (for tests)
|
|
242
|
+
* @param {string} [opts.currentVersion] override version string (for tests)
|
|
243
|
+
* @returns {{
|
|
244
|
+
* pass: boolean,
|
|
245
|
+
* breakingChanges: string[],
|
|
246
|
+
* warnings: string[],
|
|
247
|
+
* diffs: object[],
|
|
248
|
+
* overdueDeprecations: object[]
|
|
249
|
+
* }}
|
|
250
|
+
*/
|
|
251
|
+
export function runSnapshotCompare(opts = {}) {
|
|
252
|
+
// --- Load baseline ---
|
|
253
|
+
const baseline = opts.baselineSnapshot ?? (() => {
|
|
254
|
+
if (!fs.existsSync(SNAPSHOT_PATH)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
'API_SURFACE_SNAPSHOT.json not found. ' +
|
|
257
|
+
"Run 'finggu snapshot --write' to generate the initial baseline."
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
return JSON.parse(fs.readFileSync(SNAPSHOT_PATH, 'utf8'));
|
|
261
|
+
})();
|
|
262
|
+
|
|
263
|
+
// --- Generate current state ---
|
|
264
|
+
const current = opts.currentSnapshot ?? generateSnapshot();
|
|
265
|
+
|
|
266
|
+
// --- Diff each surface dimension ---
|
|
267
|
+
const diffs = [
|
|
268
|
+
diffSurface(baseline.surface.cssTokens, current.surface.cssTokens, 'CSS Tokens'),
|
|
269
|
+
diffSurface(baseline.surface.jsExports, current.surface.jsExports, 'JS Exports'),
|
|
270
|
+
diffSurface(baseline.surface.cliCommands, current.surface.cliCommands, 'CLI Commands'),
|
|
271
|
+
diffSurface(baseline.surface.componentFiles, current.surface.componentFiles, 'Component Files'),
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const breakingChanges = [];
|
|
275
|
+
const warnings = [];
|
|
276
|
+
|
|
277
|
+
for (const diff of diffs) {
|
|
278
|
+
// Removals are BREAKING
|
|
279
|
+
for (const item of diff.removed) {
|
|
280
|
+
breakingChanges.push(`[BREAKING] ${diff.label}: '${item}' was removed from the public API surface.`);
|
|
281
|
+
}
|
|
282
|
+
// Additions are warnings (undocumented surface growth)
|
|
283
|
+
for (const item of diff.added) {
|
|
284
|
+
warnings.push(`[WARN] ${diff.label}: '${item}' is new β ensure it is intentional and documented.`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// --- Overdue deprecations (items past their `until` date are breaking) ---
|
|
289
|
+
const currentVersion = opts.currentVersion ?? current.version;
|
|
290
|
+
const overdue = detectOverdueDeprecations(currentVersion);
|
|
291
|
+
for (const d of overdue) {
|
|
292
|
+
breakingChanges.push(
|
|
293
|
+
`[BREAKING] Deprecation overdue: '${d.name}' (${d.type}) was scheduled for removal in v${d.until} ` +
|
|
294
|
+
`but is still present. Remove it or extend the deprecation window.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
pass: breakingChanges.length === 0,
|
|
300
|
+
breakingChanges,
|
|
301
|
+
warnings,
|
|
302
|
+
diffs,
|
|
303
|
+
overdueDeprecations: overdue,
|
|
304
|
+
baselineVersion: baseline.version,
|
|
305
|
+
currentVersion,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// βββ CLI printer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
310
|
+
|
|
311
|
+
const C = {
|
|
312
|
+
reset: '\x1b[0m',
|
|
313
|
+
bold: '\x1b[1m',
|
|
314
|
+
red: '\x1b[31m',
|
|
315
|
+
green: '\x1b[32m',
|
|
316
|
+
yellow: '\x1b[33m',
|
|
317
|
+
cyan: '\x1b[36m',
|
|
318
|
+
gray: '\x1b[90m',
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const line = (ch = 'β', n = 54) => ch.repeat(n);
|
|
322
|
+
|
|
323
|
+
export function printSnapshotReport(report, verbose = false) {
|
|
324
|
+
const { pass, breakingChanges, warnings, diffs, overdueDeprecations,
|
|
325
|
+
baselineVersion, currentVersion } = report;
|
|
326
|
+
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log(`${C.bold}${C.cyan}πΈ FingguFlux API Surface Snapshot β Drift Audit${C.reset}`);
|
|
329
|
+
console.log(C.gray + line() + C.reset);
|
|
330
|
+
console.log(` Baseline : ${C.bold}v${baselineVersion}${C.reset}`);
|
|
331
|
+
console.log(` Current : ${C.bold}v${currentVersion}${C.reset}`);
|
|
332
|
+
console.log('');
|
|
333
|
+
|
|
334
|
+
// Surface summary
|
|
335
|
+
for (const diff of diffs) {
|
|
336
|
+
const removedCount = diff.removed.length;
|
|
337
|
+
const addedCount = diff.added.length;
|
|
338
|
+
const icon = removedCount ? C.red + 'β' :
|
|
339
|
+
addedCount ? C.yellow + 'β ' :
|
|
340
|
+
C.green + 'β';
|
|
341
|
+
console.log(` ${icon}${C.reset} ${diff.label.padEnd(18)} ` +
|
|
342
|
+
`${removedCount ? C.red + removedCount + ' removed' + C.reset : 'β'} ` +
|
|
343
|
+
`${addedCount ? C.yellow + addedCount + ' added' + C.reset : ''}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Breaking changes
|
|
347
|
+
if (breakingChanges.length > 0) {
|
|
348
|
+
console.log('');
|
|
349
|
+
console.log(C.red + C.bold + ` β ${breakingChanges.length} Breaking Change(s) Detected` + C.reset);
|
|
350
|
+
for (const msg of breakingChanges) {
|
|
351
|
+
console.log(` ${C.red}${msg}${C.reset}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Warnings
|
|
356
|
+
if (warnings.length > 0 && (verbose || breakingChanges.length === 0)) {
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log(C.yellow + C.bold + ` β ${warnings.length} Warning(s)` + C.reset);
|
|
359
|
+
for (const msg of warnings) {
|
|
360
|
+
console.log(` ${C.yellow}${msg}${C.reset}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Overdue deprecations
|
|
365
|
+
if (overdueDeprecations.length > 0) {
|
|
366
|
+
console.log('');
|
|
367
|
+
console.log(C.red + C.bold + ` π¨ ${overdueDeprecations.length} Overdue Deprecation(s)` + C.reset);
|
|
368
|
+
for (const d of overdueDeprecations) {
|
|
369
|
+
console.log(` ${C.red}β’ ${d.name} (${d.type}) β scheduled removal: v${d.until}${C.reset}`);
|
|
370
|
+
if (d.replacement) console.log(` ${C.gray}β replacement: ${d.replacement}${C.reset}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
console.log('');
|
|
375
|
+
console.log(C.gray + line() + C.reset);
|
|
376
|
+
|
|
377
|
+
if (pass) {
|
|
378
|
+
console.log(`${C.green}${C.bold}β¨ API surface STABLE. No breaking changes detected.${C.reset}`);
|
|
379
|
+
} else {
|
|
380
|
+
console.log(`${C.red}${C.bold}π« API surface has BREAKING CHANGES. Release blocked.${C.reset}`);
|
|
381
|
+
console.log(`${C.gray} Fix all [BREAKING] issues before bumping version.${C.reset}`);
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
}
|