Skip to content

Commit 8897d37

Browse files
committed
feat: add MCP package validation script and integrate into npm publish workflow
1 parent 87cd12d commit 8897d37

3 files changed

Lines changed: 121 additions & 2 deletions

File tree

.github/workflows/npm-publish.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@ jobs:
2929
npm run build-pack
3030
node scripts/build-task.js
3131
32+
- name: Validate MCP package contents
33+
run: npm run validate:package --workspace=@igniteui/mcp-server -- --expected-version "$VERSION"
34+
3235
- name: Define npm tag
3336
run: |
34-
if [[ ${VERSION} == *"alpha"* || ${VERSION} == *"beta"* || ${VERSION} == *"rc"* ]]; then echo "NPM_TAG=next"; else echo "NPM_TAG=latest"; fi >> $GITHUB_ENV
35-
echo ${NPM_TAG}
37+
if [[ ${VERSION} == *"alpha"* || ${VERSION} == *"beta"* || ${VERSION} == *"rc"* ]]; then
38+
NPM_TAG=next
39+
else
40+
NPM_TAG=latest
41+
fi
42+
echo "NPM_TAG=$NPM_TAG" >> $GITHUB_ENV
43+
echo "$NPM_TAG"
3644
3745
- name: Publish packages
3846
# use npm run as yarn run changes the registry and publishes to https://registry.yarnpkg.com

packages/igniteui-mcp/igniteui-doc-mcp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"test:watch": "vitest",
3939
"coverage": "vitest run --coverage",
4040
"build:db": "npx tsx scripts/build-db.ts",
41+
"validate:package": "npx tsx scripts/validate-package.ts",
4142
"inspector": "npx @modelcontextprotocol/inspector dist/index.js",
4243
"clear": "npx tsx -e \"import{rmSync}from'fs';rmSync('dist',{recursive:true,force:true})\"",
4344
"clear:angular": "npx tsx -e \"import{rmSync}from'fs';['docs_processing','docs_prepeared','docs_final'].forEach(d=>{rmSync('dist/'+d+'/angular',{recursive:true,force:true})})\"",
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { readdirSync, readFileSync, statSync, existsSync } from "fs";
2+
import { join, resolve } from "path";
3+
import { fileURLToPath } from "url";
4+
5+
const PKG_ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
6+
const DB_PATH = join(PKG_ROOT, "dist", "igniteui-docs.db");
7+
const DB_MIN_BYTES = 20 * 1024 * 1024; // 20 MB minimum for the SQLite DB
8+
const DOCS_ROOT = join(PKG_ROOT, "docs");
9+
const FRAMEWORK_DIRS = ["angular-api", "react-api", "webcomponents-api", "blazor-api"];
10+
const PACKAGE_MIN_BYTES = 300 * 1024; // 300 KB minimum for each package
11+
12+
const errors: string[] = [];
13+
14+
function getExpectedVersion(): string | null {
15+
const idx = process.argv.indexOf("--expected-version");
16+
if (idx >= 0 && process.argv[idx + 1]) return process.argv[idx + 1].replace(/^v/, "");
17+
if (process.env.EXPECTED_VERSION) return process.env.EXPECTED_VERSION.replace(/^v/, "");
18+
return null;
19+
}
20+
21+
const expectedVersion = getExpectedVersion();
22+
if (expectedVersion) {
23+
const pkgJsonPath = join(PKG_ROOT, "package.json");
24+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
25+
if (pkgJson.version !== expectedVersion) {
26+
errors.push(`package.json version mismatch: got ${pkgJson.version}, expected ${expectedVersion}`);
27+
} else {
28+
console.log(`OK ver package.json ${pkgJson.version}`);
29+
}
30+
31+
const serverJsonPath = join(PKG_ROOT, "server.json");
32+
if (!existsSync(serverJsonPath)) {
33+
errors.push(`server.json missing: ${serverJsonPath}`);
34+
} else {
35+
const serverJson = JSON.parse(readFileSync(serverJsonPath, "utf8"));
36+
if (serverJson.version !== expectedVersion) {
37+
errors.push(`server.json version mismatch: got ${serverJson.version}, expected ${expectedVersion}`);
38+
} else {
39+
console.log(`OK ver server.json ${serverJson.version}`);
40+
}
41+
const pkgs: Array<{ identifier?: string; version?: string }> = serverJson.packages ?? [];
42+
if (pkgs.length === 0) {
43+
errors.push(`server.json has no entries in "packages"`);
44+
}
45+
pkgs.forEach((p, i) => {
46+
const label = p.identifier ?? `packages[${i}]`;
47+
if (p.version !== expectedVersion) {
48+
errors.push(`server.json ${label} version mismatch: got ${p.version}, expected ${expectedVersion}`);
49+
} else {
50+
console.log(`OK ver server.json ${label.padEnd(20)} ${p.version}`);
51+
}
52+
});
53+
}
54+
}
55+
56+
function formatSize(bytes: number): string {
57+
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
58+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;
59+
return `${bytes} B`;
60+
}
61+
62+
function dirSize(dir: string): number {
63+
let total = 0;
64+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
65+
const full = join(dir, entry.name);
66+
if (entry.isDirectory()) total += dirSize(full);
67+
else if (entry.isFile()) total += statSync(full).size;
68+
}
69+
return total;
70+
}
71+
72+
if (!existsSync(DB_PATH)) {
73+
errors.push(`DB missing: ${DB_PATH}`);
74+
} else {
75+
const size = statSync(DB_PATH).size;
76+
if (size < DB_MIN_BYTES) {
77+
errors.push(`DB too small: ${formatSize(size)} < ${formatSize(DB_MIN_BYTES)} (${DB_PATH})`);
78+
} else {
79+
console.log(`OK db ${formatSize(size)} ${DB_PATH}`);
80+
}
81+
}
82+
83+
for (const framework of FRAMEWORK_DIRS) {
84+
const frameworkDir = join(DOCS_ROOT, framework);
85+
if (!existsSync(frameworkDir)) {
86+
errors.push(`Docs framework dir missing: ${frameworkDir}`);
87+
continue;
88+
}
89+
const packages = readdirSync(frameworkDir, { withFileTypes: true }).filter(e => e.isDirectory());
90+
if (packages.length === 0) {
91+
errors.push(`No package subfolders in ${frameworkDir}`);
92+
continue;
93+
}
94+
for (const pkg of packages) {
95+
const pkgDir = join(frameworkDir, pkg.name);
96+
const size = dirSize(pkgDir);
97+
if (size < PACKAGE_MIN_BYTES) {
98+
errors.push(`Package too small: ${framework}/${pkg.name} = ${formatSize(size)} < ${formatSize(PACKAGE_MIN_BYTES)}`);
99+
} else {
100+
console.log(`OK pkg ${formatSize(size).padStart(10)} ${framework}/${pkg.name}`);
101+
}
102+
}
103+
}
104+
105+
if (errors.length > 0) {
106+
console.error(`\nValidation failed with ${errors.length} error(s):`);
107+
for (const e of errors) console.error(` - ${e}`);
108+
process.exit(1);
109+
}
110+
console.log("\nAll checks passed.");

0 commit comments

Comments
 (0)