Skip to content

Commit 7dcdf8c

Browse files
committed
fix(packaging): Include schemas in portable installs
Package structured output schemas with portable macOS distributions so MCP clients can load tool schemas from installed builds. Add verification coverage to catch missing schema files in portable install roots.
1 parent 5c3a781 commit 7dcdf8c

3 files changed

Lines changed: 95 additions & 0 deletions

File tree

scripts/package-macos-portable.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ if [[ "$UNIVERSAL" == "true" ]]; then
316316
mkdir -p "$UNIVERSAL_ROOT/bin" "$UNIVERSAL_ROOT/libexec"
317317
cp -R "$ARM64_ROOT/libexec/build" "$UNIVERSAL_ROOT/libexec/"
318318
cp -R "$ARM64_ROOT/libexec/manifests" "$UNIVERSAL_ROOT/libexec/"
319+
cp -R "$ARM64_ROOT/libexec/schemas" "$UNIVERSAL_ROOT/libexec/"
319320
cp -R "$ARM64_ROOT/libexec/bundled" "$UNIVERSAL_ROOT/libexec/"
320321
cp -R "$ARM64_ROOT/libexec/skills" "$UNIVERSAL_ROOT/libexec/"
321322
cp -R "$ARM64_ROOT/libexec/node_modules" "$UNIVERSAL_ROOT/libexec/"
@@ -364,6 +365,7 @@ install_node_runtime_for_arch "$ARCH" "$PORTABLE_ROOT/libexec/node-runtime"
364365

365366
cp -R "$PROJECT_ROOT/build" "$PORTABLE_ROOT/libexec/"
366367
cp -R "$PROJECT_ROOT/manifests" "$PORTABLE_ROOT/libexec/"
368+
cp -R "$PROJECT_ROOT/schemas" "$PORTABLE_ROOT/libexec/"
367369
cp -R "$PROJECT_ROOT/bundled" "$PORTABLE_ROOT/libexec/"
368370
cp -R "$PROJECT_ROOT/skills" "$PORTABLE_ROOT/libexec/"
369371
cp "$PROJECT_ROOT/package.json" "$PORTABLE_ROOT/libexec/package.json"

scripts/verify-portable-install.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ if [[ ! -d "$PORTABLE_ROOT/libexec/manifests" ]]; then
7979
echo "Missing manifests directory under libexec"
8080
exit 1
8181
fi
82+
if [[ ! -f "$PORTABLE_ROOT/libexec/schemas/structured-output/_defs/common.schema.json" ]]; then
83+
echo "Missing structured output common schema under libexec"
84+
exit 1
85+
fi
86+
if [[ ! -f "$PORTABLE_ROOT/libexec/schemas/structured-output/xcodebuildmcp.output.session-defaults/1.schema.json" ]]; then
87+
echo "Missing session defaults structured output schema under libexec"
88+
exit 1
89+
fi
8290
if [[ ! -x "$PORTABLE_ROOT/libexec/bundled/axe" ]]; then
8391
echo "Missing bundled axe binary under libexec"
8492
exit 1
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { execFileSync } from 'node:child_process';
2+
import type { ExecFileSyncOptionsWithStringEncoding } from 'node:child_process';
3+
import { chmodSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
4+
import { tmpdir } from 'node:os';
5+
import { join } from 'node:path';
6+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7+
8+
const verifyScriptPath = join(process.cwd(), 'scripts', 'verify-portable-install.sh');
9+
const execOptions = {
10+
encoding: 'utf8',
11+
stdio: 'pipe',
12+
} satisfies ExecFileSyncOptionsWithStringEncoding;
13+
14+
function createExecutable(path: string) {
15+
writeFileSync(path, '#!/usr/bin/env bash\nexit 0\n');
16+
chmodSync(path, 0o755);
17+
}
18+
19+
function createPortableRoot(root: string) {
20+
mkdirSync(join(root, 'bin'), { recursive: true });
21+
mkdirSync(join(root, 'libexec', 'manifests'), { recursive: true });
22+
mkdirSync(join(root, 'libexec', 'bundled', 'Frameworks'), { recursive: true });
23+
mkdirSync(join(root, 'libexec', 'skills'), { recursive: true });
24+
mkdirSync(join(root, 'libexec', 'schemas', 'structured-output', '_defs'), { recursive: true });
25+
mkdirSync(
26+
join(root, 'libexec', 'schemas', 'structured-output', 'xcodebuildmcp.output.session-defaults'),
27+
{ recursive: true },
28+
);
29+
30+
createExecutable(join(root, 'bin', 'xcodebuildmcp'));
31+
createExecutable(join(root, 'bin', 'xcodebuildmcp-doctor'));
32+
createExecutable(join(root, 'libexec', 'xcodebuildmcp'));
33+
createExecutable(join(root, 'libexec', 'node-runtime'));
34+
createExecutable(join(root, 'libexec', 'bundled', 'axe'));
35+
writeFileSync(
36+
join(root, 'libexec', 'schemas', 'structured-output', '_defs', 'common.schema.json'),
37+
'{}',
38+
);
39+
writeFileSync(
40+
join(
41+
root,
42+
'libexec',
43+
'schemas',
44+
'structured-output',
45+
'xcodebuildmcp.output.session-defaults',
46+
'1.schema.json',
47+
),
48+
'{}',
49+
);
50+
}
51+
52+
describe('verify-portable-install.sh', () => {
53+
let tempDir: string;
54+
55+
beforeEach(() => {
56+
tempDir = join(tmpdir(), `xbmcp-portable-verify-${process.pid}-${Date.now()}`);
57+
mkdirSync(tempDir, { recursive: true });
58+
});
59+
60+
afterEach(() => {
61+
rmSync(tempDir, { recursive: true, force: true });
62+
});
63+
64+
it('accepts a portable root containing structured output schemas', () => {
65+
const root = join(tempDir, 'portable-root');
66+
createPortableRoot(root);
67+
68+
expect(() => execFileSync(verifyScriptPath, ['--root', root], execOptions)).not.toThrow();
69+
});
70+
71+
it('rejects a portable root missing structured output schemas', () => {
72+
const root = join(tempDir, 'portable-root');
73+
createPortableRoot(root);
74+
rmSync(join(root, 'libexec', 'schemas'), { recursive: true, force: true });
75+
76+
try {
77+
execFileSync(verifyScriptPath, ['--root', root], execOptions);
78+
throw new Error('Expected verify-portable-install.sh to fail');
79+
} catch (error) {
80+
expect(error).toMatchObject({
81+
stdout: expect.stringContaining('Missing structured output common schema'),
82+
});
83+
}
84+
});
85+
});

0 commit comments

Comments
 (0)