@graphql-inspector/diff-command 3.1.4 → 3.4.0
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/{dist/index.d.ts → index.d.ts} +0 -0
- package/{dist/index.js → index.js} +9 -11
- package/{dist/index.mjs → index.mjs} +9 -11
- package/package.json +30 -33
- package/__tests__/assets/onComplete.js +0 -1
- package/__tests__/assets/rule.js +0 -1
- package/__tests__/diff-command.spec.ts +0 -154
- package/dist/package.json +0 -48
- package/src/index.ts +0 -283
|
File without changes
|
|
@@ -10,9 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
|
|
11
11
|
function handler(input) {
|
|
12
12
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
13
|
-
const onComplete = input.onComplete
|
|
14
|
-
? resolveCompletionHandler(input.onComplete)
|
|
15
|
-
: failOnBreakingChanges;
|
|
13
|
+
const onComplete = input.onComplete ? resolveCompletionHandler(input.onComplete) : failOnBreakingChanges;
|
|
16
14
|
const rules = input.rules
|
|
17
15
|
? input.rules
|
|
18
16
|
.filter(isString)
|
|
@@ -23,7 +21,7 @@ function handler(input) {
|
|
|
23
21
|
}
|
|
24
22
|
return rule;
|
|
25
23
|
})
|
|
26
|
-
.filter(
|
|
24
|
+
.filter(f => f)
|
|
27
25
|
: [];
|
|
28
26
|
const changes = yield core.diff(input.oldSchema, input.newSchema, rules, {
|
|
29
27
|
checkUsage: input.onUsage ? resolveUsageHandler(input.onUsage) : undefined,
|
|
@@ -33,9 +31,9 @@ function handler(input) {
|
|
|
33
31
|
return;
|
|
34
32
|
}
|
|
35
33
|
logger.Logger.log(`\nDetected the following changes (${changes.length}) between schemas:\n`);
|
|
36
|
-
const breakingChanges = changes.filter(
|
|
37
|
-
const dangerousChanges = changes.filter(
|
|
38
|
-
const nonBreakingChanges = changes.filter(
|
|
34
|
+
const breakingChanges = changes.filter(change => change.criticality.level === core.CriticalityLevel.Breaking);
|
|
35
|
+
const dangerousChanges = changes.filter(change => change.criticality.level === core.CriticalityLevel.Dangerous);
|
|
36
|
+
const nonBreakingChanges = changes.filter(change => change.criticality.level === core.CriticalityLevel.NonBreaking);
|
|
39
37
|
if (breakingChanges.length) {
|
|
40
38
|
reportBreakingChanges(breakingChanges);
|
|
41
39
|
}
|
|
@@ -48,7 +46,7 @@ function handler(input) {
|
|
|
48
46
|
onComplete({ breakingChanges, dangerousChanges, nonBreakingChanges });
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
|
-
const index = commands.createCommand(
|
|
49
|
+
const index = commands.createCommand(api => {
|
|
52
50
|
const { loaders } = api;
|
|
53
51
|
return {
|
|
54
52
|
command: 'diff <oldSchema> <newSchema>',
|
|
@@ -134,21 +132,21 @@ function sortChanges(changes) {
|
|
|
134
132
|
function reportBreakingChanges(changes) {
|
|
135
133
|
const label = logger.symbols.error;
|
|
136
134
|
const sorted = sortChanges(changes);
|
|
137
|
-
sorted.forEach(
|
|
135
|
+
sorted.forEach(change => {
|
|
138
136
|
logger.Logger.log(`${label} ${logger.bolderize(change.message)}`);
|
|
139
137
|
});
|
|
140
138
|
}
|
|
141
139
|
function reportDangerousChanges(changes) {
|
|
142
140
|
const label = logger.symbols.warning;
|
|
143
141
|
const sorted = sortChanges(changes);
|
|
144
|
-
sorted.forEach(
|
|
142
|
+
sorted.forEach(change => {
|
|
145
143
|
logger.Logger.log(`${label} ${logger.bolderize(change.message)}`);
|
|
146
144
|
});
|
|
147
145
|
}
|
|
148
146
|
function reportNonBreakingChanges(changes) {
|
|
149
147
|
const label = logger.symbols.success;
|
|
150
148
|
const sorted = sortChanges(changes);
|
|
151
|
-
sorted.forEach(
|
|
149
|
+
sorted.forEach(change => {
|
|
152
150
|
logger.Logger.log(`${label} ${logger.bolderize(change.message)}`);
|
|
153
151
|
});
|
|
154
152
|
}
|
|
@@ -6,9 +6,7 @@ import { existsSync } from 'fs';
|
|
|
6
6
|
|
|
7
7
|
function handler(input) {
|
|
8
8
|
return __awaiter(this, void 0, void 0, function* () {
|
|
9
|
-
const onComplete = input.onComplete
|
|
10
|
-
? resolveCompletionHandler(input.onComplete)
|
|
11
|
-
: failOnBreakingChanges;
|
|
9
|
+
const onComplete = input.onComplete ? resolveCompletionHandler(input.onComplete) : failOnBreakingChanges;
|
|
12
10
|
const rules = input.rules
|
|
13
11
|
? input.rules
|
|
14
12
|
.filter(isString)
|
|
@@ -19,7 +17,7 @@ function handler(input) {
|
|
|
19
17
|
}
|
|
20
18
|
return rule;
|
|
21
19
|
})
|
|
22
|
-
.filter(
|
|
20
|
+
.filter(f => f)
|
|
23
21
|
: [];
|
|
24
22
|
const changes = yield diff(input.oldSchema, input.newSchema, rules, {
|
|
25
23
|
checkUsage: input.onUsage ? resolveUsageHandler(input.onUsage) : undefined,
|
|
@@ -29,9 +27,9 @@ function handler(input) {
|
|
|
29
27
|
return;
|
|
30
28
|
}
|
|
31
29
|
Logger.log(`\nDetected the following changes (${changes.length}) between schemas:\n`);
|
|
32
|
-
const breakingChanges = changes.filter(
|
|
33
|
-
const dangerousChanges = changes.filter(
|
|
34
|
-
const nonBreakingChanges = changes.filter(
|
|
30
|
+
const breakingChanges = changes.filter(change => change.criticality.level === CriticalityLevel.Breaking);
|
|
31
|
+
const dangerousChanges = changes.filter(change => change.criticality.level === CriticalityLevel.Dangerous);
|
|
32
|
+
const nonBreakingChanges = changes.filter(change => change.criticality.level === CriticalityLevel.NonBreaking);
|
|
35
33
|
if (breakingChanges.length) {
|
|
36
34
|
reportBreakingChanges(breakingChanges);
|
|
37
35
|
}
|
|
@@ -44,7 +42,7 @@ function handler(input) {
|
|
|
44
42
|
onComplete({ breakingChanges, dangerousChanges, nonBreakingChanges });
|
|
45
43
|
});
|
|
46
44
|
}
|
|
47
|
-
const index = createCommand(
|
|
45
|
+
const index = createCommand(api => {
|
|
48
46
|
const { loaders } = api;
|
|
49
47
|
return {
|
|
50
48
|
command: 'diff <oldSchema> <newSchema>',
|
|
@@ -130,21 +128,21 @@ function sortChanges(changes) {
|
|
|
130
128
|
function reportBreakingChanges(changes) {
|
|
131
129
|
const label = symbols.error;
|
|
132
130
|
const sorted = sortChanges(changes);
|
|
133
|
-
sorted.forEach(
|
|
131
|
+
sorted.forEach(change => {
|
|
134
132
|
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
135
133
|
});
|
|
136
134
|
}
|
|
137
135
|
function reportDangerousChanges(changes) {
|
|
138
136
|
const label = symbols.warning;
|
|
139
137
|
const sorted = sortChanges(changes);
|
|
140
|
-
sorted.forEach(
|
|
138
|
+
sorted.forEach(change => {
|
|
141
139
|
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
142
140
|
});
|
|
143
141
|
}
|
|
144
142
|
function reportNonBreakingChanges(changes) {
|
|
145
143
|
const label = symbols.success;
|
|
146
144
|
const sorted = sortChanges(changes);
|
|
147
|
-
sorted.forEach(
|
|
145
|
+
sorted.forEach(change => {
|
|
148
146
|
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
149
147
|
});
|
|
150
148
|
}
|
package/package.json
CHANGED
|
@@ -1,51 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-inspector/diff-command",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Compare GraphQL Schemas",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@graphql-inspector/commands": "3.4.0",
|
|
11
|
+
"@graphql-inspector/core": "3.4.0",
|
|
12
|
+
"@graphql-inspector/logger": "3.4.0",
|
|
13
|
+
"tslib": "^2.0.0"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "kamilkisiela/graphql-inspector",
|
|
18
|
+
"directory": "packages/commands/diff"
|
|
19
|
+
},
|
|
5
20
|
"keywords": [
|
|
6
21
|
"graphql",
|
|
7
22
|
"graphql-inspector",
|
|
8
23
|
"graphql-inspector-command",
|
|
9
24
|
"tools"
|
|
10
25
|
],
|
|
11
|
-
"sideEffects": false,
|
|
12
|
-
"main": "dist/index.js",
|
|
13
|
-
"module": "dist/index.mjs",
|
|
14
|
-
"exports": {
|
|
15
|
-
".": {
|
|
16
|
-
"require": "./dist/index.js",
|
|
17
|
-
"import": "./dist/index.mjs"
|
|
18
|
-
},
|
|
19
|
-
"./*": {
|
|
20
|
-
"require": "./dist/*.js",
|
|
21
|
-
"import": "./dist/*.mjs"
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"typings": "dist/index.d.ts",
|
|
25
|
-
"typescript": {
|
|
26
|
-
"definition": "dist/index.d.ts"
|
|
27
|
-
},
|
|
28
26
|
"author": {
|
|
29
27
|
"name": "Kamil Kisiela",
|
|
30
28
|
"email": "kamil.kisiela@gmail.com",
|
|
31
29
|
"url": "https://github.com/kamilkisiela"
|
|
32
30
|
},
|
|
33
31
|
"license": "MIT",
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"peerDependencies": {
|
|
40
|
-
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"@graphql-inspector/commands": "3.1.4",
|
|
44
|
-
"@graphql-inspector/core": "3.1.4",
|
|
45
|
-
"@graphql-inspector/logger": "3.1.4",
|
|
46
|
-
"tslib": "^2.0.0"
|
|
32
|
+
"main": "index.js",
|
|
33
|
+
"module": "index.mjs",
|
|
34
|
+
"typings": "index.d.ts",
|
|
35
|
+
"typescript": {
|
|
36
|
+
"definition": "index.d.ts"
|
|
47
37
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"require": "./index.js",
|
|
41
|
+
"import": "./index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./*": {
|
|
44
|
+
"require": "./*.js",
|
|
45
|
+
"import": "./*.mjs"
|
|
46
|
+
}
|
|
50
47
|
}
|
|
51
48
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = () => process.exit(2);
|
package/__tests__/assets/rule.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = ({ changes }) => changes;
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import '@graphql-inspector/testing';
|
|
2
|
-
import { mockCommand } from '@graphql-inspector/commands';
|
|
3
|
-
import { mockLogger, unmockLogger } from '@graphql-inspector/logger';
|
|
4
|
-
import yargs from 'yargs';
|
|
5
|
-
import { buildSchema } from 'graphql';
|
|
6
|
-
import { resolve } from 'path';
|
|
7
|
-
import createCommand from '../src';
|
|
8
|
-
|
|
9
|
-
const oldSchema = buildSchema(/* GraphQL */ `
|
|
10
|
-
type Post {
|
|
11
|
-
id: ID
|
|
12
|
-
title: String
|
|
13
|
-
createdAt: String
|
|
14
|
-
modifiedAt: String
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type Query {
|
|
18
|
-
post: Post!
|
|
19
|
-
}
|
|
20
|
-
`);
|
|
21
|
-
const newSchema = buildSchema(/* GraphQL */ `
|
|
22
|
-
type Post {
|
|
23
|
-
id: ID!
|
|
24
|
-
title: String!
|
|
25
|
-
createdAt: String!
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type Query {
|
|
29
|
-
post: Post!
|
|
30
|
-
}
|
|
31
|
-
`);
|
|
32
|
-
|
|
33
|
-
const diff = createCommand({
|
|
34
|
-
config: {
|
|
35
|
-
commands: [],
|
|
36
|
-
loaders: [],
|
|
37
|
-
},
|
|
38
|
-
loaders: {
|
|
39
|
-
async loadSchema(pointer) {
|
|
40
|
-
if (pointer.includes('old')) {
|
|
41
|
-
return oldSchema;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return newSchema;
|
|
45
|
-
},
|
|
46
|
-
async loadDocuments() {
|
|
47
|
-
throw new Error('Not implemented');
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe('diff', () => {
|
|
53
|
-
let spyReporter: jest.SpyInstance;
|
|
54
|
-
let spyProcessExit: jest.SpyInstance;
|
|
55
|
-
let spyProcessCwd: jest.SpyInstance;
|
|
56
|
-
|
|
57
|
-
beforeEach(() => {
|
|
58
|
-
yargs.reset();
|
|
59
|
-
spyProcessExit = jest.spyOn(process, 'exit');
|
|
60
|
-
spyProcessExit.mockImplementation();
|
|
61
|
-
|
|
62
|
-
spyProcessCwd = jest
|
|
63
|
-
.spyOn(process, 'cwd')
|
|
64
|
-
.mockImplementation(() => __dirname);
|
|
65
|
-
|
|
66
|
-
spyReporter = jest.fn();
|
|
67
|
-
mockLogger(spyReporter as any);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
afterEach(() => {
|
|
71
|
-
yargs.reset();
|
|
72
|
-
unmockLogger();
|
|
73
|
-
spyProcessExit.mockRestore();
|
|
74
|
-
spyProcessCwd.mockRestore();
|
|
75
|
-
spyReporter.mockRestore();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('should load graphql file', async () => {
|
|
79
|
-
await mockCommand(diff, 'diff old.graphql old.graphql');
|
|
80
|
-
|
|
81
|
-
expect(spyReporter).toHaveBeenCalledNormalized('No changes detected');
|
|
82
|
-
expect(spyReporter).not.toHaveBeenCalledNormalized(
|
|
83
|
-
'Detected the following changes',
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('should load different schema from graphql file', async () => {
|
|
88
|
-
await mockCommand(diff, 'diff old.graphql new.graphql');
|
|
89
|
-
|
|
90
|
-
expect(spyReporter).not.toHaveBeenCalledWith('No changes detected');
|
|
91
|
-
expect(spyReporter).toHaveBeenCalledNormalized(
|
|
92
|
-
'Detected the following changes (4) between schemas:',
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
expect(spyProcessExit).toHaveBeenCalledWith(1);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('should load rule by name', async () => {
|
|
99
|
-
await mockCommand(
|
|
100
|
-
diff,
|
|
101
|
-
'diff old.graphql new.graphql --rule suppressRemovalOfDeprecatedField',
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
expect(spyReporter).not.toHaveBeenCalledNormalized('does not exist');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('should load rules with local path from fs', async () => {
|
|
108
|
-
await mockCommand(
|
|
109
|
-
diff,
|
|
110
|
-
'diff old.graphql new.graphql --rule ./assets/rule.js',
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(spyReporter).not.toHaveBeenCalledNormalized('does not exist');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test('should load rules with absolute path from fs', async () => {
|
|
117
|
-
await mockCommand(
|
|
118
|
-
diff,
|
|
119
|
-
`diff old.graphql new.graphql --rule ${resolve(
|
|
120
|
-
__dirname,
|
|
121
|
-
'assets/rule.js',
|
|
122
|
-
)}`,
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
expect(spyReporter).not.toHaveBeenCalledNormalized('does not exist');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('should render error if file does not exist', async () => {
|
|
129
|
-
await mockCommand(diff, `diff old.graphql new.graphql --rule noop.js`);
|
|
130
|
-
|
|
131
|
-
expect(spyReporter).toHaveBeenCalledNormalized('does not exist');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('should render error if file does not exist', async () => {
|
|
135
|
-
await mockCommand(
|
|
136
|
-
diff,
|
|
137
|
-
`diff old.graphql new.graphql --onComplete ${resolve(
|
|
138
|
-
__dirname,
|
|
139
|
-
'assets/onComplete.js',
|
|
140
|
-
)}`,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
expect(spyProcessExit).toHaveBeenCalledWith(2);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('should render error if file does not exist', async () => {
|
|
147
|
-
await mockCommand(
|
|
148
|
-
diff,
|
|
149
|
-
`diff old.graphql new.graphql --onComplete noop.js`,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
expect(spyReporter).toHaveBeenCalledNormalized('does not exist');
|
|
153
|
-
});
|
|
154
|
-
});
|
package/dist/package.json
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@graphql-inspector/diff-command",
|
|
3
|
-
"version": "3.1.4",
|
|
4
|
-
"description": "Compare GraphQL Schemas",
|
|
5
|
-
"sideEffects": false,
|
|
6
|
-
"peerDependencies": {
|
|
7
|
-
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
8
|
-
},
|
|
9
|
-
"dependencies": {
|
|
10
|
-
"@graphql-inspector/commands": "3.1.4",
|
|
11
|
-
"@graphql-inspector/core": "3.1.4",
|
|
12
|
-
"@graphql-inspector/logger": "3.1.4",
|
|
13
|
-
"tslib": "^2.0.0"
|
|
14
|
-
},
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "kamilkisiela/graphql-inspector",
|
|
18
|
-
"directory": "packages/commands/diff"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"graphql",
|
|
22
|
-
"graphql-inspector",
|
|
23
|
-
"graphql-inspector-command",
|
|
24
|
-
"tools"
|
|
25
|
-
],
|
|
26
|
-
"author": {
|
|
27
|
-
"name": "Kamil Kisiela",
|
|
28
|
-
"email": "kamil.kisiela@gmail.com",
|
|
29
|
-
"url": "https://github.com/kamilkisiela"
|
|
30
|
-
},
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"main": "index.js",
|
|
33
|
-
"module": "index.mjs",
|
|
34
|
-
"typings": "index.d.ts",
|
|
35
|
-
"typescript": {
|
|
36
|
-
"definition": "index.d.ts"
|
|
37
|
-
},
|
|
38
|
-
"exports": {
|
|
39
|
-
".": {
|
|
40
|
-
"require": "./index.js",
|
|
41
|
-
"import": "./index.mjs"
|
|
42
|
-
},
|
|
43
|
-
"./*": {
|
|
44
|
-
"require": "./*.js",
|
|
45
|
-
"import": "./*.mjs"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CommandFactory,
|
|
3
|
-
createCommand,
|
|
4
|
-
ensureAbsolute,
|
|
5
|
-
GlobalArgs,
|
|
6
|
-
parseGlobalArgs,
|
|
7
|
-
} from '@graphql-inspector/commands';
|
|
8
|
-
import {
|
|
9
|
-
Change,
|
|
10
|
-
CompletionArgs,
|
|
11
|
-
CompletionHandler,
|
|
12
|
-
UsageHandler,
|
|
13
|
-
CriticalityLevel,
|
|
14
|
-
diff as diffSchema,
|
|
15
|
-
DiffRule,
|
|
16
|
-
Rule,
|
|
17
|
-
} from '@graphql-inspector/core';
|
|
18
|
-
import { bolderize, Logger, symbols } from '@graphql-inspector/logger';
|
|
19
|
-
import { existsSync } from 'fs';
|
|
20
|
-
import { GraphQLSchema } from 'graphql';
|
|
21
|
-
|
|
22
|
-
export { CommandFactory };
|
|
23
|
-
|
|
24
|
-
export async function handler(input: {
|
|
25
|
-
oldSchema: GraphQLSchema;
|
|
26
|
-
newSchema: GraphQLSchema;
|
|
27
|
-
onComplete?: string;
|
|
28
|
-
onUsage?: string;
|
|
29
|
-
rules?: Array<string | number>;
|
|
30
|
-
}) {
|
|
31
|
-
const onComplete = input.onComplete
|
|
32
|
-
? resolveCompletionHandler(input.onComplete)
|
|
33
|
-
: failOnBreakingChanges;
|
|
34
|
-
|
|
35
|
-
const rules = input.rules
|
|
36
|
-
? input.rules
|
|
37
|
-
.filter(isString)
|
|
38
|
-
.map((name): Rule => {
|
|
39
|
-
const rule = resolveRule(name);
|
|
40
|
-
|
|
41
|
-
if (!rule) {
|
|
42
|
-
throw new Error(`\Rule '${name}' does not exist!\n`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return rule;
|
|
46
|
-
})
|
|
47
|
-
.filter((f) => f)
|
|
48
|
-
: [];
|
|
49
|
-
|
|
50
|
-
const changes = await diffSchema(input.oldSchema, input.newSchema, rules, {
|
|
51
|
-
checkUsage: input.onUsage ? resolveUsageHandler(input.onUsage) : undefined,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (changes.length === 0) {
|
|
55
|
-
Logger.success('No changes detected');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
Logger.log(
|
|
60
|
-
`\nDetected the following changes (${changes.length}) between schemas:\n`,
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const breakingChanges = changes.filter(
|
|
64
|
-
(change) => change.criticality.level === CriticalityLevel.Breaking,
|
|
65
|
-
);
|
|
66
|
-
const dangerousChanges = changes.filter(
|
|
67
|
-
(change) => change.criticality.level === CriticalityLevel.Dangerous,
|
|
68
|
-
);
|
|
69
|
-
const nonBreakingChanges = changes.filter(
|
|
70
|
-
(change) => change.criticality.level === CriticalityLevel.NonBreaking,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (breakingChanges.length) {
|
|
74
|
-
reportBreakingChanges(breakingChanges);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (dangerousChanges.length) {
|
|
78
|
-
reportDangerousChanges(dangerousChanges);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (nonBreakingChanges.length) {
|
|
82
|
-
reportNonBreakingChanges(nonBreakingChanges);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
onComplete({ breakingChanges, dangerousChanges, nonBreakingChanges });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export default createCommand<
|
|
89
|
-
{},
|
|
90
|
-
{
|
|
91
|
-
oldSchema: string;
|
|
92
|
-
newSchema: string;
|
|
93
|
-
rule?: Array<string | number>;
|
|
94
|
-
onComplete?: string;
|
|
95
|
-
onUsage?: string;
|
|
96
|
-
} & GlobalArgs
|
|
97
|
-
>((api) => {
|
|
98
|
-
const { loaders } = api;
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
command: 'diff <oldSchema> <newSchema>',
|
|
102
|
-
describe: 'Compare two GraphQL Schemas',
|
|
103
|
-
builder(yargs) {
|
|
104
|
-
return yargs
|
|
105
|
-
.positional('oldSchema', {
|
|
106
|
-
describe: 'Point to an old schema',
|
|
107
|
-
type: 'string',
|
|
108
|
-
demandOption: true,
|
|
109
|
-
})
|
|
110
|
-
.positional('newSchema', {
|
|
111
|
-
describe: 'Point to a new schema',
|
|
112
|
-
type: 'string',
|
|
113
|
-
demandOption: true,
|
|
114
|
-
})
|
|
115
|
-
.options({
|
|
116
|
-
rule: {
|
|
117
|
-
describe: 'Add rules',
|
|
118
|
-
array: true,
|
|
119
|
-
},
|
|
120
|
-
onComplete: {
|
|
121
|
-
describe: 'Handle Completion',
|
|
122
|
-
type: 'string',
|
|
123
|
-
},
|
|
124
|
-
onUsage: {
|
|
125
|
-
describe: 'Checks usage of schema',
|
|
126
|
-
type: 'string',
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
},
|
|
130
|
-
async handler(args) {
|
|
131
|
-
try {
|
|
132
|
-
const oldSchemaPointer = args.oldSchema;
|
|
133
|
-
const newSchemaPointer = args.newSchema;
|
|
134
|
-
const apolloFederation = args.federation || false;
|
|
135
|
-
const aws = args.aws || false;
|
|
136
|
-
const method = args.method?.toUpperCase() || 'POST';
|
|
137
|
-
const { headers, leftHeaders, rightHeaders, token } =
|
|
138
|
-
parseGlobalArgs(args);
|
|
139
|
-
|
|
140
|
-
const oldSchemaHeaders = {
|
|
141
|
-
...(headers ?? {}),
|
|
142
|
-
...(leftHeaders ?? {}),
|
|
143
|
-
};
|
|
144
|
-
const newSchemaHeaders = {
|
|
145
|
-
...(headers ?? {}),
|
|
146
|
-
...(rightHeaders ?? {}),
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const oldSchema = await loaders.loadSchema(
|
|
150
|
-
oldSchemaPointer,
|
|
151
|
-
{
|
|
152
|
-
headers: oldSchemaHeaders,
|
|
153
|
-
token,
|
|
154
|
-
method,
|
|
155
|
-
},
|
|
156
|
-
apolloFederation,
|
|
157
|
-
aws,
|
|
158
|
-
);
|
|
159
|
-
const newSchema = await loaders.loadSchema(
|
|
160
|
-
newSchemaPointer,
|
|
161
|
-
{
|
|
162
|
-
headers: newSchemaHeaders,
|
|
163
|
-
token,
|
|
164
|
-
method,
|
|
165
|
-
},
|
|
166
|
-
apolloFederation,
|
|
167
|
-
aws,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
await handler({
|
|
171
|
-
oldSchema,
|
|
172
|
-
newSchema,
|
|
173
|
-
rules: args.rule,
|
|
174
|
-
onComplete: args.onComplete,
|
|
175
|
-
onUsage: args.onUsage,
|
|
176
|
-
});
|
|
177
|
-
} catch (error) {
|
|
178
|
-
Logger.error(error);
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
function sortChanges(changes: Change[]) {
|
|
186
|
-
return changes.slice().sort((a, b) => {
|
|
187
|
-
const aPath = a.path || '';
|
|
188
|
-
const bPath = b.path || '';
|
|
189
|
-
|
|
190
|
-
if (aPath > bPath) {
|
|
191
|
-
return 1;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (bPath > aPath) {
|
|
195
|
-
return -1;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return 0;
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function reportBreakingChanges(changes: Change[]) {
|
|
203
|
-
const label = symbols.error;
|
|
204
|
-
const sorted = sortChanges(changes);
|
|
205
|
-
|
|
206
|
-
sorted.forEach((change) => {
|
|
207
|
-
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function reportDangerousChanges(changes: Change[]) {
|
|
212
|
-
const label = symbols.warning;
|
|
213
|
-
const sorted = sortChanges(changes);
|
|
214
|
-
|
|
215
|
-
sorted.forEach((change) => {
|
|
216
|
-
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function reportNonBreakingChanges(changes: Change[]) {
|
|
221
|
-
const label = symbols.success;
|
|
222
|
-
const sorted = sortChanges(changes);
|
|
223
|
-
|
|
224
|
-
sorted.forEach((change) => {
|
|
225
|
-
Logger.log(`${label} ${bolderize(change.message)}`);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function resolveRule(name: string): Rule | undefined {
|
|
230
|
-
const filepath = ensureAbsolute(name);
|
|
231
|
-
if (existsSync(filepath)) {
|
|
232
|
-
return require(filepath);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return DiffRule[name as keyof typeof DiffRule];
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function resolveCompletionHandler(name: string): CompletionHandler | never {
|
|
239
|
-
const filepath = ensureAbsolute(name);
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
require.resolve(filepath);
|
|
243
|
-
} catch (error) {
|
|
244
|
-
throw new Error(`CompletionHandler '${name}' does not exist!`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const mod = require(filepath);
|
|
248
|
-
|
|
249
|
-
return mod?.default || mod;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function resolveUsageHandler(name: string): UsageHandler | never {
|
|
253
|
-
const filepath = ensureAbsolute(name);
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
require.resolve(filepath);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
throw new Error(`UsageHandler '${name}' does not exist!`);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const mod = require(filepath);
|
|
262
|
-
|
|
263
|
-
return mod?.default || mod;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function failOnBreakingChanges({ breakingChanges }: CompletionArgs) {
|
|
267
|
-
const breakingCount = breakingChanges.length;
|
|
268
|
-
|
|
269
|
-
if (breakingCount) {
|
|
270
|
-
Logger.error(
|
|
271
|
-
`Detected ${breakingCount} breaking change${
|
|
272
|
-
breakingCount > 1 ? 's' : ''
|
|
273
|
-
}`,
|
|
274
|
-
);
|
|
275
|
-
process.exit(1);
|
|
276
|
-
} else {
|
|
277
|
-
Logger.success('No breaking changes detected');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function isString(val: any): val is string {
|
|
282
|
-
return typeof val === 'string';
|
|
283
|
-
}
|