Skip to content

Commit 316859e

Browse files
fix(cli): handle escaped single-quoted strings in schema change (#7321)
1 parent 594b176 commit 316859e

File tree

6 files changed

+109
-4
lines changed

6 files changed

+109
-4
lines changed

.changeset/brave-tigers-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-hive/cli': patch
3+
---
4+
5+
handle escaped single-quoted strings in schema changes
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Query {
2+
status: Status
3+
}
4+
5+
enum Status {
6+
ACTIVE
7+
INACTIVE @deprecated(reason: "Use 'DISABLED' instead, it's clearer")
8+
PENDING
9+
DISABLED
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type Query {
2+
status: Status
3+
}
4+
5+
enum Status {
6+
ACTIVE
7+
INACTIVE
8+
PENDING
9+
}

integration-tests/tests/cli/schema.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-process-env */
22
import { createHash } from 'node:crypto';
3+
import stripAnsi from 'strip-ansi';
34
import { ProjectType, RuleInstanceSeverityLevel } from 'testkit/gql/graphql';
45
import * as GraphQLSchema from 'testkit/gql/graphql';
56
import type { CompositeSchema } from '@hive/api/__generated__/types';
@@ -974,3 +975,33 @@ test.concurrent(
974975
).rejects.toThrow('Failed to auto-approve: Schema check has schema policy errors');
975976
},
976977
);
978+
979+
test.concurrent(
980+
'schema:check displays enum deprecation reason with single quotes correctly',
981+
async ({ expect }) => {
982+
const { createOrg } = await initSeed().createOwner();
983+
const { createProject } = await createOrg();
984+
const { createTargetAccessToken } = await createProject(ProjectType.Single);
985+
const { secret } = await createTargetAccessToken({});
986+
987+
await schemaPublish([
988+
'--registry.accessToken',
989+
secret,
990+
'--author',
991+
'Test',
992+
'--commit',
993+
'init',
994+
'fixtures/enum-with-deprecation-init.graphql',
995+
]);
996+
997+
const result = await schemaCheck([
998+
'--registry.accessToken',
999+
secret,
1000+
'fixtures/enum-with-deprecation-change.graphql',
1001+
]);
1002+
1003+
expect(stripAnsi(result)).toContain(
1004+
"Enum value Status.INACTIVE was deprecated with reason Use 'DISABLED' instead, it's clearer",
1005+
);
1006+
},
1007+
);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import colors from 'colors';
2+
import { boldQuotedWords } from '../../../packages/libraries/cli/src/helpers/texture/texture';
3+
4+
describe('boldQuotedWords', () => {
5+
test('handles simple single-quoted strings', () => {
6+
const input = "Changed value for 'foo'";
7+
const expected = `Changed value for ${colors.bold('foo')}`;
8+
expect(boldQuotedWords(input)).toBe(expected);
9+
});
10+
11+
test('handles simple double-quoted strings', () => {
12+
const input = 'Changed value for "foo"';
13+
const expected = `Changed value for ${colors.bold('foo')}`;
14+
expect(boldQuotedWords(input)).toBe(expected);
15+
});
16+
17+
test('handles multiple quoted strings', () => {
18+
const input = "Field 'name' on type 'User' was changed";
19+
const expected = `Field ${colors.bold('name')} on type ${colors.bold('User')} was changed`;
20+
expect(boldQuotedWords(input)).toBe(expected);
21+
});
22+
23+
test('handles string with no quotes', () => {
24+
const input = 'No quotes here';
25+
expect(boldQuotedWords(input)).toBe(input);
26+
});
27+
28+
test('handles escaped single quotes within single-quoted strings', () => {
29+
const input =
30+
"Enum value 'Status.INACTIVE' has deprecation reason 'Use \\'DISABLED\\' instead'";
31+
const expected = `Enum value ${colors.bold('Status.INACTIVE')} has deprecation reason ${colors.bold("Use 'DISABLED' instead")}`;
32+
expect(boldQuotedWords(input)).toBe(expected);
33+
});
34+
35+
test('handles escaped double quotes within double-quoted strings', () => {
36+
const input = 'Default value changed from "\\"test\\"" to "other"';
37+
const expected = `Default value changed from ${colors.bold('"test"')} to ${colors.bold('other')}`;
38+
expect(boldQuotedWords(input)).toBe(expected);
39+
});
40+
41+
test('handles apostrophes when quotes are escaped', () => {
42+
const input = "Reason 'Use \\'DISABLED\\' instead, it\\'s clearer'";
43+
const expected = `Reason ${colors.bold("Use 'DISABLED' instead, it's clearer")}`;
44+
expect(boldQuotedWords(input)).toBe(expected);
45+
});
46+
});

packages/libraries/cli/src/helpers/texture/texture.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ export const trimEnd = (value: string) => value.replace(/\s+$/g, '');
2121
* Convert quoted text to bolded text. Quotes are stripped.
2222
*/
2323
export const boldQuotedWords = (value: string) => {
24-
const singleQuotedTextRegex = /'([^']+)'/gim;
25-
const doubleQuotedTextRegex = /"([^"]+)"/gim;
24+
const singleQuotedTextRegex = /'((?:[^'\\]|\\.)+?)'/g;
25+
const doubleQuotedTextRegex = /"((?:[^"\\]|\\.)+?)"/g;
2626
return value
27-
.replace(singleQuotedTextRegex, (_, capturedValue: string) => colors.bold(capturedValue))
28-
.replace(doubleQuotedTextRegex, (_, capturedValue: string) => colors.bold(capturedValue));
27+
.replace(singleQuotedTextRegex, (_, capturedValue: string) =>
28+
colors.bold(capturedValue.replace(/\\'/g, "'")),
29+
)
30+
.replace(doubleQuotedTextRegex, (_, capturedValue: string) =>
31+
colors.bold(capturedValue.replace(/\\"/g, '"')),
32+
);
2933
};
3034

3135
export const prefixedInspect =

0 commit comments

Comments
 (0)