Skip to content

Commit beb6e1b

Browse files
cameroncookecodex
andcommitted
fix(version): Share version validation and restore single-quote output
Extract VERSION_REGEX and validateVersion into a shared module used by the generator and validation tests, so regex updates only happen in one place. Switch generated src/version.ts literals back to single quotes to align with Prettier and remove the eslint ignore carve-out for that generated file. Fixes #368 Fixes #369 Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent 54837d4 commit beb6e1b

5 files changed

Lines changed: 25 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- Added `toggle_connect_hardware_keyboard` tool to toggle the iOS Simulator hardware keyboard connection ([#346](https://github.com/getsentry/XcodeBuildMCP/issues/346)).
2424
- Fixed `xcode_tools_bridge_disconnect` immediately re-syncing proxied tools after a manual disconnect ([#343](https://github.com/getsentry/XcodeBuildMCP/issues/343)).
2525
- Stopped suggesting an unsupported `--device-id`/`deviceId` argument in the `device list` next-step hint for `device build`/`build_device`; device targeting flows through session defaults ([#350](https://github.com/getsentry/XcodeBuildMCP/pull/350) by [@MukundaKatta](https://github.com/MukundaKatta)).
26+
- Extracted shared version validation (`VERSION_REGEX`/`validateVersion`) for version generation and tests, and switched generated `src/version.ts` literals back to single quotes to avoid Prettier conflicts ([#368](https://github.com/getsentry/XcodeBuildMCP/issues/368), [#369](https://github.com/getsentry/XcodeBuildMCP/issues/369)).
2627

2728
## [2.3.2]
2829

eslint.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default [
1313
'coverage/**',
1414
'src/core/generated-plugins.ts',
1515
'src/core/generated-resources.ts',
16-
'src/version.ts',
1716
],
1817
},
1918
{

scripts/generate-version.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFile, writeFile } from 'node:fs/promises';
22
import path from 'node:path';
3+
import { validateVersion } from '../src/utils/version/version-validation.ts';
34

45
interface PackageJson {
56
name: string;
@@ -19,16 +20,6 @@ function parseGitHubOwnerAndName(url: string): { owner: string; name: string } {
1920
return { owner: match[1], name: match[2] };
2021
}
2122

22-
const VERSION_REGEX = /^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.\-]+)?(\+[a-zA-Z0-9.\-]+)?$/;
23-
24-
function validateVersion(name: string, value: string): void {
25-
if (!VERSION_REGEX.test(value)) {
26-
throw new Error(
27-
`Invalid ${name} in package.json: ${JSON.stringify(value)}. Expected a version string.`,
28-
);
29-
}
30-
}
31-
3223
async function main(): Promise<void> {
3324
const repoRoot = process.cwd();
3425
const packagePath = path.join(repoRoot, 'package.json');
@@ -49,12 +40,12 @@ async function main(): Promise<void> {
4940
validateVersion('macOSTemplateVersion', pkg.macOSTemplateVersion);
5041

5142
const content =
52-
`export const version = ${JSON.stringify(pkg.version)};\n` +
53-
`export const iOSTemplateVersion = ${JSON.stringify(pkg.iOSTemplateVersion)};\n` +
54-
`export const macOSTemplateVersion = ${JSON.stringify(pkg.macOSTemplateVersion)};\n` +
55-
`export const packageName = ${JSON.stringify(pkg.name)};\n` +
56-
`export const repositoryOwner = ${JSON.stringify(repo.owner)};\n` +
57-
`export const repositoryName = ${JSON.stringify(repo.name)};\n`;
43+
`export const version = '${pkg.version}';\n` +
44+
`export const iOSTemplateVersion = '${pkg.iOSTemplateVersion}';\n` +
45+
`export const macOSTemplateVersion = '${pkg.macOSTemplateVersion}';\n` +
46+
`export const packageName = '${pkg.name}';\n` +
47+
`export const repositoryOwner = '${repo.owner}';\n` +
48+
`export const repositoryName = '${repo.name}';\n`;
5849

5950
await writeFile(versionPath, content, 'utf8');
6051
}

src/utils/__tests__/generate-version-validation.test.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { describe, it, expect } from 'vitest';
2-
3-
// We cannot easily import the generate-version script (it runs main() immediately),
4-
// so we extract and test the core logic: VERSION_REGEX and JSON.stringify defense.
5-
6-
const VERSION_REGEX = /^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.\-]+)?(\+[a-zA-Z0-9.\-]+)?$/;
2+
import { VERSION_REGEX, validateVersion } from '../version/version-validation.ts';
73

84
describe('generate-version: VERSION_REGEX validation', () => {
95
it('accepts standard semver', () => {
@@ -47,25 +43,14 @@ describe('generate-version: VERSION_REGEX validation', () => {
4743
});
4844
});
4945

50-
describe('generate-version: JSON.stringify defense-in-depth', () => {
51-
it('produces safe code even if a value somehow contains quotes', () => {
52-
const malicious = "1.0.0'; process.exit(1); //";
53-
const generated = `const version = ${JSON.stringify(malicious)};\n`;
54-
// The output should use escaped double-quoted string, not break out
55-
expect(generated).toContain('"1.0.0');
56-
expect(generated).not.toContain("'1.0.0'; process.exit(1)");
57-
// Should be parseable JS (using const instead of export for Function() compat)
58-
expect(() => new Function(generated)).not.toThrow();
46+
describe('generate-version: validateVersion', () => {
47+
it('throws for invalid versions', () => {
48+
expect(() => validateVersion('version', "1.0.0'; process.exit(1); //")).toThrow(
49+
/Invalid version in package\.json/,
50+
);
5951
});
6052

61-
it('JSON.stringify properly escapes backslashes and control characters', () => {
62-
const tricky = '1.0.0\n";process.exit(1);//';
63-
const serialized = JSON.stringify(tricky);
64-
// The newline should be escaped as \\n, and the quote should be escaped
65-
expect(serialized).toContain('\\n');
66-
expect(serialized).toContain('\\"');
67-
// The resulting assignment should be valid JS
68-
const code = `const v = ${serialized};`;
69-
expect(() => new Function(code)).not.toThrow();
53+
it('does not throw for valid versions', () => {
54+
expect(() => validateVersion('version', '1.0.0-alpha.1+meta')).not.toThrow();
7055
});
7156
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const VERSION_REGEX = /^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.\-]+)?(\+[a-zA-Z0-9.\-]+)?$/;
2+
3+
export function validateVersion(name: string, value: string): void {
4+
if (!VERSION_REGEX.test(value)) {
5+
throw new Error(
6+
`Invalid ${name} in package.json: ${JSON.stringify(value)}. Expected a version string.`,
7+
);
8+
}
9+
}

0 commit comments

Comments
 (0)